定時器(timer)
(參考)定時器(不懂的話,可以暫時想成個鬧鐘)分為硬件和軟件定時器,軟件定時器最終還是要依靠硬件定時器來完成。
內核在時鐘中斷發生後檢測各定時器是否到期,到期後的定時器處理函數將作為軟中斷在底半部執行。
實質上,時鐘中斷處理程序執行update_process_timers函數,該函數調用run_local_timers函數,這個函數處理TIMER_SOFTIRQ軟中斷,運行當前處理上到期的所有定時器。
Linux內核中定義提供了一些用於操作定時器的數據結構和函數如下:
1)timer_list:說定時器,當然要來個定時器的結構體
1 2 3 4 5 6 7 |
struct timer_list{ struct list_head entry; //定時器列表 unsigned long expires; //定時器到期時間 void (*function)(unsigned long) ;//定時器處理函數 unsigned long data; //作為參數被傳入定時器處理函數 struct timer_base_s *base; } |
2)初始化定時器:
經過這個初始化後,entry的next為NULL,並給base賦值
1 |
void init_timer(struct timer_list *timer); |
3)增加定時器:
該函數用於註冊內核定時器,並將定時器加入到內核動態定時器鍊錶中。
1 |
void add_timer(struct timer_list *timer); |
4)刪除定時器:
1 |
int del_timer(struct timer_list *timer); |
說明:del_timer_sync是del_timer的同步版,主要在多處理器系統中使用,如果編譯內核時不支持SMP,del_timer_sync和del_timer等價.
5)修改定時器:
1 |
int mod_timer(struct timer_list *timer, unsigned long expires); |
ex: 下邊是一個使用定時器的模版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
struct xxx_dev /*second設備結構體*/ { struct cdev cdev; /*cdev結構體*/ ... struct timer_list xxx_timer; /*設備要使用的定時器*/ }; int xxx_func1(...) //xxx驅動中某函數 { struct xxx_dev *dev = filp->private_data; ... /*初始化定時器*/ init_timer(&dev->xxx_timer); dev->xxx_timer.function = &xxx_do_handle; dev->xxx_timer.data = (unsigned long)dev; dev->xxx_timer.expires = jiffies + delay; add_timer(&dev->xxx_timer); /*添加(註冊)定時器*/ ... return 0; } int xxx_func2(...) //驅動中某函數 { ... del_timer(&second_devp->s_timer); ... } static void xxx_do_timer(unsigned long arg) //定時器處理函數 { struct xxx_device *dev = (struct xxx_device *)(arg); ... //調度定時器再執行 dev->xxx_timer.expires = jiffies + delay; add_timer(&dev->xxx_timer); } 在定時器函數中往往會在做完具體工作後,延遲expires並將定時器再次添加到內核定時器鍊錶中,以便定時器能被再次觸發 |
關於延遲與睡眠
在內核定時器中,常常少不了要說下內核延遲的事,請接著往下看:
1)短延遲:
在linux內核中提供了三個函數來分別實現納秒,微秒,毫秒延遲,原理上是忙等待,它根據CPU頻率進行一定次數的循環
1 2 3 |
void ndelay(unsigned long nsecs); void udelay(unsigned long usecs); void mdelay(unsigned long msecs); |
毫秒延遲已經相當大了,當然更秒延遲當然要小一些,在內核中,為了性能,最好不要用mdelay,這會耗費大量cpu資源
1 2 3 |
void msleep(unsigned int millisecs); unsigned long msleep_interruptible(unsigned int millisecs); void ssleep(unsigned int seconds); |
這三個是內核專門提供該我們用來處理毫秒以上的延遲。
上述函數將使得調用它的進程睡眠參數指定的秒數,其中第二個是可以被打斷的,其餘的兩個是不可以的。
2)長延遲:
內核中進行延遲最常用的方法就是比較當前的jiffies和目標jiffies(當前的加上時間間隔的jiffies),直到未來的jiffies達到目標jiffies。
比如:
1 2 |
unsigned long delay = jiffies + 100; //延遲100個jiffies while(time_before(jiffies, delay)); |
與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()實現。
就像下邊這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
void msleep(unsigned int msecs) { unsigned long timeout = msecs_to_jiffies(msecs) + 1; while(timeout) timeout = schedule_timeout_uninterruptible(timeout); } unsigned long msleep_interruptible(unsigned int msecs) { unsigned long timeout = msecs_to_jiffies(msecs) + 1; while(timeout && !signal_pending(current)) timeout = schedule_timeout_interruptible(timeout); return jiffies_to_msecs(timeout); } signed long __sched schedule_timeout_interruptible()signed long timeout) { __set_current_state(TASK_INTERRUPTIBLE); return schedule_timeout(timeout); } signed long __sched schedule_timeout_uninterruptible()signed long timeout) { __set_current_state(TASK_UNINTERRUPTIBLE); return schedule_timeout(timeout); } |
另外還有如下:
1 2 |
time_on_timeout(wait_queue_head_t *q, unsigned long timeout); interruptible_sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout); |
這兩個將當前進程添加到等待隊列,從而在等待隊列上睡眠,當超時發生時,進程將被喚醒。
關於udelay & mdelay, 就是busy waiting方式
driver常常會需要很短且精準的delay(n microsecond/millisecond),以完成sync。
此時用jiffies就不恰當,第一單位不夠小,如果timer是100Hz,表示一個tick是10 millisecond。
第二不夠準,因為透過scheduler。在kernel裡有兩個function來完成很小的delay,不使用jiffies:
在<linux/delay.h>
1 2 |
void udelay(unsigned long usecs) void mdelay(unsigned long msecs) |
以上兩個都是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。
使用方式是:
1 2 3 4 5 |
/* set task's state to interruptible sleep */ set_current_state(TASK_INTERRUPTIBLE); /* take a nap and wake up in "s" seconds */ schedule_timeout(s * HZ); |
可看出這是使用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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<strong>xxx_sleep() - sleep</strong> The function suspends the execution of the current thread until the specified time out interval elapses. msInterval - the number of milliseconds to suspend the current thread. A value of 0 may or may not cause the current thread to yield. void xxx_sleep( v_U32_t msInterval ) { if (in_interrupt()) { PRINT_TRACE(XXX_MODULE_ID_XXX, XXX_TRACE_LEVEL_ERROR, "%s cannot be called from interrupt context!!!", __func__); return; } <strong> msleep_interruptible(msInterval);</strong> } |
接著看這個小API
它實作與msleep()很像, 只是差別在使用 usecs_to_jiffies()
而msleep()是使用 msecs_to_jiffies()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<strong> xx_sleep_us() - sleep</strong> The function suspends the execution of the current thread until the specified time out interval elapses. usInterval - the number of microseconds to suspend the current thread. A value of 0 may or may not cause the current thread to yield. --------------------------------------------------------------------------*/ void xxx_sleep_us( v_U32_t usInterval ) { unsigned long timeout = usecs_to_jiffies(usInterval) + 1; if (in_interrupt()) { PRINT_TRACE(XXX_MODULE_ID_XXX, XXX_TRACE_LEVEL_ERROR, "%s cannot be called from interrupt context!!!", __func__); return; } <strong> while (timeout && !signal_pending(current)) timeout = schedule_timeout_interruptible(timeout);</strong> } |
最後一個例子,
則是透過udelay()實作busy wait
1 2 3 4 5 6 7 8 9 10 11 |
<strong>xxx_busy_wait() - busy wait</strong> The function places the current thread in busy wait until the specified time out interval elapses. If the interval is greater than 50us on WM, the behaviour is undefined. para: usInterval - the number of microseconds to busy wait. void xxx_busy_wait( v_U32_t usInterval ) { <strong>udelay(usInterval);</strong> } |
Reference
Delaying Execution
補充1: in_irq() in_softirq() in_interrupt() 函數區別
1 2 3 4 5 6 7 8 9 10 |
#define hardirq_count() (preempt_count() & HARDIRQ_MASK) #define softirq_count() (preempt_count() & SOFTIRQ_MASK) #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK)) /* * Are we doing bottom half or hardware interrupt processing? * Are we in a softirq context? Interrupt context? */ #define in_irq() (hardirq_count()) //判断当前是否在硬件中断上下文 #define in_softirq() (softirq_count()) //判断当前是否在软件中断上下文 #define in_interrupt() (irq_count()) //判断当前是否在硬件、软件、底半部中断上下文 |
補充2: 不忙碌的等待
如果在等待過程中,希望 CPU 去做其它事的話,可以用定義在「linux/delay.h」中 sleep 系列的等待函式:
1 2 3 |
void ssleep(unsigned int seconds); void msleep(unsigned int msecs); unsigned long msleep_interruptible(unsigned int msecs); |
延遲的單位分別為秒、毫秒(10^-3)、毫秒(10^-3)。
msleep_interruptible() 與 msleep() 不同的地方在於等待過程可能被中斷,中斷指的是 process 可以接收 signal。
如果在 msleep_interruptible() 中收到 signal 而中斷等待的話,則會回傳距離原始時限的時間(正值),否則的話傳回「0」。
自動引用通知: Linux kernel中timer的使用方法 - 易春木