mingdu.zheng <at> gmail <dot> com
http://blog.csdn.net/zoomdy/article/details/8869843
1. 概述
嘀嗒定时器(Tick)是操作系统的核心部件之一,操作系统使用嘀嗒定时器实现时间片轮、延时、超时判断等特性。 本文介绍eCos系统中的嘀嗒定时器的使用和实现,以及需要考虑的一些问题。
嘀嗒定时器在eCos中称做RealTimeClock(RTC),一般情况下,RTC是指提供年月日时分秒的实时时钟, 在eCos中RTC表示嘀嗒定时器,WallClock(挂钟)才是指提供年月日时分秒的实时时钟,为什么是这样?
通常使用定时器模块为系统提供嘀嗒定时服务,Cortex-M提供了专用的SysTick定时器。
嘀嗒定时器与硬件相关的代码是由HAL提供的,与硬件无关的代码由内核的clock.cxx提供,ISR和DSR也是由clock.cxx提供的。
2. 配置项
共有3个配置项与嘀嗒定时器有关,分别是:Real-time clock numerator(
CYGNUM_HAL_RTC_NUMERATOR
)、 Real-time clock denominator(
CYGNUM_HAL_RTC_DENOMINATOR
)、Real-time clock period(
CYGNUM_HAL_RTC_PERIOD
), 这3个配置项通常在平台层HAL或变体层HAL定义。
CYGNUM_HAL_RTC_NUMERATOR
和
CYGNUM_HAL_RTC_DENOMINATOR
定义嘀嗒定时器中断间隔时间, 中断间隔时间 =
CYGNUM_HAL_RTC_NUMERATOR
/
CYGNUM_HAL_RTC_DENOMINATOR
,单位为纳秒(ns)。 默认情况下
CYGNUM_HAL_RTC_NUMERATOR
= 1000000000ns = 1s,
CYGNUM_HAL_RTC_DENOMINATOR
= 100, 中断间隔 = 1000000000 / 100 = 10000000ns = 10ms。在
CYGNUM_HAL_RTC_NUMERATOR
= 1s 的情况下,
CYGNUM_HAL_RTC_DENOMINATOR
等于每秒产生的嘀嗒数。 据eCos参考手册的描述,采用分子分母的形式来定义中断间隔的原因是提高分辨率减少误差, 在
CYGNUM_HAL_RTC_NUMERATOR
不能被
CYGNUM_HAL_RTC_DENOMINATOR
整除的情况下,确实是这么一回事。
CYGNUM_HAL_RTC_PERIOD
是嘀嗒定时器中断间隔时间内CPU运行的时钟数,这个选项用来初始化定时器硬件, 一般情况下,
CYGNUM_HAL_RTC_PERIOD
=
CPU主频
/
CYGNUM_HAL_RTC_DENOMINATOR
。
3. 内核函数
与嘀嗒定时器相关的内核函数包括
cyg_current_time
、
cyg_thread_delay
以及支持超时机制的通信和同步函数。
cyg_tick_count_t
cyg_current_time
(void):读取当前嘀嗒计数器值,返回值类型为cyg_tick_count_t,这是个64位无符号整数类型。
void
cyg_thread_delay
(cyg_tick_count_t delay):线程延时,delay为需要延时的嘀嗒数。
支持超时机制的通信和同步函数包括
cyg_mbox_timed_get
、
cyg_mbox_timed_put
、
cyg_semaphore_timed_wait
、
cyg_flag_timed_wait
、
cyg_cond_timed_wait
,这些函数都有一个abstime参数,abstime是嘀嗒计数器值的绝对值, 这是与
cyg_thread_delay
的delay参数不同的,在使用这些函数前首先要调用
cyg_current_time
获取当前计数器值,然后加上超时时间再传给abstime参数, 为什么不使用和
cyg_thread_delay
一样的相对计数器值呢?下面是使用超时等待条件变量的一个简单例子。
res = cyg_cond_timed_wait( cv, cyg_current_time()+10 );
4. HAL代码
HAL为内核实现嘀嗒定时器提供3个硬件相关的宏定义:
HAL_CLOCK_INITIALIZE
、
HAL_CLOCK_RESET
、
CYGNUM_HAL_INTERRUPT_RTC
HAL_CLOCK_INITIALIZE
( _period_ )对定时器进行初始化,通常对定时器模块相关的寄存器进行设置, 但是不会安装ISR和DSR,也不会使能中断,这部分工作由内核在初始化嘀嗒定时器时完成。 _period_为
CYGNUM_HAL_RTC_PERIOD
。
HAL_CLOCK_RESET
( _vec_, _period_ )清除定时器中断标志,复位计数器值,在ISR中调用, _vec_为
CYGNUM_HAL_INTERRUPT_RTC
,_period_为
CYGNUM_HAL_RTC_PERIOD
。
CYGNUM_HAL_INTERRUPT_RTC
嘀嗒定时器的中断号,定时器中断的ISR和DSR是由内核提供的, 只有知道中断号的情况下,才能安装ISR和DSR,以及屏蔽和使能中断。
5. 内核代码
与嘀嗒定时器相关的内核代码主要在
kernel/.../src/common/clock.cxx
中实现。 嘀嗒定时器功能由C++类
Cyg_RealTimeClock
提供,clock.cxx定义了一个全局的
Cyg_RealTimeClock
实例对象Cyg_RealTimeClock::rtc, 内核API通过该实例实现嘀嗒定时器相关特性。
class Cyg_RealTimeClock : public Cyg_Clock(1)
{
Cyg_Interrupt interrupt;(2)
static cyg_uint32 isr(cyg_vector vector, CYG_ADDRWORD data);(3)
static void dsr(cyg_vector vector, cyg_ucount32 count, CYG_ADDRWORD data);
Cyg_RealTimeClock();
static Cyg_RealTimeClock rtc;(4)
};
Cyg_RealTimeClock Cyg_RealTimeClock::rtc CYG_INIT_PRIORITY( CLOCK );(5)
(1) | 继承自 , 继承自 。 |
(2) | 嘀嗒定时器的中断实例。 |
(3) | ISR和DSR函数。 |
(4) | 全局的 实例,这里将其声明为 类自身的静态变量,因此通过 ::rtc引用。 |
(5) | Cyg_RealTimeClock::rtc在这里定义。 |
Cyg_RealTimeClock::Cyg_RealTimeClock()(1)
: Cyg_Clock(rtc_resolution),
interrupt(CYGNUM_HAL_INTERRUPT_RTC,
CYGNUM_KERNEL_COUNTERS_CLOCK_ISR_PRIORITY,
(CYG_ADDRWORD)this, isr, dsr)
{
HAL_CLOCK_INITIALIZE( CYGNUM_KERNEL_COUNTERS_RTC_PERIOD );(2)
interrupt.attach();(3)
interrupt.unmask_interrupt(CYGNUM_HAL_INTERRUPT_RTC);(4)
Cyg_Clock::real_time_clock = this;
}
(1) | 构造函数,调用基类构造函数以及Cyg_Interrupt实例构造函数。 |
(2) | 调用HAL提供的 初始化硬件。 |
(3) | 安装中断服务ISR和DSR。 |
(4) | 使能定时器中断,使能中断后不一定就能产生嘀嗒中断,因为CPU那里还有一个中断总开关。 |
cyg_uint32 Cyg_RealTimeClock::isr(cyg_vector vector, CYG_ADDRWORD data)(1)
{
HAL_CLOCK_RESET( CYGNUM_HAL_INTERRUPT_RTC, CYGNUM_KERNEL_COUNTERS_RTC_PERIOD );(2)
Cyg_Interrupt::acknowledge_interrupt(CYGNUM_HAL_INTERRUPT_RTC);
return Cyg_Interrupt::CALL_DSR|Cyg_Interrupt::HANDLED;
}
(1) | 嘀嗒定时器的ISR。(已删减非关键代码) |
(2) | 调用HAL提供的定时器复位宏,复位定时器,并重新初始化定时器的计数器值。 |
void Cyg_RealTimeClock::dsr(cyg_vector vector, cyg_ucount32 count, CYG_ADDRWORD data)(1)
{
Cyg_RealTimeClock *rtc = (Cyg_RealTimeClock *)data;
rtc->tick( count );(2)
}
(1) | 嘀嗒定时器的DSR。(已删减非关键代码) |
(2) | DSR仅调用 基类的tick函数,嘀嗒定时器的关键特性是在 中实现的。 |
void Cyg_Counter::tick( cyg_uint32 ticks )(1)
{
// Increment the counter in a loop so we process
// each tick separately. This is easier than trying
// to cope with a range of increments.
while( ticks-- )(2)
{
Cyg_Scheduler::lock();(3)
// increment the counter, note that it is
// allowed to wrap.
counter += increment;(4)
// now check for any expired alarms
Cyg_Alarm_List *alarm_list_ptr; // pointer to list
alarm_list_ptr = &alarm_list;
// Now that we have the list pointer, we can use common code for
// both list organizations.
// With unsorted lists we must scan the whole list for
// candidates. However, we must be careful here since it is
// possible for the function of one alarm to add or remove
// other alarms to/from this list. Having the list shift under
// our feet in this way could be disasterous. We solve this by
// restarting the scan from the beginning whenever we call an
// alarm function.
cyg_bool rescan = true;
while( rescan )(5)
{
Cyg_DNode_T<Cyg_Alarm> *node = alarm_list_ptr->get_head();
rescan = false;
while( node != NULL )
{
Cyg_Alarm *alarm = CYG_CLASSFROMBASE( Cyg_Alarm, Cyg_DNode, node );
Cyg_DNode_T<Cyg_Alarm> *next = alarm->get_next();
CYG_ASSERTCLASS(alarm, "Bad alarm in counter list" );
if( alarm->trigger <= counter )(6)
{
alarm_list_ptr->remove(alarm);
if( alarm->interval != 0 )
{
// The alarm has a retrigger interval.
// Reset the trigger time and requeue
// the alarm.
alarm->trigger += alarm->interval;
add_alarm( alarm );
}
else alarm->enabled = false;
CYG_INSTRUMENT_ALARM( CALL, this, alarm );
// Call alarm function
alarm->alarm(alarm, alarm->data);
rescan = true;
break;
}
// If the next node is the head of the list, then we have
// looped all the way around. The node == next test
// catches the case where we only had one element to start
// with.
if( next == alarm_list_ptr->get_head() || node == next )
node = NULL;
else
node = next;
}
}
Cyg_Scheduler::unlock();
}
}
(1) | 函数,已删减非关键代码, 将对计数器值进行累加, 然后检查链接到该计数器的所有 实例,如果 实例的触发值小于等于当前计数器值,那么调用该 实例的回调函数。 从这里可以看出 和 是密切关联的两个类,因此这两个类互相把对方声明为友元类。 |
(2) | 根据未处理嘀嗒中断的次数进行多次处理,eCos的DSR机制通过记录中断次数的方式保证中断不会丢失,即使在DSR未及时得到执行的情况下。 |
(3) | 对调度器进行加锁,接下来开始访问共享数据,访问之前对调度器加锁以保护共享数据。 |
(4) | 累加计数值,对嘀嗒定时器而言,increment = 1。 |
(5) | 扫描所有链接到该计数器的 实例,链接到同一个计数器的 实例以链表的方式组织。 |
(6) | 检查 实例的触发值是否小于等于当前计数器值,如果是,那么删除该 实例并调用该实例对应的回调函数。 |
6. 计数器溢出
细心的朋友可能已经注意到,在
Cyg_Counter::tick
函数中没有考虑计数器溢出回滚到0的问题, 溢出是迟早会发生的事情呀!eCos使用了64位的无符号整数来存储计数器,假设1毫秒产生一次嘀嗒中断, 也就是说每1毫秒,Cyg_Count::counter加1,那么经过0×10000000000000000 / 1000秒后将会溢出,换算成天数, 那么是0×10000000000000000 / 1000 / 60 / 60 / 24天,等于213503982334天,等于584942417年, 早晚会溢出的,但是那是差不多6亿年后的事情啦,反正我死都死啦,管不着啦!使用64位整数做计数器虽然占用更多的内存空间,但是算法简单许多,还算划得来。