第十一章 定时器和时间管理

定时器和时间管理

内核中大量函数是基于时间驱动的,如定期执行的函数,定时执行的函数。系统定时器是一种可编程硬件芯片,它能以固定频率产生中断——定时中断。

1. 内核中的时间

内核必须在硬件帮助下才能计算和管理时间,硬件为内核提供一个系统定时器用来计算流逝的时间,系统定时器以某种频率自行触发时钟中断,该频率可以编程预定,称作节拍率(tick rate),发生时钟中断时内核会调用特殊的中断处理程序。

节拍率可以编程控制,那么两次连续时钟中断的间隔也可以得到,这个间隔称为节拍(tick),等于1/(tick rate)秒。

内核靠这种已知时钟中断间隔计算实际时间和系统运行时间。

2. 节拍率:HZ

系统定时器频率通过静态预处理定义,也就是HZ赫兹,在系统启动时按照HZ值对硬件进行设置。

内核在asm/param.h定义了这个值。最新内核定义了timekeeper结构保存实际时间,
定义在[linux/timekeeper_internal.h][https://github.com/torvalds/linux/blob/master/include/linux/timekeeper_internal.h]

1
2
3
4
5
6
long timediff(clock_t t1, clock_t t2)
{
long elapsed;
elapsed = ((double)t2 - t1) / CLOCKS_PER_SEC * 1000;
return elapsed;
}

提高节拍率的优劣

提高节拍率使时钟中断产生得更频繁,如此给整个系统带来如下好处:

  • 更高的时钟中断解析度(resolution),可提高时间驱动事件的解析度
  • 提高了时间驱动事件的准确度(accuracy)
    高HZ的优劣
优势 劣势
内核定时器能够以更高的频度和更高的准确度运行;依赖定时器执行的系统调用如poll()/select()能以更高的精度运行;对诸如资源消耗和系统运行时间等的测量精度更高;提高了进程抢占的准确度。 节拍率越高,时钟中断频率越高,导致系统负担越重,因为中断处理程序占用的处理器时间增多。

3. jiffies

全局变量jiffies保存了系统启动以来的节拍总数,jiffies一秒内增加的值为HZ,系统运行时间等于jiffies/HZ。

jiffies是无符号长整数(unsigned long),定义在linux/jiffies.h

需要注意jiffies回绕的问题,比如32位无符号整型容易溢出,通常的处理是将其回绕(wrap around)到0,因此不要直接与jiffies值比较,应该使用内核提供的宏。

4. 硬时钟和定时器

体系结构提供两种设备进行计时:系统定时器和实时时钟。

  • 实时时钟(RTC):用于持久存储系统时间,即便系统关闭,还可以依靠主板上的电池保持系统计时,它最主要的作用是启动时初始化xtime变量。
  • 系统定时器:根本思想是提供一种周期性触发中断机制。x86采用可编程中断时钟PIT,其他时钟资源:时间戳计数TSC

5. 时钟中断处理程序

时钟中断处理程序分为两部分:体系结构相关部分和体系结构无关部分

绝大部分处理程序需要执行如下工作:

  • 获取xtime_lock锁,保护对jiffies_64和墙上时间xtime的访问
  • 需要时应答或重新设置系统时钟
  • 周期性地使用墙上时间更新实时时钟
  • 调用体系结构无关的时钟例程:tick_periodic()

tick_periodic()主要执行如下工作:

  • jiffies_64加一
  • 更新资源消耗的统计值,如当前进程所消耗的系统时间和用户时间
  • 执行已经到期的动态定时器
  • 执行sheduler_tick()函数
  • 更新墙上时间xtime
  • 计算平均负载值

以上操作每1/HZ秒发生一次,可以知道在x86上时钟中断处理程序每秒执行100次或1000次。

6. 实际时间

实际时间也就是墙上时间,内核版本3.3之前定义在kernel/time/timekeeping.c

1
struct timespec xtime;

xtime存放了1970年一月一日(UTC)以来的时间。用户空间获取墙上时间的接口为gettimeofday(),定义在kernel/time.c,文件系统的实现代码存放访问时间戳需要使用xtime。

7. 定时器

定时器(动态定时器或内核定时器)是管理内核时间流逝的基础,定时器常常用于延迟特点时间后执行某些代码。

定时器由结构体timer_list表示,定义在linux/timer.h中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct hlist_node entry; // 定时器链表入口
unsigned long expires; // 以jiffies为单位的定时值
void (*function)(unsigned long); // 定时器处理函数
unsigned long data; // 传给处理函数的参数
u32 flags;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};

内核提供了一组与定时器相关的接口用于定时器操作,声明在,实现在kernel/time/timer.c;

8. 延迟执行

内核除了使用定时器或下半部机制以外,还需要其他方法来推迟执行任务,这种推迟通常发生在等待硬件完成某些工作,等待的时间往往非常短。

  • 忙等待
  • 短延迟
  • schedule_timeout()