1 #include <linux/jiffies.h> 2 unsigned long j, stamp_1, stamp_half, stamp_n; 3 j = jiffies; /* read the current value */ 4 stamp_1 = j + HZ; /* 1 second in the future */ 5 stamp_half = j + HZ/2; /* half a second */ 6 stamp_n = j + n * HZ / 1000; /* n milliseconds */
要判断两个时间点的先后关系,可以采用下列方法:
1 #include <linux/jiffies.h> 2 int time_after(unsigned long a, unsigned long b); 3 int time_before(unsigned long a, unsigned long b); 4 int time_after_eq(unsigned long a, unsigned long b); 5 int time_before_eq(unsigned long a, unsigned long b);
用户程序用来表示时间的数据结构是struct timeval,struct timespec。前者包含两个数,分别代表秒和毫秒;后者包含两个数,分别代表秒和纳秒。相关的函数定义如下:
1 #include <linux/time.h> 2 unsigned long timespec_to_jiffies(struct timespec *value); 3 void jiffies_to_timespec(unsigned long jiffies, struct timespec *value); 4 unsigned long timeval_to_jiffies(struct timeval *value); 5 void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);
1 #include <asm/msr.h> 2 rdtsc(low32, high32); 3 rdtscl(low32); 4 rdtscll(var64);
通常情况下,只采用寄存器的低32位已经足够——在1GHz的CPU上,每4.2秒才会溢出一次。
#include <linux/timex.h> cycles_t get_cycles(void);
1 #include <linux/time.h> 2 unsigned long mktime (unsigned int year, unsigned int mon, 3 unsigned int day, unsigned int hour, 4 unsigned int min, unsigned int sec);
以及获取当前时间的方法:
1 #include <linux/time.h> 2 void do_gettimeofday(struct timeval *tv); 3 struct timespec current_kernel_time(void);
while (time_before(jiffies, j1)) cpu_relax();
这种延时功能会导致CPU空转,即忙等待,严重影响系统性能。如果在进入循环之前恰巧关闭了中断,jiffies的值不会得到更新,导致循环的条件一直为真。
1 #include <linux/wait.h> 2 long wait_event_timeout(wait_queue_head_t q, condition, long timeout); 3 long wait_event_interruptible_timeout(wait_queue_head_t q, 4 condition, long timeout);
一种典型的使用方法如下:
1 set_current_state(TASK_INTERRUPTTABLE); 2 schedule_timeout(delay);
1 #include <linux/delay.h> 2 void ndelay(unsigned long nsecs); 3 void udelay(unsigned long usecs); 4 void mdelay(unsigned long msecs);
具体实现在asm/delay.h,体系结构相关。三个延时函数都是忙等待。
1 void msleep(unsigned int millisecs); 2 unsigned long msleep_interruptible(unsigned int millisecs); 3 void ssleep(unsigned int seconds);
不带有interruptible的函数不可中断,一定能够睡眠足够的时间。
-
不能访问用户空间——没有进程上下文就没有访问用户空间的路径
-
current指针在原子模式下没有意义——current和被中断的进程没有关系
-
不能休眠或者直接间接调用调度函数,例如调用wait_event,kmalloc,以及信号量
1 #include <linux/timer.h> 2 struct timer_list { 3 /* ... */ 4 unsigned long expires; 5 void (*function)(unsigned long); 6 unsigned long data; 7 }; 8 void init_timer(struct timer_list *timer); 9 struct timer_list TIMER_INITIALIZER(_function, _expires, _data); 10 void add_timer(struct timer_list * timer); 11 int del_timer(struct timer_list * timer);
计时器在expires指明的jiffies后以data作为参数执行function,如果要传入多个参数,可以将其转化为指针。
1 int mod_timer(struct timer_list *timer, unsigned long expires); 2 int del_timer_sync(struct timer_list *timer); 3 int timer_pending(const struct timer_list * timer);
mod_timer可以修改活跃的或者非活跃的计时器。del_timer_sync保证在返回时,计时器不会在任何CPU上运行;这个函数还可以避免SMP系统的竞争条件,和UP内核上的del_timer相同;持有锁的时侯调用del_timer_sync函数要十分小心,如果计时函数也试图获得相同的锁,系统可能会死锁。timer_pending用来判断计时器当前受否被调度来执行。
-
计时器的管理必须尽可能轻量
-
活跃的计时器增加时design should scale well
-
绝大多数计时器会在几秒或至多几分钟内失效,更长时间的计时器十分罕见
-
计时器运行在注册其上的CPU
1 #include <linux/interrupt.h> 2 struct tasklet_struct { 3 /* ... */ 4 void (*func)(unsigned long); 5 unsigned long data; 6 }; 7 void tasklet_init(struct tasklet_struct *t, 8 void (*func)(unsigned long), unsigned long data); 9 DECLARE_TASKLET(name, func, data); 10 DECLARE_TASKLET_DISABLED(name, func, data);
任务集具有以下特点:
-
可以启用或者关闭,但是只有启用的次数和关闭的次数一样多时才会执行
-
可以重新注册自己
-
可以以高优先级或者普通优先级调度执行。前者通常先执行
-
在负载较低的系统上任务集可能会立刻执行,但不会晚于下一个计时器刻度
-
多个任务集可以并发,但是一个任务集只会运行在注册其的CPU上
1 void tasklet_disable(struct tasklet_struct *t); 2 void tasklet_disable_nosync(struct tasklet_struct *t); 3 void tasklet_enable(struct tasklet_struct *t); 4 void tasklet_schedule(struct tasklet_struct *t); 5 void tasklet_hi_schedule(struct tasklet_struct *t); 6 void tasklet_kill(struct tasklet_struct *t);
tasklet_disable关闭给定的任务集,仍然能够通过tasklet_schedule调度,但是直到开启之后才会执行。如果任务集当前正在执行,这个函数会忙等待直到任务集退出;因此,调用此函数之后,可以保证此任务集不会在系统中执行。有nosync的函数不会等正在执行的任务集退出才返回;因此此函数返回时任务集可能还在运行。
tasklet_enable开启给定的任务集,如果任务集已经被调度,会立刻执行。此函数调用的次数必须和关闭函数的调用次数相匹配——内核会记录每个任务集的关闭次数。
tasklet_schedule调度给定的任务集来执行,如果一个任务集在执行之前又被调度,只会执行一次。如果任务集在执行的时候被调度,在执行完成后会再次执行;这样才能保证和其他事件同时发生的事件得到应有的关注,同样保证一个任务集能够重新调度自己。hi的函数可以以高优先级调度任务集。
tasklet_kill保证给定的任务集不会再被调度,如果任务集被调度执行,此函数会等待其完成。在调用此函数前,任务集必须阻止其重新调度自己。
-
任务集的代码运行在软中断上下文中,必须是原子的;而工作队列运行在特殊的内核进程中,因此更零活,可以休眠
-
任务集通常运行在注册其的处理器,工作队列默认也是如此
-
内核代码可以要求工作队列的函数推迟一段明确的时间
-
最关键的是,由于任务集是原子的,执行速度更快;而工作队列不需要是原子的,延迟可能更高
1 struct workqueue_struct *create_workqueue(const char *name); 2 struct workqueue_struct *create_singlethread_workqueue(const char *name);
1 DECLARE_WORK(name, void (*function)(void *), void *data);
1 INIT_WORK(struct work_struct *work, void (*function)(void *), void *data); 2 PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data);
初次创建时要调用INIT_WORK,PREPARE_WORK功能类似,但是不会初始化将work_sturct指向工作队列的指针。如果要对数据结构进行修改,采用后者。
1 int queue_work(struct workqueue_struct *queue, struct work_struct *work); 2 int queue_delayed_work(struct workqueue_struct *queue, 3 struct work_struct *work, unsigned long delay);
queue_delayed_work直到delay指明的jiffies后才会执行work。function会在worker线程的上下文中运行,因此可以睡眠。f由于worker线程位于内核空间,unction无法访问用户空间。要取消未完成的工作队列项:
1 int cancel_delayed_work(struct work_struct *work);
1 void flush_workqueue(struct workqueue_struct *queue);
1 void destroy_workqueue(struct workqueue_struct *queue);
1 int schedule_work(struct work_struct *work); 2 int schedule_delayed_work(struct work_struct *work, unsigned long delay); 3 void flush_scheduled_work(void);