一、内核计数器
1.HZ
- 内核通过定时器中断来跟踪时间流,时钟中断由系统定时硬件以周期性的间隔产生,这个时间间隔由内核根据HZ的值设定。如果希望改变系统时钟中断发生的频率,可以修改HZ值,一般保持默认。所以HZ就是指一秒钟内产生多少次时钟中断(单位为次/ 秒),而产生一次时钟中断需要的时间(也即周期性的间隔)T = 1 / HZ,单位为秒(S)。
2.jiffies
- 而每次当时钟中断发生时,内核内部计数器的值就增加1,这个计数器的值在系统引导时被初始化为0,因此,它的值就是自上次操作系统引导以来的时钟滴答数,一般用jifies_64表示。这是一个64位的变量,即使在32位架构也是64位。但是在驱动程序中开发者一般都是访问jiffies变量,为unsigned long类型,要么是jiffies_64,要么是jiffies_64的低32位,通常首选使用jiffies,因为对它的访问很快。仅仅是一个机器指令。比如,从开机过去了2秒钟,那么jiffies = 2 * HZ,因为HZ就表示1秒内发生了多少次时钟中断,而jiffies表示的是从开机后累计的时间内发生了多少次时钟中断(因为每次当时钟中断发生时,内核内部计数器的值就增加1)。
3.jiffies的应用
-
jiffies和jiffies_64均应被看成是只读变量,该变量被声明为volatile,这样可以避免编译器对访问该变量的语句的优化。如果需要计算未来的某个时间戳,如下:
unsigned long j, time_1, time_half, time_n; j = jiffies; time_1 = j + HZ; //未来的 1秒 time_half = j + HZ / 2; //未来的 1/2秒 time_n = j + n * HZ; //未来的 n秒
-
由于jiffies为32位,可能会出现溢出,如果HZ为1000,则 50天后jiffies的值为:50 * 24 * 60 * 60 * 1000 = 4,320,000,000,而32位值最大为4,294,967,295,则每过50天就会溢出一次。所以在比较以上time_n时,则可能已经溢出了,故应该使用以下宏函数:
time_after(unsigned long a, unsigned long b); time_before(unsigned long a, unsigned long b); time_after_eq(unsigned long a, unsigned long b); time_before_eq(unsigned long a, unsigned long b);
如果a是某个jiffies的快照所代表的时间在b后,则第一个宏返回真;如果a在b后或者相等,则第三个宏返回真。也可以用下面方式计算两个jiffies实例之间的差,然后转换为毫秒:
diff = (long) t2 - (long) t1; msec = diff * 1000 / HZ;
-
有时需要将来自用户空间的时间表述方式(struct timeval和struct timespec)和内核表述方法进行转换,这两个数据结构均使用两个数表示精确的时间:在老的,流行的struct timeval中使用秒和毫秒,而较新的struct timespec中则使用秒和纳秒,前者比后者出现的早,但更为常用。为了完成jiffies值与这些结构之间的转换,内核提供了如下四个辅助函数:
unsigned long timespec_to_jiffies(struct timespec *value); void jiffies_to_timespec(unsigned long jiffies, struct timespec *value); unsigned long timeval_to_jiffies(struct timeval *value); void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);
-
对于64位的jiffies_64的访问不像对jiffies的访问那样直接,在64位的计算机上,这两个变量其实是同一个;但是在32位的处理器上,对64位的值的访问不是原子的,需要两个机器指令才能完成,这意味着,在我们读取64位值的高32位及低32位时,可能发生更新,导致获得错误的值。因此,对64位计数器的直接读取是很靠不住的,但如果必须读取64位计数器,则应该使用内核导出的一个特殊辅助函数,该函数为我们为完成适当的锁定:
u64 get_jiffies_64(void) u64 get_jiffies_64(void) { unsigned long seq; u64 ret; do { seq = read_seqbegin(&jiffies_lock); ret = jiffies_64; } while (read_seqretry(&jiffies_lock, seq)); return ret; } extern seqlock_t jiffies_lock; typedef struct { struct seqcount seqcount; spinlock_t lock; } seqlock_t; static inline unsigned read_seqbegin(const seqlock_t *sl) { return read_seqcount_begin(&sl->seqcount); }
可以看到get_jiffies_64,实际上的实现方式为顺序锁,首先读取一个顺序锁的seqcount值,然后获取jiffies_64,最后比较之前读取的seqcount值与现在的seqcount值是否相等,如果不相等,说明jiffies_64值已经发生了改变,需要再次重新读取,这也正是顺序锁的原理。
另外,如果用户想确切地知道HZ的值,只能通过/proc/interrupts获得,比如通过/proc/interrupts获得的计数值除以/proc/uptime文件报告的系统运行时间,既可获得内核的确切HZ值。
4.获取当前时间
-
内核一般通过jiffies获取当前时间。该数值表示的是自最近一次系统启动到当前的时间间隔,它和设备驱动程序无关,因为它的生命周期只限于系统的运行期(uptime)。可以使用jiffies的差值来区分鼠标的单双击,简而言之,利用jiffies值来测量时间间隔在大多数情况下已经满足了,