关闭

第七章:时间、延迟及延缓操作

180人阅读 评论(0) 收藏 举报

度量时间差

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及其特有的寄存器);

读取时间戳计数器TSCTimestamp 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);

共享队列

详见书

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:13790次
    • 积分:289
    • 等级:
    • 排名:千里之外
    • 原创:15篇
    • 转载:1篇
    • 译文:0篇
    • 评论:0条
    文章存档