长延迟
有些驱动程序需要延迟比较长的时间,即长于一个时钟滴答;
忙等待
如果想把执行延迟若干个时钟滴答,或者对延迟的精度要求不高,最简单的实现方法就是一个监视jiffies计数器的循环;这种忙等待的实现方法通常具有下面形式,其中j1是延迟终止的jiffies值:
1 while (time_before(jiffies, j1)) 2 cpu_relax();
对cpu_relax的调用将以架构相关的方式执行,其中不执行大量的处理器代码;在许多系统上,该函数根本不会做任何事情;而在SMP系统上,它可能将处理器让给其他线程;但是,只要可能,我们应该避免使用这种方式,这里提到它,只是因为可能偶尔需要运行这段代码,以便更好的理解其他延迟计数;
让出处理器
忙等待为系统整体增加了沉重的负担,因此有必要寻找更好的延迟计数,比如在不需要CPU时主动释放CPU,这可以通过调用shcedule函数实现;
1 while (time_before(jiffies, j1)) { 2 schedule(); 3 }
当进程使用schedule释放处理器之后,没有任何保证说进程可以再随后很快就能得到处理器;除了影响计算机系统整体性能之外,这种用法对驱动程序的需求并不安全,因为延迟可能远大于需求;
超时
通过监视jiffies计数器实现的延迟循环可以工作,但不是非常理想;存在两种构造基于jiffies超时的途径,使用哪个规则则依赖于驱动程序是否在等待其他事件;
如果驱动程序使用等待队列类等待其他一些事件,而我们同时希望在特定的时间段内运行,则可以使用wait_event_timeout或者wait_event_interruptible_timeout函数:
1 #define wait_event_timeout(wq, condition, timeout) 2 #define wait_event_interruptible_timeout(wq, condition, timeout)
上述函数会在给定的等待队列上休眠,但是会在超时到期时返回;这样,这两个函数实现了一种有界的休眠,这种休眠不会永远继续;注意,这里的timeout标识要等待的jiffies值,而不是绝对时间值;如果超时到期,这个两个函数会返回零;如果进程由其他事件唤醒,则会返回剩余的延迟时间,并用jiffies表达;返回值不会是负数,及时因为系统负荷而导致真正的延迟时间超过预期;
上述两个函数需要有人在等待队列上调用wake_up函数,或者超时到期;而在没有人会在等待队列上调用wake_up的情况下,进程将始终会在超时到期时被唤醒;为了适应这种不等待任何事件而延迟的情况,内核提供了schedule_timeout函数;
1 signed long __sched schedule_timeout(signed long timeout)
这里,timeout是用jiffies表示的延迟时间,正常返回值是0,除非在给定超时值之前函数返回(比如响应某个信号);schedule_timeout要求调用者首先设置当前进程的状态,典型的调用如下:
1 set_current_state(TASK_INTERRUPTIBLE); 2 schedule_timeout(delay);
需要注意的是超时到期和真正呗调度执行之间,需要额外的时间;
短延迟
当设备驱动程序需要处理硬件的延迟时,这种延迟通常最多涉及到几十个毫秒;这种情况下,依赖于时钟滴答显然不是正确的方法;
ndelay,udelay和mdelay这几个内核函数很好的完成短延迟任务,它们分别延迟指定数量的纳秒、微秒和毫秒时间,它们的原型如下:
1 #define mdelay(n) 2 #define udelay(n) 3 #define ndelay(n)
需要注意的是,这三个延迟函数均是忙等待函数,因而在延迟过程中无法运行其他任务;因为,我们只能在没有其他实用方法时使用这些函数;
实现毫秒级(或者更长)延迟还有另一种方法,这种方法不涉及忙等待;
1 void msleep(unsigned int msecs) 2 unsigned long msleep_interruptible(unsigned int msecs) 3 void ssleep(unsigned int seconds)
通常,如果我们能够容忍比所请求更长的延迟,则应当使用schedule_timeout、sleep或者ssleep;