第10章 中断与时钟之内核延时

本文介绍了Linux内核中的延时机制,包括短延迟和长延迟的实现方式,如忙等待和睡眠等待,并探讨了不同延时函数的应用场景及其内部实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

10.6 内核延时

10.6.1 短延迟

Linux内核中提供下列3个函数以分别进行纳秒、微秒和毫秒延迟:

<linux/delay.h>

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()函数的实现方式原理与此类似。kernel在启动时,会运行一个延迟循环校准(Delay Loop Calibration),计算出lPJ(Loops Per Jiffy),内核启动时会打印如下类似信息:

Calibrating delay loop... 530.84 BogoMIPS (lpj=1327104)

如果直接在bootloader(U-boot)传递给内核的bootargs中设置lpj=1327104,则可以省掉这个校准的过程,节省约百毫秒级的开机时间。

毫秒时延(以及更大的秒时延)已经比较大了,在内核中,最好不要直接使用mdelay()函数,会耗费CPU资源。对于毫秒级以上的时延,内核提供如下函数:

void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);

void ssleep(unsigned int seconds);

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

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

10.6.2 长延迟

在内核中进行延迟的一个很直观的方法是比较当前的jiffies和目标jiffies(设置为当前jiffies加上时间间隔的jiffies),直到未来的jiffies达到目标jiffies。

代码清单10.15给出使用忙等待先延迟100个jiffies再延迟2s的实例。

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

#include <linux/jiffies.h>

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

/* 再延迟2s */
unsigned long delay = jiffies + 2*HZ;
while(time_before(jiffies, delay));/*第一个参数为被调用时的jiffies,第二个参数为未来时间的jiffies*/

与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的优化,内核将其jiffies定义为volatile变量,这将保证每次都会重新读取这个变量。因此volatile更多的作用还是避免这种读合并。

10.6.3 睡着延迟

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

代码清单10.16 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.17 schedule_timeout_uninterruptible()和schedule_timeout_interruptible()

signed long __sched schedule_timeout_uninterruptible(signed long timeout)
{
     __set_current_state(TASK_UNINTERRUPTIBLE);// 设置进程状态为TASK_UNINTERRUPTIBLE
     return schedule_timeout(timeout);
}

signed long __sched schedule_timeout_interruptible(signed long timeout)
{
     __set_current_state(TASK_INTERRUPTIBLE);// 设置进程状态为TASK_INTERRUPTIBLE
     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);// 可以在超时前被打断

总结:

Linux的中断处理分为两个半部,上半部处理紧急的硬件操作,下半部处理不紧急的耗时操作。tasklet(小任务)和工作队列都是中断下半部实现的良好机制,tasklet基于软中断实现(软中断上下文中)。内核定时器也依靠软中断实现。

内核中的延时可以采用忙等待或睡眠等待,为了充分利用CPU资源,使系统有更好的吞吐性能,在对延迟时间的要求并不是很精确的情况下,通常值得推荐睡眠等待,而ndelay()、udelay()忙等待机制在驱动中通常是为了配合硬件上的短时延迟要求,mdelay()通常在驱动中不推荐使用


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值