首页 > 代码库 > Linux内核——定时器和时间管理
Linux内核——定时器和时间管理
定时器和时间管理
系统定时器是一种可编程硬件芯片,它能以固定频率产生中断。该中断就是所谓的定时器中断,它所对应的中断处理程序负责更新系统时间,还负责执行需要周期性运行的任务。系统定时器和时钟中断处理程序是Linux系统内核管理机制中的中枢。
另外一个关注的焦点是动态定时器——一种用来推迟执行程序的工具。比如说,如果软驱马达在一定时间内都未活动,那么软盘驱动程序会使用动态定时器关闭软驱马达。内核可以动态创建或销毁动态定时器。
内核中的时间观念
内核在硬件的帮助下计算和管理时间。硬件为内核提供一个系统定时器用以计算流逝的时间。系统定时器以某种频率自行触发,产生时钟中断,进入内核时钟中断处理程序中进行处理。该频率可以通过编程预定,称为节拍率(tick rate)。连续两次时钟中断的间隔时间称为节拍(tick),它等于节拍率分之一秒。
墙上时间和系统运行时间根据时钟间隔来计算。
节拍率HZ
节拍率(HZ)是时钟中断的频率,表示的一秒内时钟中断的次数。比如 HZ=100 表示一秒内触发100次时钟中断程序。
提高节拍率中断产生更加频繁带来的好处:
提高时间驱动事件的解析度;
提高时间驱动事件的准确度;
内核定时器以更高的频度和准确度;
依赖顶上执行的系统调用poll()和select()能更高的精度运行;
系统时间测量更精细;
提高进程抢占的准确度;
提高节拍率带来的副作用:
中断频率增高系统负担增加;
中断处理程序占用处理器时间增多;
频繁打断处理器高速缓存;
jiffies
jiffies:全局变量,用来记录自系统启动以来产生的节拍总数。启动时内核将该变量初始化为0;此后每次时钟中断处理程序增加该变量的值。每一秒钟中断次数HZ,jiffies一秒内增加HZ。系统运行时间 = jiffie/HZ.
jiffies用途:计算流逝时间和时间管理。
jiffies 变量总是无符号长整数(unsignedlong),因此,在32位体系结构上是32位,在64位体系结构是64位,当 jiffies 的值超过它的最大存放范围后就会发生溢出,它的值会回绕到0。内核提供了四个宏来帮助比较节拍计数,它们能正确地处理节拍计数回绕情况。
硬时钟和定时器
实时时钟(RTC):用来持久存放系统时间的设备,即便系统关闭后,靠主板上的微型电池提供电力保持系统的计时。系统启动内核通过读取RTC来初始化墙上时间,改时间存放在xtime变量中。
系统定时器:内核定时机制,注册中断处理程序,周期性触发中断,响应中断处理程序。
时钟中断处理程序
时钟中断处理程序可以划分为两个部分:体系结构相关部分和体系结构无关部分。与体系结构相关的例程作为系统定时器的中断处理程序而注册到内核中,以便在产生时钟中断时,它能够相应地运行。虽然处理程序的具体工作依赖于特定的体系结构,但是绝大多数处理程序最低限度都要执行如下工作:
1)获得 xtime_lock锁,以便对访问 jiffies_64和墙上时间 xtime进行保护。
2)需要时应答或重新设置系统时钟。
3)周期性地使用墙上时间更新实时时钟。
4)调用体系结构无关的时钟例程:do_timer()。
中断服务程序主要通过调用与体系结构无关的例程 do_timer执行下面的工作:
1)给 jiffies_64变量增加 1 (这个操作即使是在 32 位体系结构上也是安全的,因为前面已经获得了 xtime_lock锁)。
2)更新资源消耗的统计值,比如当前进程所消耗的系统时间和用户时间。
3)执行已经到期的动态定时器。
4)执行 scheduler_tick函数。
5)更新墙上时间,该时间存放在 xtime变量中。
6)计算平均负载值。
实际时间
实际时间就是现实中钟表上显示的时间,其实内核中并不常用这个时间,主要是用户空间的程序有时需要获取当前时间,所以内核中也管理着这个时间。实际时间的获取是在开机后,内核初始化时从RTC读取的。内核读取这个时间后就将其放入内核中的 xtime 变量中,并且在系统的运行中不断更新这个值。
定时器
定时器:管理内核时间的基础,推后或执行时间执行某些代码。时器由结构 time_list表示:
struct timer_list { struct list_head entry; /* 定时器链表的入口 */ unsigned long expires; /* 以 jiffies 为单位的定时值 */ spinlock_t lock; /* 保护定时器的锁 */ void (*function)(unsigned long); /* 定时器处理函数 */ unsigned long data; /* 传给处理函数的长整形参数 */ struct tvec_t_base_s *base; /* 定时器内部值,用户不要使用 */ };
使用定时器:
struct timer_list my_timer; init_timer( &my_timer ); /* 初始化定时器 */ my_timer.expires = jiffies + delay; /* 定时器超时时的节拍数 */ my_timer.data = http://www.mamicode.com/0; /* 给定时器处理函数传入 0 值 */ >如果需要更改超时时间,可以调用 mod_timer函数:mod_timer函数不管 my_timer是否已被激活,一旦从 mod_timer返回,my_timer都被激活而且设置了新的定时值。如果调用时 my_timer未被激活,该函数返回 0,否则返回 1。
如果需要停止定时器,可以使用 del_timer和 del_timer_sync函数。
延迟执行
内核代码除了使用定时器或下半部机制以外还需要其他方法来推迟执行任务。这种推迟通常发生在等待硬件完成某些工作时,而且等待的时间往往非常短。
忙等待:
忙等待想要延迟的时间时节拍的整数倍,或者精确要求不高时才可以使用。
unsigned long delay = jiffies + 5 * HZ; while( time_before( jiffies, delay ) ) cond_resched();
cond_resched函数将调度一个新程序投入运行,但它只有在设置完 need_resched 标志后,才能生效。换句话说,该方法有效的条件是系统中存在更重要的任务需要运行。注意因为该方法需要调用调度程序,所以它只能在进程上下文中使用。
短延迟
void udelay( unsigned long usecs ); void mdelay( unsigned long msecs );前一个函数利用忙循环将任务延迟到指定的微秒数后运行,后者延迟指定的毫秒数。
schedule_timeout
该方法会让需要延迟执行的任务睡眠到指定的延迟时间耗尽后再重新运行,但不保证睡眠时间正好等于指定的延迟时间——只能尽量使睡眠时间接近指定的延迟时间。当指定的时间到期后,内核唤醒被延迟的任务并将其重新放回运行队列。
/* 将任务设置为可中断睡眠状态 */ set_current_state( TASK_INTERRUPTIBLE ); /* 小睡一会儿,“s”秒后唤醒 */ schedule_timeout( s * HZ);唯一的参数是延迟的相对时间,单位为 jiffies,注意在调用 schedule_timeout 函数前必须首先将任务设置为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 两种状态之一,否则任务不会睡眠。
由于schedule_timeout()函数需要调用调度程序,所以调用它的代码必须保证能够睡眠。简而言之,调用代码必须处于进程上下文中,并且不能持有锁。
设置超时时间,在等待队列上睡眠
进程上下文中的代码为了等待特定事假发生,可以将自己放入等待队列,然后调用调度程序去执行新任务。一旦事件发生后,内核调用 wake_up函数唤醒在睡眠队列上的任务,使其重新投入运行。
有时,等待队列上的某个任务可能即在等待一个特定事件到来,又在等待一个特定时间到来——就看谁来的更快。这种情况下,代码可以简单的使用schedule_timeout()函数代替schedule()函数,这样一来,当希望指定时间到期,任务都会被唤醒。当然,代码需要检查被唤醒的原因——有可能是被事件唤醒,也有可能因为延迟的时间到期,还可能因为接收到了信号——然后执行相应的操作。
被激活或未被激活的定时器都可以使用,如果定时器还未被激活,该函数返回 0,否则返回 1。注意,不需要为已经超时的定时器调用该函数,因为它们会自动被删除。两者区别在于 del_timer_sync 会等待其他处理器上运行的定时器处理程序都退出才执行。所以 del_timer_sync不能在中断上下文中使用。但在安全性方面考虑,优先使用 del_timer_sync。
参考
http://www.cnblogs.com/bastard/archive/2012/09/21/2696393.html
http://www.cnblogs.com/pennant/archive/2012/12/31/2839545.html
http://www.cnblogs.com/wang_yb/archive/2013/05/10/3070373.html
Linux内核设计与实现