內核代碼(尤其是驅動程序)除了使用定時器或下半部機制以外還需要其他方法來推遲執行任務。這種推遲通常發生在等待硬件完成某些工作時,而且等待時間非常短。
內核提供了許多延遲方法處理各種延遲要求。不同的方法有不同的處理特點,有些是在延遲任務時掛起處理器,防止處理器執行任何實際工作;另一些不會掛起處理器,所以也不能確保被延遲的代碼能夠在指定的延遲時間運行。
忙等待
最簡單的延遲方法(雖然也是最不理想的方法)是忙等帶。該方法僅僅在想要延遲的時間是節拍的整數倍,或者精確率要求不太高時才可以使用。
忙循環實現起來很簡單,在循環中不斷旋轉直到希望的時鐘節拍數耗盡,比如:
unsigned long delay = jiffies + 10;
while(time_before(jiffies,delaly))
;
更好的方法應該是在代碼等待時,運行內核重新調度執行其他任務:
unsigned long delay = jiffies + 2 * HZ;
while(time_before(jiffies,delay))
cond_resched();
在中
int __sched cond_resched(void)
{
if (need_resched() && !(preempt_count() & PREEMPT_ACTIVE) &
system_state == SYSTEM_RUNNING) {
__cond_resched();
return 1;
}
return 0;
}
static void __cond_resched(void)
{
#ifdef CONFIG_DEBUG_SPINLOCK_SLEEP
__might_sleep(__FILE__, __LINE__);
#endif
/*
* The BKS might be reacquired before we have dropped
* PREEMPT_ACTIVE, which could trigger a second
* cond_resched() call.
*/
do {
add_preempt_count(PREEMPT_ACTIVE);
schedule();
sub_preempt_count(PREEMPT_ACTIVE);
} while (need_resched());
}
cond_resched()函數將調度一個新程序投入運行,但是它只有在設置完need_resched標志后,才能生效。也就是說,該方法有效的條件是系統中存在更重要的任務需要執行。注意,因為該方法需要調用調度程序,所以它不能在中斷上下文中使用,只能在進程上下文中使用。實際上,所有延遲方法在進程上下文中使用,因為中斷處理程序都應該盡可能快地執行。另外,延遲執行不管在哪種情況下都不應該在持有鎖時或禁止中斷時發生。
C編譯器通常只將變量裝載一次,一般情況下不能保證循環中的jiffies變量在每次循環中被讀取時都重新被載入。但是要求jiffies在每次循環時都必須重新裝載,因為在后臺jiffies值會隨時鐘中斷的發生而不斷增加。為了解決這個問題,jiffies變量被標記為volatile。
關鍵字volatile指示編譯器在每次訪問變量時都重新從主內存中獲得,而不是通過寄存器中變量的別名來訪問,從而確保前面的循環能夠按預期的方式執行。
短延遲
有時候,內核代碼(通常也是驅動程序)不但需要很短暫的延遲(比時鐘節拍還短)而且還要求延遲的時間很精確。這種情況多發生在和硬件同步時,也就是說需要短暫等待某個動作的完成,等待時間往往小于1毫秒,所以不能使用像前面例子中那種基于jiffies的延遲方法。
內核提供了兩個可以處理為妙和毫秒級別的延遲的函數udelay()和mdelay()。前一個函數利用忙循環來將任務延遲到指定的微妙數后運行,后者延遲指定的毫秒數:
在中
/* 0x10c7 is 2**32 / 1000000 (rounded up) */
#define udelay(n) (__builtin_constant_p(n) ? /
((n) 》 20000 ? __bad_udelay() : __const_udelay((n) * 0x10c7ul)) : /
__udelay(n))
/* 0x5 is 2**32 / 1000000000 (rounded up) */
#define ndelay(n) (__builtin_constant_p(n) ? /
((n) 》 20000 ? __bad_ndelay() : __const_udelay((n) * 5ul)) : /
__ndelay(n))
在中
/*
* Copyright (C) 1993 Linus Torvalds
*
* Delay routines, using a pre-computed “loops_per_jiffy” value.
*/
extern unsigned long loops_per_jiffy;
#include
/*
* Using udelay() for intervals greater than a few milliseconds can
* risk overflow for high loops_per_jiffy (high bogomips) machines. The
* mdelay() provides a wrapper to prevent this. For delays greater
* than MAX_UDELAY_MS milliseconds, the wrapper is used. Architecture
* specific values can be defined in asm-???/delay.h as an override.
* The 2nd mdelay() definition ensures GCC will optimize away the
* while loop for the common cases where n 《= MAX_UDELAY_MS -- Paul G.
*/
#ifndef MAX_UDELAY_MS
#define MAX_UDELAY_MS 5
#endif
#ifndef mdelay
#define mdelay(n) (/
(__builtin_constant_p(n) && (n)《=MAX_UDELAY_MS) ? udelay((n)*1000) : /
({unsigned long __ms=(n); while (__ms--) udelay(1000);}))
#endif
#ifndef ndelay
#define ndelay(x) udelay(((x)+999)/1000)
#endif
udelay()函數依靠執行數次達到延遲效果,而mdelay()函數通過udelay()函數實現。因為內核知道處理器在一秒內能執行多少次循環,所以udelay()函數緊急需要根據指定的延遲時間在1秒中占的比例就能決定需要進行多少次循環就能達到要求的推遲時間。
不要使用udelay()函數處理超過1毫秒的延遲,延遲超過1毫秒使用mdelay()更安全。
千萬要注意不要在持有鎖時或禁止中斷時使用忙等待,因為這時忙等待會時系統響應速度和性能大打折扣。
內核如何知道處理器在一秒內能執行多少循環呢?
BogoMIPS值記錄的是處理器在給定時間內忙循環執行的次數。其實就是記錄處理器在空閑時速度有多快。它主要被udelay()和mdelay()函數使用。該值存放在變量loops_per_jiffies中,可以從文件/proc/cpuinfo中讀到它。延遲循環函數使用loops_per_jiffies值來計算為提供精確延遲而需要進行多少次循環。內核在啟動時利用函數calibrate_delay()函數計算loops_per_jiffies值,該函數在文件init/main.c中使用到。
在中
/*
* This should be approx 2 Bo*oMips to start (note initial shift), and will
* still work even if initially too large, it will just take slightly longer
*/
unsigned long loops_per_jiffy = (1《《12);
在中
void __devinit calibrate_delay(void)
{
unsigned long ticks, loopbit;
int lps_precision = LPS_PREC;
if (preset_lpj) {
loops_per_jiffy = preset_lpj;
printk(“Calibrating delay loop (skipped)。.. ”
“%lu.%02lu BogoMIPS preset/n”,
loops_per_jiffy/(500000/HZ),
(loops_per_jiffy/(5000/HZ)) % 100);
} else if ((loops_per_jiffy = calibrate_delay_direct()) != 0) {
printk(“Calibrating delay using timer specific routine.。 ”);
printk(“%lu.%02lu BogoMIPS (lpj=%lu)/n”,
loops_per_jiffy/(500000/HZ),
(loops_per_jiffy/(5000/HZ)) % 100,
loops_per_jiffy);
} else {
loops_per_jiffy = (1《《12);
printk(KERN_DEBUG “Calibrating delay loop.。. ”);
while ((loops_per_jiffy 《《= 1) != 0) {
/* wait for “start of” clock tick */
ticks = jiffies;
while (ticks == jiffies)
/* nothing */;
/* Go 。. */
ticks = jiffies;
__delay(loops_per_jiffy);
ticks = jiffies - ticks;
if (ticks)
break;
}
/*
* Do a binary approximation to get loops_per_jiffy set to
* equal one clock (up to lps_precision bits)
*/
loops_per_jiffy 》》= 1;
loopbit = loops_per_jiffy;
while (lps_precision-- && (loopbit 》》= 1)) {
loops_per_jiffy |= loopbit;
ticks = jiffies;
while (ticks == jiffies)
/* nothing */;
ticks = jiffies;
__delay(loops_per_jiffy);
if (jiffies != ticks) /* longer than 1 tick */
loops_per_jiffy &= ~loopbit;
}
/* Round the value and print it */
printk(“%lu.%02lu BogoMIPS (lpj=%lu)/n”,
loops_per_jiffy/(500000/HZ),
(loops_per_jiffy/(5000/HZ)) % 100,
loops_per_jiffy);
}
}
schedule_timeout()
更理想的延遲方法時使用schedule_timeout()函數。該方法會讓需要延遲執行的任務睡眠到指定的延遲時間耗盡后再重新運行。但該方法也不能保證睡眠時間正好等于指定的延遲時間,只能盡量使睡眠時間接近指定的延遲時間。當指定時間到期后,內核喚醒被延遲的任務并將其重新放回運行隊列:
set_current_state(TASK_INTERRUPTABLE);
schedule_timeout(s*HZ);//參數是延遲的相對時間,單位為jiffies
注意,在調用schedule_timeout()函數前,必須將任務狀態設置成TASK_INTERRUPTABLE和TASK_UNINTERRUPTABLE狀態之一,否則任務不會睡眠。
注意,由于schedule_timeout()函數需要調用調度程序,所以調用它的代碼必須保證能夠睡眠。調用代碼必須處于進程上下文中,并且不能持有鎖。
在(Timer.c(kernel))中
/**
* schedule_timeout - sleep until timeout
* @timeout: timeout value in jiffies
*
* Make the current task sleep until @timeout jiffies have
* elapsed. The routine will return immediately unless
* the current task state has been set (see set_current_state())。
*
* You can set the task state as follows -
*
* %TASK_UNINTERRUPTIBLE - at least @timeout jiffies are guaranteed to
* pass before the routine returns. The routine will return 0
*
* %TASK_INTERRUPTIBLE - the routine may return early if a signal is
* delivered to the current task. In this case the remaining time
* in jiffies will be returned, or 0 if the timer expired in time
*
* The current task state is guaranteed to be TASK_RUNNING when this
* routine returns.
*
* Specifying a @timeout value of %MAX_SCHEDULE_TIMEOUT will schedule
* the CPU away without a bound on the timeout. In this case the return
* value will be %MAX_SCHEDULE_TIMEOUT.
*
* In all cases the return value is guaranteed to be non-negative.
*/
fastcall signed long __sched schedule_timeout(signed long timeout)
{
struct timer_list timer;
unsigned long expire;
switch (timeout)
{
case MAX_SCHEDULE_TIMEOUT: //用于檢查任務是否無限期的睡眠,如果這樣的話,函數不會為它設置定時器
/*
* These two special cases are useful to be comfortable
* in the caller. Nothing more. We could take
* MAX_SCHEDULE_TIMEOUT from one of the negative value
* but I‘ d like to return a valid offset (》=0) to allow
* the caller to do everything it want with the retval.
*/
schedule();
goto out;
default:
/*
* Another bit of PARANOID. Note that the retval will be
* 0 since no piece of kernel is supposed to do a check
* for a negative retval of schedule_timeout() (since it
* should never happens anyway)。 You just have the printk()
* that will tell you if something is gone wrong and where.
*/
if (timeout 《 0) {
printk(KERN_ERR “schedule_timeout: wrong timeout ”
“value %lx/n”, timeout);
dump_stack();
current-》state = TASK_RUNNING;
goto out;
}
}
expire = timeout + jiffies;
setup_timer(&timer, process_timeout, (unsigned long)current); /*創建定時器,設置超時時間,設置超時函數 */
__mod_timer(&timer, expire); /*激活定時器 */
schedule(); /*選擇其他任務運行 */
del_singleshot_timer_sync(&timer); /*銷毀定時器*/
timeout = expire - jiffies;
out:
return timeout 《 0 ? 0 : timeout;
}
在中
static inline void setup_timer(struct timer_list * timer,
void (*function)(unsigned long),
unsigned long data)
{
timer-》function = function;
timer-》data = data;
init_timer(timer);
}
在中
//當定時器超時,該函數被調用
static void process_timeout(unsigned long __data)
{
wake_up_process((struct task_struct *)__data);
}
schedule_timeout()函數是內核定時器的一個簡單應用。
設置超時時間,在等待隊列上睡眠
進程上下文中的代碼為了等待特定事件發生,可以將自己放入等待隊列,然后調用調度程序去執行新任務。一旦事件發生后,內核調用wake_up()函數喚醒在睡眠隊列上的任務使其重新投入運行。
有時,等待隊列上的某個任務可能既在等待一個特定的事件到來,又在等待一個特定的時間到期,在這種情況下,代碼可以簡單地使用schedule_timeout()函數代替schedule()函數,當希望指定時間到期,任務就會被喚醒。代碼需要檢查被喚醒的原因,可能是被事件喚醒,可能是因為延遲時間到期,也可能是因為接收到了信號;然后執行相應的操作。
評論
查看更多