Linux- 關於timer定時器, delay延遲, sleep睡眠, 與中斷….等等

定時器(timer)

(參考)定時器(不懂的話,可以暫時想成個鬧鐘)分為硬件和軟件定時器,軟件定時器最終還是要依靠硬件定時器來完成。
內核在時鐘中斷發生後檢測各定時器是否到期,到期後的定時器處理函數將作為軟中斷在底半部執行。
實質上,時鐘中斷處理程序執行update_process_timers函數,該函數調用run_local_timers函數,這個函數處理TIMER_SOFTIRQ軟中斷,運行當前處理上到期的所有定時器。

Linux內核中定義提供了一些用於操作定時器的數據結構和函數如下:
1)timer_list:說定時器,當然要來個定時器的結構體

2)初始化定時器:
經過這個初始化後,entry的next為NULL,並給base賦值

3)增加定時器:
該函數用於註冊內核定時器,並將定時器加入到內核動態定時器鍊錶中。

4)刪除定時器:

說明:del_timer_sync是del_timer的同步版,主要在多處理器系統中使用,如果編譯內核時不支持SMP,del_timer_sync和del_timer等價.

5)修改定時器:

ex: 下邊是一個使用定​​時器的模版:

關於延遲與睡眠

在內核定時器中,常常少不了要說下內核延遲的事,請接著往下看:
1)短延遲:
在linux內核中提供了三個函數來分別實現納秒,微秒,毫秒延遲,原理上是忙等待,它根據CPU頻率進行一定次數的循環

毫秒延遲已經相當大了,當然更秒延遲當然要小一些,在內核中,為了性能,最好不要用mdelay,這會耗費大量cpu資源

這三個是內核專門提供該我們用來處理毫秒以上的延遲。
上述函數將使得調用它的進程睡眠參數指定的秒數,其中第二個是可以被打斷的,其餘的兩個是不可以的。

2)長延遲:
內核中進行延遲最常用的方法就是比較當前的jiffies和目標jiffies(當前的加上時間間隔的jiffies),直到未來的jiffies達到目標jiffies。
比如:

與time_before對應的還有一個time_after().
其實就是#define time_before(a,b) time_after(b,a);

另外兩個是time_after_eq(a,b)和time_before_eq(a,b)

3)睡著延遲:
這顯然是比忙等待好的方法,因為在未到來之前,進程會處於睡眠狀態,
把CPU空出來,讓CPU可以做別的事情,等時間到了,調用schedule_timeout()就可以喚醒它並重新調度執行。
msleep和msleep_interruptible本質上都是依靠包含了schedule_timeout的schedule_timeout_uninterruptible()和schedule_timeout_interruptible()實現。
就像下邊這樣:

另外還有如下:

這兩個將當前進程添加到等待隊列,從而在等待隊列上睡眠,當超時發生時,進程將被喚醒。


關於udelay & mdelay, 就是busy waiting方式

driver常常會需要很短且精準的delay(n microsecond/millisecond),以完成sync。
此時用jiffies就不恰當,第一單位不夠小,如果timer是100Hz,表示一個tick是10 millisecond。
第二不夠準,因為透過scheduler。在kernel裡有兩個function來完成很小的delay,不使用jiffies:

在<linux/delay.h>

以上兩個都是busy waiting。
udelay的實作要先談BogoMIPS,這個值是指在特定時間內CPU可執行多少個busy loop operation。
也就是說,這個CPU不做任何事可以多快(久) (how fast a processor can do nothing)。
這個值在kernel裡是一個叫做loops_per_jiffy的變數,
在userspace則可在/proc/cpuinfo裡找到。

而kernel是在init/main.c裡透過calibrate_delay()這個function來計算。
值得一提的是這個值是跟performance沒太大關係的。
這個BogoMIPS主要就是用來實作udelay。
udelay去算需要多少個loop operation來得到精確的delay。

使用udelay要小心overflow,它適合很短的delay,通常不超過1 millisecond。
長一點的delay就用mdelay。
要注意的是udelay和mdelay會影響performance,因為CPU無法做任何其他事,所以通常是用來delay以microsecond為單位比較適合。

關於schedule_timeout(), 就是會進入sleep state

另一種delay是schedule_timeout()。與上述不同的是這方法會進入sleep state,直到指定的時間結束,而不是busy waiting。
另一個是不那麼精確,因為睡完是進入run queue。
使用方式是:

可看出這是使用jiffies。上面的code是將這個process在interruptible的情況下睡s秒。
state必須要在TASK_INTERRUPTIBLE或是TASK_UNINTERRUPTIBLE執行schedule_timeout才會進入sleep。

其他範例如下:

首先看到怎麼透過sleep 作延遲
其中第一個判斷式 in_interrupt() 定義為
>> #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK)) // 判斷當前是否在硬件、軟件、底半部中斷上下文 (下方補充1)

msleep_interruptible() 延遲的單位為毫秒(10^-3)
且在於等待過程可能被中斷,中斷指的是 process 可以接收 signal (下方補充2)

接著看這個小API
它實作與msleep()很像, 只是差別在使用 usecs_to_jiffies()
而msleep()是使用 msecs_to_jiffies()

最後一個例子,
則是透過udelay()實作busy wait

Reference

Delaying Execution
補充1: in_irq() in_softirq() in_interrupt() 函數區別

補充2: 不忙碌的等待
如果在等待過程中,希望 CPU 去做其它事的話,可以用定義在「linux/delay.h」中 sleep 系列的等待函式:

延遲的單位分別為秒、毫秒(10^-3)、毫秒(10^-3)。
msleep_interruptible() 與 msleep() 不同的地方在於等待過程可能被中斷,中斷指的是 process 可以接收 signal。
如果在 msleep_interruptible() 中收到 signal 而中斷等待的話,則會回傳距離原始時限的時間(正值),否則的話傳回「0」。

在〈Linux- 關於timer定時器, delay延遲, sleep睡眠, 與中斷….等等〉中有 1 則留言

  1. 自動引用通知: Linux kernel中timer的使用方法 - 易春木

發表迴響