延时与定时

延时操作:

长延时,长于一个时间滴答的。

忙等待:

通过监视jiffies寄存器实现

while(time_before(jiffies, j1))

cpu_relax();

cpu_relax()的实现与平台有关,大部分系根本不会做任何事情,而在对称多线程系统上,可能会将处理器让给其它线程。

如果进入循环前禁用了中断,那么jiffies值就不会被更新,while永远为真。只能重启了。


主动让出处理器:

在不需要处理器时让出处理器,但仍在运行队列

调用schedule函数实现

while(time_before(jiffies, j1))

schedule();

如果系统很忙时,会延时很长时间


超时:

睡眠,等超时时间到时唤醒

#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);

timeout表示的是要等待的jiffies值,而不是绝对时间值,这个值用有符号数表示,一般是相减的结果,当提供的超时值是负数时,会通过printk产生一天抱怨信息。如果超时到期, 会返回0,。而如果有其它事件唤醒,会产生剩余的延迟实现,返回值从来不会是负数,即使因为系统负荷而导致真正的延迟时间超过预期。


如不是等待特定事件而延迟,可使用schedule_timeout函数,避免声明和使用多余的等待队列头

#include<linux/sched.h>

signed long schedule_timeout(signed long timeout);

schedule_timeout要求使用者首先设置当前进程状态:

set_current_state(TASK_INTERRUPTIBLE);

schedule_timeout(delay);



短延时:

当处理硬件的延时时,通常最多涉及到几十个毫秒

以下函数分别延时指定数量的纳秒、微秒、和毫秒时间:

#include<linux/delay.h>

void ndelay(unsigned long nsecs);

void udelay(unsigned long usecs);

void mdelay(unsigned long msecs);

这些函数实际包含在<asm/delay.h>中

所有架构都会实现udelay,但其他函数可能未定义。


udelay的实现使用软件循环,它根据引导期间计算出的处理器速度以及loops_pre_jiffy整数变量确定循环的次数。为了避免循环计算中的整数溢出,udelay和ndelay为传递给它们的值强加了上限,如果模块无法装载,并显示未解析的符号__bad_udelay,则说明在调用udelay时传入了太大的值。

以上是忙等待的,也有利用休眠的

#include<linux/delay.h>

void msleep(unsigned int millisecs);

unsigned long msleep_interruptible(unsigned int millisecs);

void ssleep(unsigned int seconds);

ssleep不可中断休眠秒级,msleep_interruptible可中断。



内核定时器:

一个内核定时器是一个数据结构,它告诉内核在用户定义的是时间点用用户定义的参数来执行一个用户定义的函数。其实现位于<linux/timer.h>和<kernel/timer.c>文件。

这个调用是异步执行的,定时器运行时,调度该定时器的进程可能正在休眠或在其他处理器上执行,或干脆已退出。

这种异步执行类似于硬件中断发生时的情景。定时函数必须以原子方式执行,这种非进程上下文还带来其他的问题:

不允许访问用户空间,因为非进程上下文没有用户空间

current指针在原子模式下没有任何意义,也是不可用的,因为相关代码和被中断的进程没有任何关联

不能执行休眠和调度。原子代码不可以调用schedule或者wait_event,也不能调用任何可能引起休眠的函数。


内核代码可以通过调用函数in_interrupt()来判断自己是否运行于中断上下文,该函数无需参数,如果处理器运行在中断上下文就返回非零值,而无论是硬件中断还是软件中断。和in_interrupt相关的是in_atomic(),当调度不被允许时,后者的返回值也是非零值;调度不被允许的情况包括硬件和软件中断上下文以及拥有自旋锁的任何时间点。在后一种情况current是可用的,但禁止访问用户空间,因为这会导致调度的发生,不管何时使用in_interrupt(),都应该考虑是否真正该使用in_atomic()。这两个函数均在<asm/hardirq.h>中声明。



定时器API:

内核为驱动程序提供一组用来声明、注册和删除内核定时器的函数:

#include<linux/timer.h>

struct timer_lsit{

unsigned long expirse;

void (*function) (unsigned long);

unsigned long data;

};

void init_timer(struct time_list *timer);

struct timer_list TIMER_INITIALIZER(_function, _expires, _data);


void add_timer(struct timer_list *timer);

void del_timer(struct timer_list *timer);


expiress字段表示期望定时器执行jiffies值,并不使用jiffies_64,到时将调用function函数,data作为参数。


初始化,运行时初始化或者将TIMER_INITIALIZER赋予某个静态结构。在初始化后可在调用add_timer之前修改上面三个公共字段,这三个字段是可有定时器之外的代码访问的。


其它定时器API:

int mod_timer(struct timer_list *timer, unsigned long expires);

更新某个定时器到期时间,经常用于超时定时器(例如软驱的马达关闭时间)

int del_time_sync(struct timer_list *timer);

和del_timer工作类似,但可以确保返回时没有任何CPU在运行定时器函数。一般用来在SMP上避免竞态,如果定时器函数会重新注册自己,则必须保证不会发生重新注册,这通常设置一个由定时器函数检查的“关闭”标志来实现。


int timer_pending(const struct timer_list *timer);

通过读取timer_list结构一个不可见字段来返回定时器是否正在被调度运行。


内核定时器的实现:

内核定时器的实现要满足如下需求及假定:

定时器的管理必须尽可能做到轻量级

其设计必须在活动定时器大量增加时具有很好的伸缩性

大部分定时器会在最多几秒或几分钟内到期,而很少存在长期延迟的定时器

定时器应该在注册它的统一CPU上运行


内核开发者使用的解决方案是利用per-CPU数据结构,timer_list结构的base字段包含了指向该结构的指针。如果base为NULL,定时器尚未调度运行,否则该指针回告诉我们哪个数据结构。

具体实现很有参考价值


你可能感兴趣的:(linux,驱动)