12. 时间操作(一)

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

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

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

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

1. 内核中的时间描述

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

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

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

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

#include <linux/jiffies.h>
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 <linux/jiffies.h>
u64 get_jiffies_64(void);

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

#include <linux/jiffies.h>
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 <linux/time.h>
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);

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

2. 获取当前时间

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

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

#include <linux/time.h>
void do_gettimeofday(struct timeval *tv);
struct timespec current_kernel_time(void);

3. 延迟执行

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

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

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

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

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

#include <linux/sched.h>
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(delay);

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

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

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

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

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

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

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

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容