eq linux_《Linux设备驱动程序》(十二)——时间操作(一)

之前我们学会了如何编写一个字符设备,并对其中的一些重要操作进行了说明。对于一个完整的设备而已,可能还有许多工作要做。

本节我们将要说一下内核中是如何对时间问题进行操作的。

2abbb77e7ca1488a3b901e2793a28eff.png

本节主要涉及到以下内容:

  • 内核中的时间描述;
  • 如何获取当前时间;
  • 如何进行延时操作;

内核中的时间描述

在内核中,系统定时硬件以周期性的间隔产生中断,内核通过这个中断来跟踪时间流。

在内核中,上面的中断间隔是由HZ常数来决定的,该常数是与体系结构相关的,定义在中(或者在其中的某个子文件中)。HZ的含义是一秒内产生的中断数。如果需要,用户可以修改这个变量,修改完后需要重新编译整个内核以及模块,不过,不建议用户对该常数进行修改。

内核中使用一个变量来作为中断计数器,系统启动时,该计数器清零;上面的中断发生时,计数器的值加1,因此这个计数器记录了系统启动以来的中断数(也称为时钟滴答数)。这个变量称为jiffies_64,是一个64位的变量(即使在32位系统中也是64位的),而我们在编写驱动过程中,常用的变量名称为jiffies,该变量是一个unsigned long型变量,其值要么就是jiffies_64,要么就是jiffies_64的低32位。以上计数器变量及其相关操作定义在文件中。

通过以上jiffies变量和HZ常数,就可以知道或定义一些时间:

#include unsigned long  j = jiffies;unsigned long stamp_1 = j + HZ;  /* 未来1秒 */unsigned long stamp_half = j + HZ / 2;  /* 未来半秒 */unsigned long stamp_n = j + n * HZ / 1000; /* 未来n毫秒 */

上面对于jiffies可以直接读取,但对于jiffies_64变量读取是非原子的,是不可靠的,如果需要使用jiffies_64变量,我们可以使用以下辅助函数:

#include u64 get_jiffies_64(void);

如果两个unsigned long变量表示获取到的jiffies时间,则我们可以通过比较其大小来判断时间先后(较大时间靠后),但我们还需要考虑时间过长而溢出问题(概率很低),因此,我们最好使用内核提供的宏来进行比较:

#include int time_after(unsigned long a, unsigned log b);  /* 判断a时间是否比b时间靠后 */int time_before(unsigned long a, unsigned log b);  /* 判断a时间是否比b时间靠前 */int time_after_eq(unsigned long a, unsigned log b);  /* 判断a时间是否比b时间靠后或相等 */int time_before_eq(unsigned long a, unsigned log b);  /* 判断a时间是否比b时间靠前或相等 */

上面说了内核空间的时间描述。

在用户空间中,用于描述时间的变量是struct timeval(较老,包含秒和毫秒)和struct timespec(较新,包含秒和纳秒)。

如果需要对两个空间的时间描述,可以使用内核提供的函数进行转换:

#include 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(unsgined long jiffies, struct timeval *value);

上面的函数看名字和变量就知道其含义和使用方法,这里就不再描述了。

获取当前时间

通过读取jiffies变量,我们就可以获取当前时间(系统启动后经历的时间)。

但有时候我们也需要处理绝对时间戳,因此,内核中导出了以下两个函数来获取绝对时间:

#include void do_gettimeofday(struct timeval *tv);struct timespec current_kernel_time(void);

延迟执行

设备驱动程序中经常在执行某个操作后需要等待一段时间,让硬件做后某些任务后再继续执行。


对于时间较长的延时(大于一个滴答时钟),可以使用等待队列的方式实现,等待队列的使用可以查看《Linux设备驱动程序》(九)——休眠与唤醒 :

wait_queue_head_t wait;init_waitqueue_head(&wait);wait_event_interrupt_timeout(wait, 0, delay);

上面的condition设为0,因为我们并不是在等待某个特定的事件;delay是超时的时间(为jiffies数量,而不是绝对时间)。因此,上面的代码会进入休眠,等待指定的jiffies数量后继续执行。

为了使用超时功能而定义了等待队里头,这是多余的,因为我们并不需要他。为了避免定义多余的变量,内核提供了以下方法实现延时:

#include set_current_state(TASK_INTERRUPTIBLE);schedule_timeout(delay);

通过set_current_state来设置当前进程的状态(如果是不可中断的设置为TASK_UNINTERRUPTIBLE),这样调度器超时后将其设置为TASK_RUNNING,该进程才会继续执行;如果没有设置进程状态,则进程状态一直都是TASK_RUNNING,此时后面执行schedule_timeout时,其效果等效于schedule,延时不会起作用。


对于短延迟,内核提供了以下几个函数来完成:

#include void ndelay(unsigned long nscs);  /* 延迟指定的纳秒 */void udelay(unsigned long usecs);  /* 延迟指定的微秒 */void mdelay(unsigned long msecs);  /* 延迟指定的毫秒 */

这些函数的实现是与具体架构相关的,所有的架构都实现了udelay,其他函数可能没有定义,会在udelay的基础上提供默认的未定义的其他函数。

需要知道的是,以上延迟并不是说精确延时指定的时间,而是至少延迟指定的时间,可能会更长。

虽然输入的参数均为unsigned long类型的,但一般性的规则是只用在其指定的量级上,即上千纳秒应该使用udelay,而上千微秒则应使用mdelay,而不是输入一个很大的值。

以上的延迟都是忙等待函数,因此在延迟期间不能执行其他任务。

对于毫秒级以上的延迟,内核还提供了一种非忙等待的实现:

#include void msleep(unsigned int millisecs);  /* 延时等地指定毫秒 */unsigned long msleep_interruptible(unsgined int millisecs);  /* 返回剩余毫秒数,一般为0 */void ssleep(unsgined int seconds); /* 延时等待指定秒 */

以上介绍了内核时间的概念、如何获取内核时间以及在当前线程中的延时操作。下一节继续说如何进行异步延时操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值