延遲和睡眠機制

本文件旨在回答一個常見問題:“插入延遲的正確方法 (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%,特別是對於短的睡眠持續時間。