延迟和睡眠机制

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

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

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

*delay()

usleep_range*()

*sleep()

fsleep()

忙等待循环

基于 hrtimers

基于定时器列表定时器

结合其他函数

在原子上下文中使用

在“短时间间隔”上精确

取决于

在“长时间间隔”上精确

不要使用!

最大 12.5% 的延迟

可中断变体

对于非原子上下文,一个通用的建议是

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

  2. 尽可能使用*sleep()

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

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

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

*delay()函数族

这些函数使用时钟速度的 jiffy 估计值,并将忙等待足够的循环次数以达到所需的延迟。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-tree(而不是哈希)

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

  3. 定时器列表定时器总是提供某种延迟,因为它们是基于 jiffy 的

这里重复通用建议

  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()。睡眠通过避免 CPU 密集型的udelay()忙等待来提高响应能力。

*sleep()

void msleep(unsigned int msecs)

即使有等待队列中断,也能安全睡眠

参数

unsigned int msecs

请求的睡眠持续时间(以毫秒为单位)

描述

msleep()对睡眠持续时间使用基于 jiffy 的超时。由于定时器轮的设计,最大附加百分比延迟(延迟)为 12.5%。这仅对最终出现在定时器轮的级别 1 或更高级别的定时器有效。有关这 12.5% 的解释,请查看有关定时器轮基础知识的详细描述。

最终出现在级别 0 中的定时器的延迟取决于睡眠持续时间(毫秒)和 HZ 配置,并且可以通过以下方式计算(使用定时器轮设计限制,即延迟不小于 12.5%)

slack = MSECS_PER_TICK / msecs

当知道调用点的允许延迟时,可以反过来计算以找到满足约束的最小允许睡眠持续时间。例如

  • HZ=1000 with slack=25%: MSECS_PER_TICK / slack = 1 / (1/4) = 4: all sleep durations greater or equal 4ms will meet the constraints.

  • HZ=1000 with slack=12.5%: MSECS_PER_TICK / slack = 1 / (1/8) = 8: all sleep durations greater or equal 8ms will meet the constraints.

  • HZ=250 with slack=25%: MSECS_PER_TICK / slack = 4 / (1/4) = 16: all sleep durations greater or equal 16ms will meet the constraints.

  • HZ=250 with slack=12.5%: MSECS_PER_TICK / slack = 4 / (1/8) = 32: all sleep durations greater or equal 32ms will meet the constraints.

另请参阅信号感知变体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% 的延迟。这取决于 jiffies 的粒度。

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

注意

当未设置CONFIG_HIGH_RES_TIMERS时,所有睡眠都以 jiffies 的粒度处理,并且延迟可能超过 25%,特别是对于短的睡眠持续时间。