1.内核中度量时间的几个概念
① 度量时间差
时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据 HZ 值来设定,HZ 是一个体系依赖的值,在 中定义或该文件包含的某个子平台相关文件中。作为通用的规则,即便如果知道 HZ 的值,在编程时应当不依赖这个特定值,而始终使用HZ。对于当前版本,我们应完全信任内核开发者,他们已经选择了最适合的HZ值,最好保持 HZ 的默认值。
对用户空间,内核HZ几乎完全隐藏,用户 HZ 始终扩展为 100。当用户空间程序包含 param.h,每个报告给用户空间的计数器都做了相应转换。对用户来说,确切的 HZ 值只能通过 /proc/interrupts 获得:=(/proc/interrupts) / (/proc/uptime) ,自开机以来系统的滴答数除以运行时间.
对于ARM体系结构:在 文件中的定义如下:
1. #ifndef __ASM_PARAM_H
2. #define __ASM_PARAM_H
3.
4. #ifdef __KERNEL__
5. /* Internal kernel timer frequency */
6. /* User interfaces are in "ticks" */
7. (USER_HZ) /* like times() */
8. else
9. # define HZ 100
10. #endif
11.
12. #define EXEC_PAGESIZE 4096
13.
14. #ifndef NOGROUP
15. (-1)
16. #endif
17.
18. /* max length of hostname */
19. #define MAXHOSTNAMELEN 64
20.
21. #endif
也就是说:HZ 由__KERNEL__和CONFIG_HZ决定。若未定义__KERNEL__,HZ为100;否则为CONFIG_HZ。而CONFIG_HZ是在内核的根目录的.config文件中定义,并且没有在make menuconfig的配置选项中出现。Linux的\arch\arm\configs\s3c2410_defconfig文件中的定义为:
1.
2. #
3. # Kernel Features
4. #
5. CONFIG_VMSPLIT_3G=y
6. # CONFIG_VMSPLIT_2G is not set
7. # CONFIG_VMSPLIT_1G is not set
8. CONFIG_PAGE_OFFSET=0xC0000000
9. # CONFIG_PREEMPT is not set
10. CONFIG_HZ=200
11. # CONFIG_AEABI is not set
所以正常情况下s3c24x0的HZ为200。这一数值在后面的实验中可以证实。
每次发生一个时钟中断,内核内部计数器的值就加一。这个计数器在系统启动时初始化为 0, 因此它代表本次系统启动以来的时钟嘀哒数。这个计数器是一个 64-位 变量( 即便在 32-位的体系上)并且称为 “jiffies_64”。但是驱动通常访问 jiffies 变量(unsigned long)(根据体系结构的不同:可能是 jiffies_64 ,可能是jiffies_64 的低32位)。使用 jiffies 是首选,因为它访问更快,且无需在所有的体系上实现原子地访问 64-位的 jiffies_64 值。
②使用 jiffies 计数器
这个计数器和用来读取它的工具函数包含在 , 通常只需包含 , 它会自动放入 jiffies.h 。 jiffies 和 jiffies_64 必须被当作只读变量。当需要记录当前 jiffies 值(被声明为 volatile 避免编译器优化内存读)时,可以简单地访问这个 unsigned long 变量,如:
1. #include <linux/jiffies.h>
2. , stamp_1, stamp_half, stamp_n;
3.
4. = jiffies; /* read the current value */
5. = j + HZ; /* 1 second in the future */
6. = j + HZ/2; /* 0.5 second */
7. = j + n * HZ / 1000; /* n milliseconds */
相关的一些简单的工具宏及其定义:
1. #define time_after(a,b) \
2. (typecheck(unsigned long, a) && \
3. (unsigned long, b) && \
4. ((long)(b) - (long)(a) < 0))
5. (a,b) time_after(b,a)
6. (a,b) \
7. (typecheck(unsigned long, a) && \
8. (unsigned long, b) && \
9. ((long)(a) - (long)(b) >= 0))
10. (a,b) time_after_eq(b,a)
after,是a比b晚,其他类推
用户空间的时间表述法(struct timeval(232, 232, 232); background: rgb(249, 249, 249);">
1. #include <linux/time.h> /* #include <linux/jiffies.h> --> \kernel\time.c*/
2.
3. {
4. ; /* seconds */
5. ; /* nanoseconds */
6. };
7. #endif
8.
9. {
10. ; /* seconds */
11. ; /* microseconds */
12. };
13.
14. (struct timespec *value);
15. (unsigned long jiffies, struct timespec *value);
16. (struct timeval(unsigned long jiffies, struct timeval(void);
③处理器特定的寄存器(硬件Timer)
若需测量非常短时间间隔或需非常高的精度,可以借助平台依赖的资源。许多现代处理器包含一个随时钟周期不断递增的计数寄存器(ARM里面直接用硬件定时器),他是进行高精度的时间管理任务唯一可靠的方法。最有名的计数器寄存器是 TSC ( timestamp counter), 在 x86 的 Pentium 处理器开始引入并在之后所有的 CPU 中出现(包括 x86_64 平台)。它是一个 64-位 寄存器,计数 CPU 的时钟周期,可从内核和用户空间读取。在包含了 (一个 x86-特定的头文件, 它的名子代表"machine-specific registers")的代码中可使用这些宏:
- rdtsc(low32,high32);/*原子地读取 64-位TSC 值到 2 个 32-位 变量*/
- (low32);/*读取TSC的低32位到一个 32-位 变量*/
- (var64);/*读 64-位TSC 值到一个 long long 变量*/
- /*下面的代码行测量了指令自身的执行时间:*/
- , end;
- (ini); rdtscl(end);
- ("time lapse: %li\n", end - ini);
一些其他的平台提供相似的功能, 并且内核头文件提供一个体系无关的功能用来代替 rdtsc,称 get_cycles(定义在 ( 由 包含)),原型如下:
1. #include <linux/timex.h>
2. (void);
3. /*这个函数在每个平台都有定义, 但在没有时钟周期计数器的平台上返回 0 */
4.
5. /*由于s3c2410系列处理器上没有时钟周期计数器所以get_cycles定义如下:*/
6. ;
7.
8. (void)
9. {
10. ;
11. }
④获取当前时间
驱动一般无需知道时钟时间(用年月日、小时、分钟、秒来表达的时间),只对用户程序才需要,如 cron 和 syslogd。 内核提供了一个将时钟时间转变为秒数值的函数:
1.
2. /* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
3. * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
4. * => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
5. *
6. * [For the Julian calendar (which was used in Russia before 1917,
7. * Britain & colonies before 1752, anywhere else before 1582,
8. * and is still in use by some communities) leave out the
9. * -year/100+year/400 terms, and add 10.]
10. *
11. * This algorithm was first published by Gauss (I think).
12. *
13. * WARNING: this function will overflow on 2106-02-07 06:28:16 on
14. * machines were long is 32-bit! (However, as time_t is signed, we
15. * will already get problems at other places on 2038-01-19 03:14:08)
16. */
17. unsigned long
18. mktime(const unsigned int year0, const unsigned int mon0,
19. const unsigned int day, const unsigned int hour,
20. const unsigned int min, const unsigned int sec)
21. {
22. unsigned int mon = mon0, year = year0;
23.
24. /* 1..12 -> 11,12,1..10 */
25. if (0 >= (int) (mon -= 2)) {
26. mon += 12; /* Puts Feb last since it has leap day */
27. year -= 1;
28. }
29.
30. return ((((unsigned long)
31.
32.
33.
34.
35. )*60 + sec; /* finally seconds */
36. }
37.
38. EXPORT_SYMBOL(mktime);
39. /*这个函数将时间转换成从1970年1月1日0小时0分0秒到你输入的时间所经过的秒数,溢出时间为2106-02-07 06:28:16。本人认为这个函数的使用应这样:若你要计算2000-02-07 06:28:16 到2000-02-09 06:28:16 所经过的秒数:unsigned long time1 = mktime(2000,2,7,6,28,16)-mktime(2000,2,9,6,28,16); 若还要转成jiffies,就再加上:unsigned long time2 = time1*HZ. 注意溢出的情况!*/
为了处理绝对时间, 导出了 do_gettimeofday 函数,它填充一个指向 struct timeval 的指针变量。绝对时间也可来自 xtime 变量,一个 struct timespec 值,为了原子地访问它,内核提供了函数 current_kernel_time。它们的精确度由硬件决定,原型是:
- #include <linux/time.h>
- (struct timeval(void);
- /*得到的数据都表示当前时间距UNIX时间基准1970-01-01 00:00:00的相对时间*/
以上两个函数在ARM平台都是通过 xtime 变量(struct timespec xtime;)得到数据的。
全局变量xtime:它是一个timeval((aligned (16)));
但是,全局变量xtime所维持的当前时间通常是供用户来检索和设置的,而其他内核模块通常很少使用它(其他内核模块用得最多的是jiffies),因此对xtime的更新并不是一项紧迫的任务,所以这一工作通常被延迟到时钟中断的底半部(bottom half)中来进行。由于bottom half的执行时间带有不确定性,因此为了记住内核上一次更新xtime是什么时候,Linux内核定义了一个类似于jiffies的全局变量wall_jiffies,来保存内核上一次更新xtime时的jiffies值。时钟中断的底半部分每一次更新xtime的时侯都会将wall_jiffies更新为当时的jiffies值。全局变量wall_jiffies定义在kernel/timer.c文件中:
/* jiffies at the most recent update of wall time */
unsigned long wall_jiffies;