1.内核中度量时间的几个概念
① 度量时间差
时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据 HZ 值来设定,HZ 是一个体系依赖的值,在 中定义或该文件包含的某个子平台相关文件中。作为通用的规则,即便如果知道 HZ 的值,在编程时应当不依赖这个特定值,而始终使用HZ。对于当前版本,我们应完全信任内核开发者,他们已经选择了最适合的HZ值,最好保持 HZ 的默认值。
对用户空间,内核HZ几乎完全隐藏,用户 HZ 始终扩展为 100。当用户空间程序包含 param.h,每个报告给用户空间的计数器都做了相应转换。对用户来说,确切的 HZ 值只能通过 /proc/interrupts 获得:=(/proc/interrupts) / (/proc/uptime) ,自开机以来系统的滴答数除以运行时间.
对于ARM体系结构:在 文件中的定义如下:
也就是说:HZ 由__KERNEL__和CONFIG_HZ决定。若未定义__KERNEL__,HZ为100;否则为CONFIG_HZ。而CONFIG_HZ是在内核的根目录的.config文件中定义,并且没有在make menuconfig的配置选项中出现。Linux的\arch\arm\configs\s3c2410_defconfig文件中的定义为:
所以正常情况下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 变量,如:
相关的一些简单的工具宏及其定义:
after,是a比b晚,其他类推
用户空间的时间表述法(struct timeval 和 struct timespec )与内核表述法的转换函数:
访问jiffies_64 对于 32-位 处理器不是原子的,这意味着如果这个变量在你正在读取它们时被更新你可能读到错误的值。若需要访问jiffies_64,内核有一个特别的辅助函数,为你完成适当的锁定:
- #include <linux/jiffies.h>
- (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(定义在 ( 由 包含)),原型如下:
④获取当前时间
驱动一般无需知道时钟时间(用年月日、小时、分钟、秒来表达的时间),只对用户程序才需要,如 cron 和 syslogd。 内核提供了一个将时钟时间转变为秒数值的函数:
为了处理绝对时间, 导出了 do_gettimeofday 函数,它填充一个指向 struct timeval 的指针变量。绝对时间也可来自 xtime 变量,一个 struct timespec 值,为了原子地访问它,内核提供了函数 current_kernel_time。它们的精确度由硬件决定,原型是:
- #include <linux/time.h>
- (struct timeval *tv);
- (void);
- /*得到的数据都表示当前时间距UNIX时间基准1970-01-01 00:00:00的相对时间*/
以上两个函数在ARM平台都是通过 xtime 变量(struct timespec xtime;)得到数据的。
全局变量xtime:它是一个timeval结构类型的变量,用来表示当前时间距UNIX时间基准1970-01-01 00:00:00的相对秒数值。
结构timeval是Linux内核表示时间的一种格式(Linux内核对时间的表示有多种格式,每种格式都有不同的时间精度),其时间精度是微秒。该结构是内核表示时间时最常用的一种格式,它定义在头文件include/linux/time.h中,如下所示:
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
其中,成员tv_sec表示当前时间距UNIX时间基准的秒数值,而成员tv_usec则表示一秒之内的微秒值,且1000000>tv_usec>=0。
Linux内核通过timeval结构类型的全局变量xtime来维持当前时间,该变量定义在kernel/timer.c文件中,如下所示:
/* The current time */
volatile struct timeval xtime __attribute__ ((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;