延迟和睡眠机制¶
本文档旨在回答一个常见问题:“插入延迟的正确方法(TM)是什么?”
驱动程序编写者最常遇到这个问题,他们必须处理硬件延迟,并且可能不是最熟悉 Linux 内核内部工作原理的人。
下表给出了现有函数“族”及其局限性的粗略概述。此概述表不能替代使用前阅读函数描述!
*delay() |
usleep_range*() |
*sleep() |
||
---|---|---|---|---|
忙等待循环 |
基于 hrtimers |
基于定时器列表定时器 |
结合其他方法 |
|
在原子上下文中使用 |
是 |
否 |
否 |
否 |
在“短间隔”内精确 |
是 |
是 |
取决于 |
是 |
在“长间隔”内精确 |
不要使用! |
是 |
最大 12.5% 的松弛 |
是 |
可中断的变体 |
否 |
是 |
是 |
否 |
对于非原子上下文的一般建议可以是
如果不确定,请使用
fsleep()
(因为它结合了其他方法的优点)尽可能使用*sleep()
如果*sleep()的精度不足,请使用usleep_range*()
对于非常非常短的延迟,请使用*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)
计算的 loops_per_jiffy 过低(由于执行定时器中断所花费的时间。)
缓存行为会影响执行循环函数所需的时间。
CPU 时钟频率变化。
-
void ndelay(unsigned long nsec)¶
插入基于纳秒的延迟,并忙等待
-
mdelay¶
mdelay (n)
插入基于毫秒的延迟,并忙等待
usleep_range*()和*sleep()函数族¶
这些函数使用 hrtimers 或定时器列表定时器来提供请求的睡眠持续时间。为了确定哪个函数是正确的使用函数,请考虑一些基本信息
hrtimers 更昂贵,因为它们使用 rb 树(而不是哈希)
当请求的睡眠持续时间是第一个定时器时,hrtimers 更昂贵,这意味着必须对真正的硬件进行编程
定时器列表定时器总是提供某种松弛,因为它们是基于节拍的
此处重复通用建议
如果不确定,请使用
fsleep()
(因为它结合了其他方法的优点)尽可能使用*sleep()
如果*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 的秒包装器
-
void fsleep(unsigned long usecs)¶
灵活的休眠,自动选择最佳机制
参数
unsigned long usecs
请求的休眠持续时间,以微秒为单位
描述
flseep() 选择最佳机制,它将为请求的休眠持续时间提供最大 25% 的松弛。因此它使用
udelay()
循环用于 <= 10 微秒的休眠持续时间,以避免真正短的休眠持续时间产生 hrtimer 开销。usleep_range()
用于使用msleep()
会导致大于 25% 的松弛的休眠持续时间。这取决于节拍的粒度。msleep()
用于所有其他休眠持续时间。
注意
当未设置 CONFIG_HIGH_RES_TIMERS
时,所有休眠都将以节拍的粒度进行处理,并且松弛可能会超过 25%,特别是对于短的休眠持续时间。