内核定时器及延迟操作

一、内核计数器

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值来测量时间间隔在大多数情况下已经满足了,

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值