度量时间差
HZ:每秒钟发生的时钟中断数。
每次当时钟中断发生时,内核内部计数器的值加一。计数器是jiffies_64,无论在32位操作系统上还是在64位操作系统上,它都是一个64位的变量,并且是只读变量。
使用jiffies计数器
函数及变量声明在<linux/jiffies.h>中,但此文件包含于<linux/sched.h>中。
jiffies_64被声明为volatile型,避免编译器对访问该变量的语句进行优化。
比较时间先后的函数有:
int time_after(unsigned long a, unsigned long b);
int time_before(unsigned 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;
msec = diff * 1000 / HZ; //时间差转化为毫秒
jiffies与用户空间时间表达方法的转换:
unsigned long timspec_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);
考虑到系统的差异性,取jiffies值建议通过函数进行:
u64 get_jiffies_64(void);
处理器特定的寄存器
x86专用头文件:<asm/msr.h> (machine specific registers及其特有的寄存器);
读取时间戳计数器TSC(Timestamp counter)的宏:
rdtsc(low32, high 32);
rdtscl(low32);
rdtscll(var64);
一个与平台无关的函数:
#include <linux/timex.h>
cycles_t get_cycles(void);
在SMP系统中,时间戳计数器不会在多个处理器间保持同步。为了确保获得一致的值,我们需要为查询该计数器的代码禁止抢占。
获取当前时间
内核提供将墙钟时间转化为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);
另外一个精度差一点的方法:
#include <linux/time.h>
struct timespec current_kernel_time(void );
延迟--长延迟
延迟--段延迟
#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);
前两个函数将调用进程休眠以达到给定的时间,对interruptible版本的调用是不可中断的。
内核定时器
内核定时器可以用来在未来的某个特定的时间点调度执行某个函数。
在<asm/hardirq.h>中声明两个函数:
in_interrupt();
判断进程是否正运行于中断上下文。若是,则返回值非零。
in_atomic();
判断进程是否正运行在原子模式下。若调度不被允许,即原子模式,返回值非零。
关于定时器,谨记:定时器是竞态的潜在来源,这是由于异步的特定导致的,任何通过定时器访问的数据结构应该针对并发的访问进行保护。
定时器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);
void del_timer(struct timer_list *timer);
expires字段表示期望定时器执行的jiffies值。到达jiffies值时,将调用function函数,并传递data作为参数。
add_timer可以修改三个公共字段。
del_tiemr:在定时器到期前,禁止一个已注册的定时器。
其他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);
通过读取timer_list结构的一个字段返回定时器是否正在被调用。
Tasklet(小任务)机制
tasklet和内核定时器相似:始终在中断期间运行,始终在调度他们的同一个CPU上运行,都接受一个unsigned long参数。不同点:我们不能要求tasklet在某个给定的时间点执行。
tasklet数据结构以及初始化:
#include <linux/interrupt.h> struct tasklet_struct { /*...*/ void (*func)(unsigned long); unsigned long data; } void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); DECALRE_TASKLET(name, func, data); DECLARE_TASKLET_DISABLED(name, func, data); |
内核为每个CPU提供了一组ksoftirq内核线程,用于运行“软件中断”处理例程。
在<kernel/softirq.h>中定义了tasklet相关的函数操作:
void tasklet_disable(struct tasklet_struct *t); void tasklet_disable_nosync(struct tasklet_struct *t); void tasklet_enable(struct tasklet_struct *t); void tasklet_schedule(struct tasklet_struct *t); void tasklet_hi_schedule(struct tasklet_struct *t); void tasklet_kill(struct tasklet_struct *t); |
工作队列
工作队列与tasklet的区别是:
² tasklet在软件中断上下文运行,所以代码必须是原子的,而工作队列可以休眠。
² tasklet始终运行在被初始提交的同一个CPU上,而这只是工作队列的默认工作方式。
² 内核代码可以请求工作队列函数的执行延迟给定的时间间隔。
工作队列结构及相关API定义在<linux/workqueue.h>中。
显式创建一个工作队列:
struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);
create_workqueue会在每个CPU上为这个工作队列创建专用的线程,许多情况下,这样会对性能造成严重影响,因此,如果单个工作足够使用,则应该使用create_singlethread_workqueue函数。
要想工作队列提交一个任务,需要填充一个work_struct结构,可以通过下面宏编译完成:
DECLARE_WORK(name, void (*function)(void *), void *data);
如果运行时构造一个work_struct结构,可以通过下宏初始化:
INIT_WORK(struct work_struct *work, void (*fucntion)(void *), void *data);
PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data);
INIT_WORK完成彻底的初始化工作,在首次构造该结构时,应该使用这个宏。PREPARE_WORK初始化不会用来将work_struct结果链接到工作队列,若结构已经被提交到工作队列,只是修改该结构,则使用该宏,而不是INIT_WORK。
将工作提交到工作队列使用下函数:
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work( struct workqueue_struct *queue, struct work_struct *work, unsigned long delay);
添加成功返回1,返回非零意味着工作已经存在于队列中。
取消某个挂起的工作对立入口项:
int cancel_delayed_work(struct work_struct *work);
若该工作尚未执行,则函数返回非零值,以后不会执行;若该函数调用时工作已经执行,则返回零值,此后应该调用下函数:
void flush_workqueue(struct workqueue_struct *queue);
之后,该调用之前提交的工作函数都不会再系统任何地方运行。
在结束对工作队列的使用后,可调用下面的函数释放相关资源:
void destroy_workqueue(struct workqueue_struct *queue);
共享队列
详见书