內(nèi)核代碼(尤其是驅(qū)動程序)除了使用定時(shí)器或下半部機(jī)制以外還需要其他方法來推遲執(zhí)行任務(wù)。這種推遲通常發(fā)生在等待硬件完成某些工作時(shí),而且等待時(shí)間非常短。
內(nèi)核提供了許多延遲方法處理各種延遲要求。不同的方法有不同的處理特點(diǎn),有些是在延遲任務(wù)時(shí)掛起處理器,防止處理器執(zhí)行任何實(shí)際工作;另一些不會掛起處理器,所以也不能確保被延遲的代碼能夠在指定的延遲時(shí)間運(yùn)行。
忙等待
最簡單的延遲方法(雖然也是最不理想的方法)是忙等帶。該方法僅僅在想要延遲的時(shí)間是節(jié)拍的整數(shù)倍,或者精確率要求不太高時(shí)才可以使用。
忙循環(huán)實(shí)現(xiàn)起來很簡單,在循環(huán)中不斷旋轉(zhuǎn)直到希望的時(shí)鐘節(jié)拍數(shù)耗盡,比如:
unsigned long delay = jiffies + 10;
while(time_before(jiffies,delaly))
;
更好的方法應(yīng)該是在代碼等待時(shí),運(yùn)行內(nèi)核重新調(diào)度執(zhí)行其他任務(wù):
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()函數(shù)將調(diào)度一個(gè)新程序投入運(yùn)行,但是它只有在設(shè)置完need_resched標(biāo)志后,才能生效。也就是說,該方法有效的條件是系統(tǒng)中存在更重要的任務(wù)需要執(zhí)行。注意,因?yàn)樵摲椒ㄐ枰{(diào)用調(diào)度程序,所以它不能在中斷上下文中使用,只能在進(jìn)程上下文中使用。實(shí)際上,所有延遲方法在進(jìn)程上下文中使用,因?yàn)橹袛嗵幚沓绦蚨紤?yīng)該盡可能快地執(zhí)行。另外,延遲執(zhí)行不管在哪種情況下都不應(yīng)該在持有鎖時(shí)或禁止中斷時(shí)發(fā)生。
C編譯器通常只將變量裝載一次,一般情況下不能保證循環(huán)中的jiffies變量在每次循環(huán)中被讀取時(shí)都重新被載入。但是要求jiffies在每次循環(huán)時(shí)都必須重新裝載,因?yàn)樵诤笈_jiffies值會隨時(shí)鐘中斷的發(fā)生而不斷增加。為了解決這個(gè)問題,jiffies變量被標(biāo)記為volatile。
關(guān)鍵字volatile指示編譯器在每次訪問變量時(shí)都重新從主內(nèi)存中獲得,而不是通過寄存器中變量的別名來訪問,從而確保前面的循環(huán)能夠按預(yù)期的方式執(zhí)行。
短延遲
有時(shí)候,內(nèi)核代碼(通常也是驅(qū)動程序)不但需要很短暫的延遲(比時(shí)鐘節(jié)拍還短)而且還要求延遲的時(shí)間很精確。這種情況多發(fā)生在和硬件同步時(shí),也就是說需要短暫等待某個(gè)動作的完成,等待時(shí)間往往小于1毫秒,所以不能使用像前面例子中那種基于jiffies的延遲方法。
內(nèi)核提供了兩個(gè)可以處理為妙和毫秒級別的延遲的函數(shù)udelay()和mdelay()。前一個(gè)函數(shù)利用忙循環(huán)來將任務(wù)延遲到指定的微妙數(shù)后運(yùn)行,后者延遲指定的毫秒數(shù):
在中
/* 0x10c7 is 2**32 / 1000000 (rounded up) */
#define udelay(n) (__builtin_constant_p(n) ? /
?。ǎ╪) 》 20000 ? __bad_udelay() : __const_udelay((n) * 0x10c7ul)) : /
__udelay(n))
/* 0x5 is 2**32 / 1000000000 (rounded up) */
#define ndelay(n) (__builtin_constant_p(n) ? /
?。ǎ╪) 》 20000 ? __bad_ndelay() : __const_udelay((n) * 5ul)) : /
__ndelay(n))
在中
/*
* Copyright (C) 1993 Linus Torvalds
*
* Delay routines, using a pre-computed “l(fā)oops_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()函數(shù)依靠執(zhí)行數(shù)次達(dá)到延遲效果,而mdelay()函數(shù)通過udelay()函數(shù)實(shí)現(xiàn)。因?yàn)閮?nèi)核知道處理器在一秒內(nèi)能執(zhí)行多少次循環(huán),所以udelay()函數(shù)緊急需要根據(jù)指定的延遲時(shí)間在1秒中占的比例就能決定需要進(jìn)行多少次循環(huán)就能達(dá)到要求的推遲時(shí)間。
不要使用udelay()函數(shù)處理超過1毫秒的延遲,延遲超過1毫秒使用mdelay()更安全。
千萬要注意不要在持有鎖時(shí)或禁止中斷時(shí)使用忙等待,因?yàn)檫@時(shí)忙等待會時(shí)系統(tǒng)響應(yīng)速度和性能大打折扣。
內(nèi)核如何知道處理器在一秒內(nèi)能執(zhí)行多少循環(huán)呢?
BogoMIPS值記錄的是處理器在給定時(shí)間內(nèi)忙循環(huán)執(zhí)行的次數(shù)。其實(shí)就是記錄處理器在空閑時(shí)速度有多快。它主要被udelay()和mdelay()函數(shù)使用。該值存放在變量loops_per_jiffies中,可以從文件/proc/cpuinfo中讀到它。延遲循環(huán)函數(shù)使用loops_per_jiffies值來計(jì)算為提供精確延遲而需要進(jìn)行多少次循環(huán)。內(nèi)核在啟動時(shí)利用函數(shù)calibrate_delay()函數(shù)計(jì)算loops_per_jiffies值,該函數(shù)在文件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),
?。╨oops_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),
?。╨oops_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()
更理想的延遲方法時(shí)使用schedule_timeout()函數(shù)。該方法會讓需要延遲執(zhí)行的任務(wù)睡眠到指定的延遲時(shí)間耗盡后再重新運(yùn)行。但該方法也不能保證睡眠時(shí)間正好等于指定的延遲時(shí)間,只能盡量使睡眠時(shí)間接近指定的延遲時(shí)間。當(dāng)指定時(shí)間到期后,內(nèi)核喚醒被延遲的任務(wù)并將其重新放回運(yùn)行隊(duì)列:
set_current_state(TASK_INTERRUPTABLE);
schedule_timeout(s*HZ);//參數(shù)是延遲的相對時(shí)間,單位為jiffies
注意,在調(diào)用schedule_timeout()函數(shù)前,必須將任務(wù)狀態(tài)設(shè)置成TASK_INTERRUPTABLE和TASK_UNINTERRUPTABLE狀態(tài)之一,否則任務(wù)不會睡眠。
注意,由于schedule_timeout()函數(shù)需要調(diào)用調(diào)度程序,所以調(diào)用它的代碼必須保證能夠睡眠。調(diào)用代碼必須處于進(jìn)程上下文中,并且不能持有鎖。
在(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: //用于檢查任務(wù)是否無限期的睡眠,如果這樣的話,函數(shù)不會為它設(shè)置定時(shí)器
/*
* 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); /*創(chuàng)建定時(shí)器,設(shè)置超時(shí)時(shí)間,設(shè)置超時(shí)函數(shù) */
__mod_timer(&timer, expire); /*激活定時(shí)器 */
schedule(); /*選擇其他任務(wù)運(yùn)行 */
del_singleshot_timer_sync(&timer); /*銷毀定時(shí)器*/
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);
}
在中
//當(dāng)定時(shí)器超時(shí),該函數(shù)被調(diào)用
static void process_timeout(unsigned long __data)
{
wake_up_process((struct task_struct *)__data);
}
schedule_timeout()函數(shù)是內(nèi)核定時(shí)器的一個(gè)簡單應(yīng)用。
設(shè)置超時(shí)時(shí)間,在等待隊(duì)列上睡眠
進(jìn)程上下文中的代碼為了等待特定事件發(fā)生,可以將自己放入等待隊(duì)列,然后調(diào)用調(diào)度程序去執(zhí)行新任務(wù)。一旦事件發(fā)生后,內(nèi)核調(diào)用wake_up()函數(shù)喚醒在睡眠隊(duì)列上的任務(wù)使其重新投入運(yùn)行。
有時(shí),等待隊(duì)列上的某個(gè)任務(wù)可能既在等待一個(gè)特定的事件到來,又在等待一個(gè)特定的時(shí)間到期,在這種情況下,代碼可以簡單地使用schedule_timeout()函數(shù)代替schedule()函數(shù),當(dāng)希望指定時(shí)間到期,任務(wù)就會被喚醒。代碼需要檢查被喚醒的原因,可能是被事件喚醒,可能是因?yàn)檠舆t時(shí)間到期,也可能是因?yàn)榻邮盏搅?a target="_blank">信號;然后執(zhí)行相應(yīng)的操作。
評論
查看更多