Linux 定时器和时间管理
Linux 定时器和时间管理
关于时间,内核需要做的两件事是:
- 保存当前的时间和日期。
- 维持定时器。
硬件和电路
实时时钟(RTC)
实时时钟(Real Time Clock, RTC)是用来持久存放系统时间的设备,即时系统关闭后, 它也可以依靠主板上的电池保持系统的计时。RTC能在IRQ8上发出周期性的中断。系统启动 时,内核通过读取RTC来初始化墙上时间,改时间存放在xtime变量中。
时间戳计数器(TSC)
所有80x86处理器都包含一条CLK输入引线,它接受外部振荡器的时钟信号,另外还包含一 个计数器,它在每个时钟信号到来时加1. 该计数器就是64位的时间戳计数器 (Time Stamp Counter TSC)。
系统定时器
各种体系结构中的定时器的实现不尽相同,但根本思想都是:提供一种周期性触发中断机 制。可以简单地理解为一个可编程的硬件设备,它能以某个可变的频率发出电信号,作为 时钟中断的中断源。
可编程间隔定时器(PIT)
可编程间隔定时器(Programmable Interval Timer PIT)以内核确定的一个固定频率, 发出一个特殊的中断,即时钟中断(timer interrupt)。Linux启动时会给PC的第一个PIT 进行编程,以某个频率发出时钟中断,两个中断间的时间间隔叫做一个节拍(tick)。
CPU本地定时器
80x86处理器的本地APIC中还提供了另一种定时测量设备:CPU本地定时器。 与PIT的区别是:本地APIC定时器只把中断发送给自己的处理器,而PIT产生一个全局性中 断,系统中的任一CPU都可以对其处理。
高精度事件定时器(HPET)
HPET(High Precision Event Timer)俗称高精度定时器,可以提供更高精度的时间测量, 可以通过配置来取代PIT。
ACPI电源管理定时器(ACPI Power Management Timer, ACPI PMT)
ACPI PMT也是一个定时器,主要是用于CPU降频或降压来省电的情况。
内核中实现计时的数据结构和原理
前面说过,计算机可能有多种不同的计时器(timer),在系统初始化的时候,内核会选择 合适的计时器作为中断源。
内核中时间相关的概念
节拍率(tick rate)
系统定时器以某种频率自行触发(经常被称为hitting或popping)时钟中断,该频率可以
编程预定,称为节拍率。节拍率是通过静态预处理定义的,即HZ值,内核在
<asm/param.h>
中定义了这个值。
高HZ的优势是定时器更准,依赖定时执行的系统调用(如poll和epoll)精度更高,对资源 消耗等值的测量有更精细的解析度,进程抢占更准确。高HZ的劣势就是过高的时钟中断意 味着系统负担较重。
节拍(tick)
两次连续时钟中断的间隔时间。
墙上时间(wall time)
实际时间。墙上时间存放于变量xtime中<kernel/time/timekeeping.c>
. 从用户空间获
取墙上时间的主要接口是gettimeofday()
, 在内核中对应系统调用为
sys_gettimeofday()
, 本质上就是读取xtime的内容。xtime的数据结构中存放着自1970
年1月1日以来的秒数,以及自上一秒开始的纳秒数。
jiffies
全局变量jiffies
用来记录系统自启动以来产生的节拍的总数。64位系统中该变量等同于
jiffies_64
.
动态定时器
动态定时器或内核定时器,是内核提供的一个机制,让一些工作可以在一个指定时间之后
运行。定时器的数据结构是struct timer_list
,代码在
这里 。
可以使用内核提供的一组接口来创建、修改和删除定时器。每个定时器的数据结构内都有 一个字段表明这个定时器的到期时间。所有定时器都是以链表的形式存放在一起,为了让 内核寻找到超时的定时器,内核将定时器按它们的超时时间划分为5组。
定时器通过TIMER_SOFTIRQ软中断执行。
时钟中断处理程序
IRQ0表示system timer,IRQ8表示RTC timer,IRQ239就表示本地APIC时钟中断。
利用时钟中断处理的工作
利用时钟中断处理的工作包括:
- 更新系统运行时间和实际时间。
- 在smp系统上,均衡调度程序中各处理器上的运行队列。如果运行队列不均衡的话, 尽量使它们均衡。
- 进行进程调度。
- 运行超时的动态定时器。
- 更新资源消耗和处理器时间的统计值。
在多处理器系统中,所有通用的工作(比如运行超时的动态定时器,系统时间的更新等) ,是通过全局定时器(PIT或HPET等)中断触发的。而与每个CPU相关的工作(比如统计进 程消耗的时间以及资源统计等),是由每个CPU的本地定时器进行触发的。
时钟中断处理程序的主要执行过程
- 获得xtime_lock锁(对jiffies_64和xtime进行保护)。
- 需要是应答或重新设置系统时钟。
- 周期性地使用墙上时间更新RTC。
- 调用体系结构无关的例程tick_periodic():
- 给fiffies_64变量加1.
- 更新资源消耗的统计值,比如当前进程所消耗的系统时间和用户时间。
- 执行已经到期的动态定时器。
- 对当前进程的时间片数值进行更新。
- 更新墙上时间(该事件存放在xtime变量中)。
- 计算平均负载值。
中断处理程序的实现
tick_periodic()
的代码在
这里。
/*
* Periodic tick
*/
static void tick_periodic(int cpu)
{
if (tick_do_timer_cpu == cpu) {
write_seqlock(&jiffies_lock);
/* Keep track of the next tick event */
tick_next_period = ktime_add(tick_next_period, tick_period);
do_timer(1);
write_sequnlock(&jiffies_lock);
}
update_process_times(user_mode(get_irq_regs()));
profile_tick(CPU_PROFILING);
}
其中:
1
调用update_wall_time()来更新墙上时间。
2
调用calc_global_load()来计算平均负载,代码在 这里。
3
调用update_process_times()来更新所耗费的各种节拍数:
update_process_times(user_mode(get_irq_regs()));
而update_process_times()
定义在
这里:
/*
* Called from the timer interrupt handler to charge one tick to the current
* process. user_tick is 1 if the tick is user time, 0 for system.
*/
void update_process_times(int user_tick)
{
struct task_struct *p = current;
int cpu = smp_processor_id();
/* Note: this timer irq context must be accounted for as well. */
account_process_tick(p, user_tick);
run_local_timers();
rcu_check_callbacks(cpu, user_tick);
#ifdef CONFIG_IRQ_WORK
if (in_irq())
irq_work_run();
#endif
scheduler_tick();
run_posix_cpu_timers(p);
}
3.1
user_tick
的值用来指明这次对进程更新的是系统时间还是用户时间。user_tick
的值
通过查看系统寄存器来设置。也就是说,内核对进程进行时间计数时,是根据中断发生时
处理器所处的模式进行分类统计的,它把上一个节拍全部算给了进程。尽管在上个节拍内
,进程可能多次进入和退出了内核模式,而且现在的进程也不一定是上一个节拍内唯一运
行的进程。
3.2
run_local_timers()
标记了一个软中断:raise_softirq(TIMER_SOFTIRQ)
, 来运行所
有到期的定时器。
3.3
scheduler_tick()
函数负责减少当前运行进程的时间片计数值并且在需要时设置
need_resched
标志。在SMP机器中该函数还要负责平衡每个处理器上的运行队列。
参考资料:
- Man pages
- UNIX环境高级编程
- Linux内核设计与实现
- 深入理解Linux内核