处理时间委托包括如下任务,按复杂度依次上升:

  • 测量时间流失和比较时间
  • 知道当前时间
  • 指定时间量的延时操作
  • 调度异步函数在之后的时间发生

一、测量时间流失

系统定时硬件规律的产生定时器中断,在内核启动阶段,根据Hz的值,设置这个间隔时间。

HZ的值各不相同,不同平台硬件的参数也不一样,即便你知道HZ的值,在编程时也不应该依赖它。

1.1 使用jiffies计数器

  • 计数器头文件位于<linux/jiffies.h>,但是你更经常使用<linux/sched.h>
  • jiffies和jiffies_64必须是当作只读的

有时代码需要计算一个将来的时间戳,这个代码不用考虑溢出问题:

#include <linux/jiffies.h>
unsigned long j, stamp_1, stamp_half, stamp_n;
j = jiffies; /* read the current value */
stamp_1 = j + HZ; /* 1 second in the future */
stamp_half = j + HZ/; /* half a second */
stamp_n = j + n * HZ / ; /* n milliseconds */

jiffies stamp

有了上面的时间戳,下面是比较函数:

#include <linux/jiffies.h>
int time_after(unsigned long a, unsigned long b);
int time_before(unisgned long a, unsigned long b);
int time_after_eq(unsigned long a, unsigned long b);
int time_before_eq(unsigned long a, unsigned long b);

time compare

上面的函数其实可以替换为:

diff = (long)t2 - (long)t1;

然后通过转换一个jiffies差为毫秒:

msec = diff *  / HZ
  • 有时内核需要和用户空间交换时间表示,用户空间使用的是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(unsigned long jiffies, struct timeval *value);

timespec和timeval切换jiffies

在32位系统中,读取64位jiffies有同步的问题,所以内核直接给了函数:

#include <linux/jiffies.h>
u64 get_jiffies_64(void);

获取jiffies_64

1.2  处理器特定的寄存器

如果你要测量时间间隔非常短,或者精度要求非常高,那么需要放弃移植性。

大部分CPU设计中,指令时序本质上是不可预测的。CPU制造商引入了一种通过计算时钟周期来度量时间差的简便而可靠的方法。

这是一个64为的寄存器,记录CPU时钟周期数,从内核空间和用户空间都可以读取它。

头文件:<asm/msr.h>(x86专用头文件):

  • rdtsc(low32, high32);
  • rdtscl(low32);
  • rdtscll(var64);

下面的代码可以测试指令自身的运行时间:

unsigned long ini, end;
rdtscl(ini); rdtscl(end);
printk("time lapse: %li\n", end - ini);

指令时间

其他平台也提供了类似的功能,有一个与体系结构无关的函数来代替rdtsc,即get_cycles,定义在<asm/timex.h>(由<linux/timex.h>包含),原型:

#include <linux/timex.h>
cycles_t get_cycles(void);

二、获取当前时间

内核一般通过jiffies值来获取当前时间,利用jiffies来测量时间间隔大多数情况已经足够了。

内核提供了将墙钟时间转换为jiffies值的函数:

#include <linux/time.h>
unsigned long mktime(unsigned int year, unsigned int mon,
unsigned int day, unsigned int hour,
unsigned int min, unsigned int sec);

mktime

虽然在内核空间中我们不必处理时间的人类可读取表达,但有时也需要处理绝对时间戳。用来获取一个秒或微秒值来填充指针变量。

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

还有一种获得时间变量,精度差些:xtime变量(类型为struct timespec)来获得。因为很难原子地使用,有另一个辅助函数current_kernel_time:

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

三、延迟执行

我们仔细检查它们,指出每一个长处和缺点。

3.1 长延时

操作1:如果不是抢占式的,会极大的占用系统内存

while(time_before(jiffies, j1))
cpu_relax();

time_before

操作2:如果CPU在密集的工作,调度dd时间略长

phon% dd bs= count= < /proc/jitbusy

read /proc/jitbusy

超时:如果在等待队列里,延迟过长超时的话,也会结束

#include <linux/wait.h>
long wait_event_timeout(wait_queue_head_t q, condition, long timeout);
long wait_event_interruptible_timeout(wait_queue_head_t q, condition, long timeout);

wait_event_timeout

进程超时时一直唤醒,为了适应这个特别的情况,内核提供了schedule_timeout函数:

#include <linux/sched.h>
singed long schedule_timeout(signed long timeout);

schedule_timeout

timeout是延时的jiffies数

返回值0,除非在给定的timeout流失前返回

3.2 短延时

内核函数ndelay、udelay以及mdelay,这个函数是忙等待:

#include <linux/delay.h>
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

也有不涉及忙等待的办法:

#include <linux/delay.h>
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);

四、内核定时器

一个内核定时器是一个数据结构,它指导内核执行一个用户定义的函数使用一个用户定义的参数在一个用户定义的时间。这个实现在<linux/timer.h>和kernel/timer.c中

4.1 定时器API

内核提供給驱动许多函数来声明,注册,以及取出内核定时器:

#include <linux/timer.h>
struct timer_list {
/* ... */
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
};
void init_timer(struct timer_list *timer);
struct timer_list TIMER_INITIALIZER(_function, _expires, _data); void add_timer(struct timer_list *timer);
int del_timer(struct timer_list *timer);

Timer Code

定时器API包括几个比上面介绍的那些更多的功能。下面的集合是完整的核提供的函数列表:

int mod_timer(struct timer_list *timer, unsigned long expires);
更新一个定时器超时时间
int del_timer_sync(struct timer_list *timer);
和del_timer一样,但返回时,定时器函数不在任何CPU上运行
int timer_pending(const struct timer_list *timer);
返回真或假来指示是否定时器当前被调度运行

4.2 内核定时器的实现

定时器的实现被设计来符号下列要求和假设:

  • 定时器管理必须尽可能简化
  • 设计应当随着激活的定时器数目上升而很好地适应。
  • 大部分定时器在几秒或最多几分钟内到时,而带有长延时的定时器是相当少见
  • 一个定时器应当在注册它的同一个CPU上运行

4.3 Tasklets机制

void tasklet_disable(struct tasklet_struct *t);
禁止给定的tasklet
void tasklet_disable_nosync(struct tasklet_struct *t);
禁止这个tasklet,但是没有等待任何当前运行的函数退出
void tasklet_enable(struct tasklet_struct *t);
使能一个执勤啊被禁止的tasklet,如果这个tasklet已经被调度
void tasklet_schedule(struct tasklet_struct *t);
调度tasklet在更高优先级
void tasklet_hi_schedule(struct tasklet_struct *t);
调度tasklet在更高优先级执行
void tasklet_kill(struct tasklet_struct *t);
这个函数确保了这个tasklet没被再次调度来运行

tasklet

4.4 工作队列

工作队列有一个struct workqueue_struct 类型,在<linux/workqueue.h>中定义:

struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);

每个工作队列有一个或多个专用的进程,它运行提交给这个队列的函数。

使用前需要先填充一个work_struct 结构

DECLARE_WORK(name, void (*function)(void *), void *data);
name:声明的结构名称
function:从工作队列被调用的函数
data:传递给这个函数的值

如果需要建立work_struct 结构在运行时,使用下面2个宏

INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data);

有2个函数来提交工作给一个工作队列:

int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *wrok, unsigned long delay);

如果你需要取消一个挂起的工作队列入口,你可以调用:

int cancel_delayed_work(struct work_struct *work);
返回值是非零如果这个入口在它开始执行前被取消

要绝对确保工作函数没有在cancel_delayed_work返回0后再任何地方运行,必须跟随这个调用来调用:

void flush_workqueue(struct workqueue_struct *queue);

当你用完一个工作队列,你可以去掉它,使用:

void destroy_workqueue(struct workqueue_struct *queue);

LDD3 第7章 Time,Delays and Deferred Work的更多相关文章

  1. Linux Kernel Programming - Time,Delays,and Deferred Work

    Measuring Time Lapses The counter and the utility functions to read it live in <linux/jiffies.h&g ...

  2. LDD3 第15章 内存映射和DMA

    本章内容分为三个部分: 第一部分讲述了mmap系统调用的实现过程.将设备内存直接映射到用户进程的地址空间,尽管不是所有设备都需要,但是能显著的提高设备性能. 如何跨越边界直接访问用户空间的内存页,一些 ...

  3. LDD3 第13章 USB驱动程序

    通用串行总线(USB)是主机和外围设备之间的一种连接.最新USB规范修订增加了理论上高达480Mbps的高速连接. 从拓扑上看,USB子系统并不是以总线的方式来布置的,它是一颗由几个点对点的连接构建而 ...

  4. ldd3 第12章 PCI驱动程序

    PCI接口 PCI寻址 引导阶段 配置寄存器和初始化 MODULE_DEVICE_TABLE 注册PCI驱动程序 佬式PCI探测 激活PCI设备 访问配置空间 访问I/O和内存空间 PCI中断 硬件抽 ...

  5. LDD3 第11章 内核的数据类型

    考虑到可移植性的问题,现代版本的Linux内核的可移植性是非常好的. 在把x86上的代码移植到新的体系架构上时,内核开发人员遇到的若干问题都和不正确的数据类型有关.坚持使用严格的数据类型,并且使用-W ...

  6. LDD3 第10章 中断处理

    各种硬件和处理器打交道的周期不同,并且总是比处理器慢.必须有一种可以让设备在产生某个事件时通知处理器----中断. 中断仅仅是一个信号,如果硬件需要,就可以发送这个信号.Linux处理中断方式和用户空 ...

  7. LDD3 第9章 与硬件通信

    一.I/O端口和I/O内存 每种外设都通过读写寄存器进行控制.大部分外设都有几个寄存器,不管是在内村地址空间还是在I/O地址空间,这些寄存器的访问地址都是连续的. 在硬件层,内存区域和I/O区域没有区 ...

  8. LDD3源码分析之poll分析

    编译环境:Ubuntu 10.10 内核版本:2.6.32-38-generic-pae LDD3源码路径:examples/scull/pipe.c  examples/scull/main.c 本 ...

  9. 驱动: 中断【1】linux中断流程

    通常情况下,当一个给定的中断处理程序正在执行时,所有其他的中断都是打开的,所以这些不同中断线上的其他中断都能被处理,但当前中断总是被禁止的. 将中断处理切为两个部分或两半.中断处理程序上半部(top ...

随机推荐

  1. Mongodb日常管理

    用户管理: MongoDB Enterprise > db.version()3.4.10 1.创建超级管理员:MongoDB Enterprise > use admin MongoDB ...

  2. JavaScript 获取随机整数

    Math.random()方法会返回介于 0(包含) ~ 1(不包含) 之间的一个随机数 假如想要拿到0-10之间的数,只需要将该方法的值*10 即Math.random()*10: 假如想要拿到0- ...

  3. String 与StringBuffer习题

    1: 画出如下几行代码的结构 // 画出如下几行代码的结构 String s1 = "hello"; // value存储在常量池内 String s2 = "hello ...

  4. shtml与html

    前言 在浏览网页的时,忽然发现了一个网址不是以[.html]结尾,而是[.shtml].顿时勾起了我的好奇心,这是什么鬼?于是打开google,开始维基. SHTML 什么是 SHTML 使用SSI( ...

  5. Hibernate的dtd文件和properties文件

    hibernate-configuration-3.0.dtd <!-- Hibernate file-based configuration document. <!DOCTYPE hi ...

  6. 安装go版本

    下载地址(官网):https://golang.org/dl/ 下载地址(国内):https://dl.gocn.io/ 想编译GO,必须先有一个GO的编译器. 创建GO的编译器:[root@node ...

  7. 《剑指offer》面试题8 旋转数组的最小数字 Java版

    (找递增排序旋转数组中的最小数字) 书中方法:这种题目就是要寻找数组的特点,然后根据这个特点去写.旋转后的递增数组分为两段递增序列,我们找到中点,如果比第一个元素大,表示在第一段递增序列里,如果比第一 ...

  8. python 分析 知乎粉丝数据

    昨天花了一下午写了一个小爬虫,用来分析自己的粉丝数据.这个真好玩!今天帮了群里好多大V也爬了他们的数据.运行速度:每分钟5千粉丝以上.暂时先写成这样,这两天要准备补考,没有时间继续玩这个. 下次要改进 ...

  9. CodeChef Sereja and GCD

    Sereja and GCD   Problem code: SEAGCD   Submit All Submissions   All submissions for this problem ar ...

  10. Android关于Activity生命周期详解

    子曰:溫故而知新,可以為師矣.<論語> 学习技术也一样,对于技术文档或者经典的技术书籍来说,指望看一遍就完全掌握,那基本不大可能,所以我们需要经常回过头再仔细研读几遍,以领悟到作者的思想精 ...