延迟和睡眠机制

本文档旨在回答一个常见问题:“插入延迟的正确方法(TM)是什么?”

驱动程序编写者最常遇到这个问题,他们必须处理硬件延迟,并且可能不是最熟悉 Linux 内核内部工作原理的人。

下表给出了现有函数“族”及其局限性的粗略概述。此概述表不能替代使用前阅读函数描述!

*delay()

usleep_range*()

*sleep()

fsleep()

忙等待循环

基于 hrtimers

基于定时器列表定时器

结合其他方法

在原子上下文中使用

在“短间隔”内精确

取决于

在“长间隔”内精确

不要使用!

最大 12.5% 的松弛

可中断的变体

对于非原子上下文的一般建议可以是

  1. 如果不确定,请使用fsleep()(因为它结合了其他方法的优点)

  2. 尽可能使用*sleep()

  3. 如果*sleep()的精度不足,请使用usleep_range*()

  4. 对于非常非常短的延迟,请使用*delay()

在接下来的章节中找到有关函数“族”的更多详细信息。

*delay()函数族

这些函数使用时钟速度的节拍估计,并将忙等待足够的循环次数以实现所需的延迟。udelay()是基本实现,ndelay()以及mdelay()是变体。

这些函数主要用于在原子上下文中添加延迟。在原子上下文中添加延迟之前,请务必问自己:这真的是必需的吗?

void udelay(unsigned long usec)

插入基于微秒的延迟,并忙等待

参数

unsigned long usec

以微秒为单位的请求延迟

描述

当在原子上下文中延迟时,ndelay()udelay()mdelay()是唯一有效的延迟/睡眠变体。

当在非原子上下文中插入延迟时,如果延迟时间短于对例如 hrtimer 进行排队然后进入调度程序所需的时间,则使用udelay()也是有价值的。但是,为所有系统指定一个通用的阈值并不简单。一个近似值是所有延迟高达 10 微秒的阈值。

当延迟大于体系结构特定的MAX_UDELAY_MS值时,请确保使用mdelay()。否则会存在溢出风险。

请注意,出于多种原因,ndelay()udelay()mdelay()可能会提前返回(https://lists.openwall.net/linux-kernel/2011/01/09/56

  1. 计算的 loops_per_jiffy 过低(由于执行定时器中断所花费的时间。)

  2. 缓存行为会影响执行循环函数所需的时间。

  3. CPU 时钟频率变化。

void ndelay(unsigned long nsec)

插入基于纳秒的延迟,并忙等待

参数

unsigned long nsec

以纳秒为单位的请求延迟

描述

有关ndelay()及其变体的基本信息,请参见udelay()

mdelay

mdelay (n)

插入基于毫秒的延迟,并忙等待

参数

n

以毫秒为单位的请求延迟

描述

有关mdelay()及其变体的基本信息,请参见udelay()

请仔细检查,mdelay()是否是正确的方法,或者是否重构代码是更好的变体,以便能够使用msleep()代替。

usleep_range*()*sleep()函数族

这些函数使用 hrtimers 或定时器列表定时器来提供请求的睡眠持续时间。为了确定哪个函数是正确的使用函数,请考虑一些基本信息

  1. hrtimers 更昂贵,因为它们使用 rb 树(而不是哈希)

  2. 当请求的睡眠持续时间是第一个定时器时,hrtimers 更昂贵,这意味着必须对真正的硬件进行编程

  3. 定时器列表定时器总是提供某种松弛,因为它们是基于节拍的

此处重复通用建议

  1. 如果不确定,请使用fsleep()(因为它结合了其他方法的优点)

  2. 尽可能使用*sleep()

  3. 如果*sleep()的精度不足,请使用usleep_range*()

首先检查fsleep()函数描述,并要了解更多关于精度信息,请查看msleep()函数描述。

usleep_range*()

void usleep_range(unsigned long min, unsigned long max)

睡眠一段近似时间

参数

unsigned long min

要睡眠的最小时间(以微秒为单位)

unsigned long max

要睡眠的最大时间(以微秒为单位)

描述

有关基本信息,请参阅usleep_range_state()

该任务在睡眠期间将处于 TASK_UNINTERRUPTIBLE 状态。

void usleep_range_idle(unsigned long min, unsigned long max)

睡眠一段时间,并考虑空闲时间

参数

unsigned long min

要睡眠的最小时间(以微秒为单位)

unsigned long max

要睡眠的最大时间(以微秒为单位)

描述

有关基本信息,请参阅usleep_range_state()

睡眠任务在睡眠期间处于 TASK_IDLE 状态,以防止对负载平均值产生影响。

void usleep_range_state(unsigned long min, unsigned long max, unsigned int state)

在给定状态下睡眠一段时间

参数

unsigned long min

要睡眠的最小时间(以微秒为单位)

unsigned long max

要睡眠的最大时间(以微秒为单位)

unsigned int state

当前任务在睡眠期间的状态

描述

usleep_range_state() 函数至少休眠指定的最小时间,但不超过指定的最大时间。该范围可以通过允许 hrtimers 将已调度的中断与此 hrtimer 合并,从而降低功耗。在最坏的情况下,会为上限调度中断。

在开始休眠之前,休眠任务被设置为指定的状态。

在非原子上下文中,当确切的唤醒时间是灵活的时候,请使用 usleep_range() 或其变体,而不是 udelay()udelay() 使用 CPU 占用高的忙等待,而休眠则可以通过避免忙等待来提高响应速度。

*sleep()

void msleep(unsigned int msecs)

即使在等待队列中断的情况下也能安全休眠

参数

unsigned int msecs

请求的休眠持续时间,以毫秒为单位

描述

msleep() 函数使用基于节拍的超时来计算休眠持续时间。由于定时器轮的设计,最大的额外百分比延迟(松弛)为 12.5%。这仅对那些最终会进入定时器轮的第 1 级或更高级别的定时器有效。有关这 12.5% 的解释,请查看有关定时器轮基本原理的详细描述。

最终进入第 0 级的定时器的松弛时间取决于休眠持续时间 (msecs) 和 HZ 配置,可以用以下方式计算(定时器轮设计限制是松弛不小于 12.5%)

松弛 = 每个节拍的毫秒数 / 毫秒数

当调用点的允许松弛已知时,可以反过来计算出满足约束的最小允许休眠持续时间。例如

  • HZ=1000松弛=25%每个节拍的毫秒数 / 松弛 = 1 / (1/4) = 4:所有大于或等于 4 毫秒的休眠持续时间都将满足约束。

  • HZ=1000松弛=12.5%每个节拍的毫秒数 / 松弛 = 1 / (1/8) = 8:所有大于或等于 8 毫秒的休眠持续时间都将满足约束。

  • HZ=250松弛=25%每个节拍的毫秒数 / 松弛 = 4 / (1/4) = 16:所有大于或等于 16 毫秒的休眠持续时间都将满足约束。

  • HZ=250松弛=12.5%每个节拍的毫秒数 / 松弛 = 4 / (1/8) = 32:所有大于或等于 32 毫秒的休眠持续时间都将满足约束。

另请参阅信号感知变体 msleep_interruptible()

unsigned long msleep_interruptible(unsigned int msecs)

休眠等待信号

参数

unsigned int msecs

请求的休眠持续时间,以毫秒为单位

描述

有关一些基本信息,请参阅 msleep()

msleep()msleep_interruptible() 之间的区别在于,休眠可能会被信号传递中断,然后提前返回。

返回值

休眠持续时间的剩余时间,转换为毫秒(有关详细信息,请参阅 schedule_timeout())。

void ssleep(unsigned int seconds)

围绕 msleep 的秒包装器

参数

unsigned int seconds

请求的休眠持续时间,以秒为单位

描述

有关详细信息,请参阅 msleep()

void fsleep(unsigned long usecs)

灵活的休眠,自动选择最佳机制

参数

unsigned long usecs

请求的休眠持续时间,以微秒为单位

描述

flseep() 选择最佳机制,它将为请求的休眠持续时间提供最大 25% 的松弛。因此它使用

  • udelay() 循环用于 <= 10 微秒的休眠持续时间,以避免真正短的休眠持续时间产生 hrtimer 开销。

  • usleep_range() 用于使用 msleep() 会导致大于 25% 的松弛的休眠持续时间。这取决于节拍的粒度。

  • msleep() 用于所有其他休眠持续时间。

注意

当未设置 CONFIG_HIGH_RES_TIMERS 时,所有休眠都将以节拍的粒度进行处理,并且松弛可能会超过 25%,特别是对于短的休眠持续时间。