Linux Kernel Development——定时器和时间管理Posted on
几个重要的名词HZ:系统定时器频率HZ用来定义系统定时器每隔1秒产生多少个时钟中断Tick:HZ的倒数,系统定时器两次时钟中断的时间间隔Xtime:记录Wall time值,也就是UTC时间,是一个struct timeval结构,在用户空间通过gettimeofday读取Jiffies:记录系统开机以来经过了多少次Tick,定义为unsigned long volatile __jiffy_data jiffies;RTC:实时时钟,是一个硬件时钟,用来持久存放系统时间HZ
HZ是静态编译到内核中的,其定义如下:
// /usr/include/asm-generic/param.h文件中
#ifdef __KERNEL__
# define HZCONFIG_HZ /* Internal kernel timer frequency */
# define USER_HZ 100/* some user interfaces are */
# define CLOCKS_PER_SEC (USER_HZ)/* in “ticks” like times() */
#endif
#ifndef HZ
#define HZ 100
#endif
在2.6以前的内核中,如果改变内核中的HZ值会给用户空间中某些程序造成异常结果。因为内核是以节拍数/秒的形式给用户空间导出这个值的,应用程序便依赖这个特定的HZ值。如果在内核中改变了HZ的定义值,就打破了用户空间的常量关系—用户空间并不知道新的HZ值。
内核更改所有导出的jiffies值。内核定义了USER_HZ来代表用户空间看到的HZ值。在x86体系结构上,由于HZ值原来一直是100,所以USER_HZ值就定义为100。内核可以使用宏jiffies_to_clock_t()将一个有HZ表示的节拍计数转换为一个由USER_HZ表示的节拍计数:
start=jiffies;
//doing some jobs
total_time=jiffies-start;
ticks=jiffies_to_clock_t(total_time);
jiffies_to_clock_t()函数的定义如下:
/*
* Convert jiffies/jiffies_64 to clock_t and back.
*/
clock_t jiffies_to_clock_t(unsigned long x)
{
#if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
# if HZ < USER_HZ
return x * (USER_HZ / HZ);
# else
return x / (HZ / USER_HZ);
# endif
#else
return div_u64((u64)x * TICK_NSEC, NSEC_PER_SEC / USER_HZ);
#endif
}
Jiffies
Jiffies记录了系统启动以来所经过的ticks数,在开机时该值是0,随后每次时钟中断发生时加1.
根据jiffies可以计算系统的开机时间:jiffies/HZ 秒
Jiffies的定义为unsigned long volatile __jiffy_data jiffies;
由于jiffies存在溢出的可能,内核定义了一组辅助函数来处理jiffies的比较操作,香港服务器,操作jiffies时最好使用这些辅助函数:
/*
* These inlines deal with timer wrapping correctly. You are
* strongly encouraged to use them
* 1. Because people otherwise forget
* 2. Because if the timer wrap changes in future you won’t have to
*alter your driver code.
*
* time_after(a,b) returns true if the time a is after time b.
*
* Do this with “<0” and “>=0” to only test the sign of the result. A
* good compiler would generate better code (and a really good compiler
* wouldn’t care). Gcc is currently neither.
*/
#define time_after(a,b)\
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(b) – (long)(a) < 0))
#define time_before(a,b) time_after(b,a)
#define time_after_eq(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(a) – (long)(b) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)
实时时钟RTC
实时时钟是一个硬件时钟,用来持久存放系统时间,系统关闭后靠主板上的微型电池保持计时;
系统启动时,内核通过读取RTC来初始化Wall Time, 并存放在xtime变量中,这是RTC最主要的作用;
当用户修改了时间后,可以用hwclock –w将其保存到RTC中。
系统定时器
每个PC机中都有一个PIT,以通过IRQ0产生周期性的时钟中断信号,作为系统定时器 system timer。当发生时钟中断时,就会自动调用时钟中断处理程序。
时钟中断处理程序分为两个部分:体系结构相关部分和体系结构无关部分。相关的部分作为系统定时器的中断处理程序而注册到内核中,以便在产生时钟中断时,它能够相应地运行。执行的工作如下:
1.获得xtime_lock锁,以便对访问jiffies_64和墙上时间xtime进行保护。
2.需要时应答或重新设置系统时钟。
3.周期性地使用墙上时间更新实时时钟。
4.调用体系结构无关的时间例程:do_timer().
中断服务程序主要通过调用与体系结构无关的例程do_timer()执行下面的工作:
1.给jiffies_64变量加1.
2.更新资源消耗的统计值,比如当前进程所消耗的系统时间和用户时间。
3.执行已经到期的动态定时器.
4.执行scheduler_tick()函数.
5.更新墙上时间,该时间存放在xtime变量中.
6.计算平均负载值.
Xtime
记录Wall time值,也就是UTC时间,是一个struct timeval结构。
在内核空间读写这个xtime变量需要xtime_lock锁,该锁是一个顺序锁(seqlock)。
而在用户空间通过gettimeofday读取xtime,它在内核中对应系统调用为sys_gettimeofday()。
动态定时器
动态定时器并不周期执行,它在超时后就自行销毁。定义器由定义在linux/timer.h中的time_list表示,如下:
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;
unsigned long expires;
struct tvec_base *base;
void (*function)(unsigned long);
unsigned long data;
int slack;
}
内核提供了一组函数用来简化管理定时器的操作。所有这些接口都声明在文件linux/timer.h中,大多数接口在文件kernel/timer.c中获得实现。有了这些接口,我们要做的事情就很简单了:
1.创建定时器:struct timer_list my_timer;
2.初始化定时器:init_timer(&my_timer);
3.根据需要,设置定时器了:
my_timer.expires = jiffies + delay;
my_timer.data = 0;
my_timer.function = my_function;
4.激活定时器:add_timer(&my_timer);
经过上面的几步,定时器就可以开始工作了。然而,一般来说,定时器都在超时后马上就会执行,但是也有可能被推迟到下一时钟节拍时才能运行,所以不能使用它来实现硬实时。
最重要的是今天的心。