《Linux设备驱动开发详解》-- 内核延时

时间:2022-03-18 11:07:56
10.6.1  短延迟
Linux 内核中提供了如下 3 个函数分别进行纳秒、微秒和毫秒延迟。
void ndelay(unsigned long nsecs); 
void udelay(unsigned long usecs); 
void mdelay(unsigned long msecs); 

上述延迟的实现原理本质上是忙等待, 它根据 CPU 频率进行一定次数的循环。 有时候,可以在软件中进行这样的延迟:
void delay(unsigned int time) 

    while (time--); 

ndelay()、udelay()和 mdelay()函数的实现方式机理与此类似。
毫秒时延(以及更大的秒时延)已经比较大了,在内核中,最好不要直接使用mdelay()函数,这将无谓地耗费 CPU 资源,对于毫秒级以上时延,内核提供了下述函数:
void msleep(unsigned int millisecs); 
unsigned long msleep_interruptible(unsigned int millisecs); 
void ssleep(unsigned int seconds); 

上述函数将使得调用它的进程睡眠参数指定的时间,msleep()、ssleep()不能被打断,而 msleep_interruptible()则可以被打断。

NOTE:受系统 HZ 以及进程调度的影响,msleep()类似函数的精度是有限的。

10.6.2  长延迟
        内核中进行延迟的一个很直观的方法是比较当前的 jiffies 和目标 jiffies (设置为当前 jiffies 加上时间间隔的 jiffies) ,直到未来的 jiffies 达到目标 jiffies。代码清单 10.13给出了使用忙等待先延迟 100 个 jiffies 再延迟 2s 的实例。

代码清单 10.13  忙等待时延实例

/*延迟 100 个 jiffies*/ 
unsigned long delay = jiffies + 100;
while (time_before(jiffies, delay));
/*再延迟 2s*/
unsigned long delay = jiffies + 2*HZ;
while (time_before(jiffies, delay));

与 time_before()对应的还有一个 time_after(),它们在内核中定义为(实际上只是将传入的未来时间 jiffies 和被调用时的 jiffies 进行一个简单的比较) :

#define time_after(a,b)        \ 
    (typecheck(unsigned long, a) &&  \ 
    typecheck(unsigned long, b) &&  \ 
    ((long)(b) - (long)(a) < 0)) 
#define time_before(a,b)  time_after(b,a) 

为了防止 time_before()和 time_after()的比较过程中编译器对 jiffies 的优化,内核将其定义为 volatile 变量,这将保证它每次都被重新读取。


10.6.3  睡着延迟
        睡着延迟无疑是比忙等待更好的方式,随着延迟在等待的时间到来之间进程处于睡眠状态,CPU 资源被其他进程使用。schedule_timeout()可以使当前任务睡眠指定的jiffies 之后重新被调度执行,msleep()和 msleep_interruptible()在本质上都是依靠包含了schedule_timeout() 的 schedule_  timeout_uninterruptible() 和schedule_timeout_interruptible()实现的,如代码清单 10.14 所示。
代码清单 10.14  schedule_timeout()的使用

void msleep(unsigned int msecs) 
{
unsigned long timeout = msecs_to_jiffies(msecs) + 1;

while (timeout)
timeout = schedule_timeout_uninterruptible(timeout);
}

unsigned long msleep_interruptible(unsigned int msecs)
{
unsigned long timeout = msecs_to_jiffies(msecs) + 1;

while (timeout && !signal_pending(current))
timeout = schedule_timeout_interruptible(timeout);
return jiffies_to_msecs(timeout);
}
实际上,schedule_timeout()的实现原理是向系统添加一个定时器,在定时器处理函数中唤醒参数对应的进程。
代码清单 10.14 第 6 行和第 14 行分别调用 schedule_timeout_uninterruptible()和schedule_timeout_interruptible() , 这两个函数的区别在于前者在调用schedule_timeout()之前置进程状态为 TASK_INTERRUPTIBLE,后者置进程状态为TASK_UNINTERRUPTIBLE,如代码清单10.15 所示

代码清单 10.15  schedule_timeout_interruptible()和schedule_timeout_uninterruptible() 

signed  long  _  _sched  schedule_timeout_interruptible(signed  long timeout) 
{
_ _set_current_state(TASK_INTERRUPTIBLE);
return schedule_timeout(timeout);
}

signed long _ _sched schedule_timeout_uninterruptible(signed long timeout)
{
_ _set_current_state(TASK_UNINTERRUPTIBLE);
return schedule_timeout(timeout);
}
另外,下面两个函数可以将当前进程添加到等待队列中, 从而在等待队列上睡眠。当超时发生时,进程将被唤醒(后者可以在超时前被打断) ,如下所示:

sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);
interruptible_sleep_on_timeout(wait_queue_head_t*q,  unsigned  long timeout);