Linux内核设计与实现(11)第十一章:定时器和时间管理
1. 内核对时间的使用概述:
内核中有大量的函数都是基于时间驱动的,其中有一些是周期执行的,比如对调度程序中的运行队列进行平衡调整、刷新屏幕、检测当前进程是否用尽了自己的时间片等。
另一些则需要等待一个相对时间后才运行,比如要推后执行的IO操作等。除了这两种情况,内核还必须管理系统的运行时间以及当前时间。
2. 系统时间
系统中管理的时间有2种:实际时间和定时器
2.1 实际时间
实际时间的获取是在开机后,内核初始化时从RTC(实时时钟的缩写)读取的。
内核读取这个时间后就将其放入内核中的 xtime 变量中,并且在系统的运行中不断更新这个值。
当前实际时间(墙上时间)定义为struct timespec xtime;
结构体timespec中有一个long域tv_sec以秒为单位,存放着自1970年1月1日以来经过的时间。
此外还有一个v_nsec域记录自上一秒开始经过的ns数。读取xtime变量需要使用xtime_lock锁,该锁不是普通自旋锁,而是一个seqlock锁。
struct timespec 有两个成员,一个是秒,一个是纳秒, 所以最高精确度是纳秒.
struct timespec
{
time_t tv_sec; /* seconds */ //存放着自1970年1月1日以来经过的时间
long tv_nsec; /* nanoseconds */ //纳秒
};
其他时间相关结构体参考:
linux 时间 time(1)-时间相关结构体和函数详解
https://blog.csdn.net/lqy971966/article/details/107975594
2.2 定时器
内核中的定时器有2种,静态定时器和动态定时器。
2.2.1 静态定时器
静态定时器一般执行了一些周期性的固定工作:
更新系统运行时间,
更新实际时间,
时间片等,
更新资源消耗和处理器时间统计值
2.2.2 动态定时器
动态定时器顾名思义,是在需要时(一般是推迟程序执行)动态创建的定时器,使用后销毁(一般都是只用一次)。
一般我们在内核代码中使用的定时器基本都是动态定时器
定时器是内核中主要使用的时间管理方法,通过定时器,可以有效的调度程序的执行。
3. 基础概念大搜罗:HZ,节拍,jiffies,定时器中断,实时时钟
3.1 HZ 赫兹
HZ:单位时间内完成振动或振荡的次数或周数。
LINUX 系统时钟频率是一个常数HZ来决定的,通常HZ=100,那么他的精度度就是10ms(毫秒)。
也就是说每10ms一次中断。所以一般来说Linux的精确度是10毫秒
3.2 节拍(tick)
连续两次时钟中断的时间间隔就称为节拍(tick)。
3.3 jiffies
简单说就是:电脑开机到现在总共的时钟中断次数
计算:
记录自系统启动以来产生的总节拍数,比如系统启动了 N 秒,那么 jiffies就为 N×HZ
初始化:
系统启动时内核将该变量初始化为0,此后每次时钟中断处理程序会增加该变量的值;
一秒内增加的值即HZ,系统运行时间以秒为单位计算就等于jiffies/HZ。
使用定时器时一般都是以jiffies为单位来延迟程序执行的,比如延迟5个节拍后执行的话,执行时间就是 jiffies+5。
3.3.1 jiffies常用的一些运算
unsigned long time_stamp = jiffies; /* 现在 */
unsigned long next_tick = jiffies+1; /* 从现在开始的下一个节拍 */
unsigned long later = jiffies + 5*HZ; /* 从现在开始后的5秒 */
unsigned long fraction = jiffies + HZ/10; /* 从现在开始后的100ms,就是0.1秒,也就是0.1*HZ */
3.3.2 回绕问题
因为jiffies使用unsigned long,所以在32位机器上(最大值为 2^32-1),时钟频率为100HZ的情况下,497天后会溢出,如果频率为1000HZ,49.7天后就会溢出。
溢出后其值会绕回到0。内核中提供了四个宏用来帮助比较节拍计数,它们能正确地处理节拍计数回绕的情况。
解决回绕……麻烦
include/linux/jiffies.h
3.4 定时器中断:
周期性产生的事件都是由系统定时器驱动的,系统定时器是一种可编程的硬件芯片,它能以固定频率产生中断·,该中断就是所谓的定时器中断。
它所对应的中断处理程序负责更新系统时间,也负责执行需要周期性运行的任务。
3.5 实时时钟
除了系统定时器,体系结构还提供了另一种计时设备,即实时时钟。
实时时钟是用来持久存放系统时间的设备,即便系统关闭后,它也可以靠主板上的微型电池提供的电力保持系统的计时。
实时时钟最主要的作用是在系统启动时初始化xtime变量(墙上时间,象征真实的用户时间)。
4. 定时器执行流程
定时器在内核中用一个链表来保存的,链表的每个节点都是一个定时器。
定时器的生命周期:
初始化-填充-激活/修改-触发-删除
4.1 伪代码实例
//实例
struct timer_list my_timer;
init_timer(&my_timer); //初始化
my_timer.expires = jiffies + delay; //填充定时器 注意单位是节拍
my_timer.data = 0;
my_timer.function = my_function;
add_timer(&my_timer); //激活定时器
mod_timer(&my_timer,jiffies+new_delay); //修改定时器
del_timer(&my_timer); //删除,为了避免竞争条件,建议使用 del_timer_sync 函数来删除定时器
5. 实现程序延迟的方法 schedule_timeout
这个函数会将当前的任务睡眠到指定时间后唤醒,所以等待时不会占用CPU时间。
/* 将任务设置为可中断睡眠状态 */
set_current_state(TASK_INTERRUPTIBLE);
/* 小睡一会儿,“s“秒后唤醒 */
schedule_timeout(s*HZ);
schedule_timeout 一般用于延迟时间较长的程序。也就是延迟大于 1 个节拍(jiffies)。