处理时间委托包括如下任务,按复杂度依次上升:
- 测量时间流失和比较时间
- 知道当前时间
- 指定时间量的延时操作
- 调度异步函数在之后的时间发生
一、测量时间流失
系统定时硬件规律的产生定时器中断,在内核启动阶段,根据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/2; /* half a second */ stamp_n = j + n * HZ / 1000; /* n milliseconds */
有了上面的时间戳,下面是比较函数:
#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);
上面的函数其实可以替换为:
diff = (long)t2 - (long)t1;
然后通过转换一个jiffies差为毫秒:
msec = diff * 1000 / 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);
在32位系统中,读取64位jiffies有同步的问题,所以内核直接给了函数:
#include <linux/jiffies.h> u64 get_jiffies_64(void);
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 ", 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);
虽然在内核空间中我们不必处理时间的人类可读取表达,但有时也需要处理绝对时间戳。用来获取一个秒或微秒值来填充指针变量。
#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();
操作2:如果CPU在密集的工作,调度dd时间略长
phon% dd bs=20 count=5 < /proc/jitbusy 1911226 1912226 1913323 1914323 1919529 1920529 1925632 1926632 1931835 1932835
超时:如果在等待队列里,延迟过长超时的话,也会结束
#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);
进程超时时一直唤醒,为了适应这个特别的情况,内核提供了schedule_timeout函数:
#include <linux/sched.h> singed long schedule_timeout(signed long 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);
定时器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没被再次调度来运行
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);