短延时
Linux 内核中提供了以下 3 个函数分别进行纳秒、微妙和毫秒延迟:
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
上述延迟的实现原理本质上是忙等待,它根据 CPU 频率进行一定次数的循环。
注意:毫秒延时(以及更大的秒延时)已经比较大了,在内核中,最好不要直接使用 mdelay() 函数,这将耗费 CPU 资源,对于毫秒级以上的延时,内核提供了下述函数:
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);
上述函数将使得调用它的进程睡眠参数指定的时间为 millisecs,msleep()、ssleep() 不能被打断,而 msleep_interrupt() 则可以被打断。
一点题外话,如下:
内核在启动时,会运行一个延迟循环校准(Delay Loop Calibration),计算出 lpj (Loops Per Jiffy),内核启动时会打印如下类似信息:
Calibrating delay loop... 530.84 BogoMIPS(lpj = 1327104)
可以直接在 bootloader 传递给内核的 bootargs 中设置 lpj=1327104,则可以省略掉这个校准过程,节省约百毫秒级的开机时间。
长延迟
在内核中进行延迟的一个很直观的方法是比较当前的 jiffies 和目标 jiffies(设置为当前 jiffies 加上时间间隔的 jiffies),直到未来的 jiffies 达到目标的 jiffies。
代码清单如下:
/* 延迟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(),它们在内核中定义为:
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)((b) - (a)) < 0))
#define time_before(a,b) time_after(b,a)
睡着延迟无疑是比忙等待更好的方式,睡着延迟是在等待的时间到来之前进程处于睡眠状态,CPU 资源被其他进程使用,在当前任务的休眠到指定的 jiffies 之后再重新被调度执行。
睡着延迟使用 msleep() 和msleep_interrupt() 函数实现,如下所示:
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);
}
在以上的代码中可以看到 msleep() 函数调用了 schedule_timeout_uninterruptible() 函数,msleep_interruptible() 调用了 schedule_timeout_interruptible() 函数,这两个函数的区别在于前者将进程的状态置为 TASK_INTERRUPTIBLE,后者将进程的状态置为 TASK_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);
}
从以上的两个函数可以看出,最终实现延迟功能的函数是 schedule_timeout()。
schedule_timeout() 的实现原理是向系统添加一个定时器,在定时器处理函数中唤醒与参数对应的进程。