我們知道低分辨率定時器和高精度定時器的實現(xiàn)原理,內(nèi)核為了方便其它子系統(tǒng),在時間子系統(tǒng)中提供了一些用于延時或調(diào)度的API,例如msleep,hrtimer_nanosleep等等,這些API基于低分辨率定時器或高精度定時器來實現(xiàn),本章的內(nèi)容就是討論這些方便、好用的API是如何利用定時器系統(tǒng)來完成所需的功能的。
1. ?msleep
msleep相信大家都用過,它可能是內(nèi)核用使用最廣泛的延時函數(shù)之一,它會使當前進程被調(diào)度并讓出cpu一段時間,因為這一特性,它不能用于中斷上下文,只能用于進程上下文中。要想在中斷上下文中使用延時函數(shù),請使用會阻塞cpu的無調(diào)度版本mdelay。msleep的函數(shù)原型如下:
[cpp]?view plain?copy
void?msleep(unsigned?int?msecs)??
延時的時間由參數(shù)msecs指定,單位是毫秒,事實上,msleep的實現(xiàn)基于低分辨率定時器,所以msleep的實際精度只能也是1/HZ級別。內(nèi)核還提供了另一個比較類似的延時函數(shù)msleep_interruptible:
[cpp]?view plain?copy
unsigned?long?msleep_interruptible(unsigned?int?msecs)??
延時的單位同樣毫秒數(shù),它們的區(qū)別如下:
函數(shù)延時單位返回值是否可被信號中斷msleep毫秒無否msleep_interruptible毫秒未完成的毫秒數(shù)是最主要的區(qū)別就是msleep會保證所需的延時一定會被執(zhí)行完,而msleep_interruptible則可以在延時進行到一半時被信號打斷而退出延時,剩余的延時數(shù)則通過返回值返回。兩個函數(shù)最終的代碼都會到達schedule_timeout函數(shù),它們的調(diào)用序列如下圖所示:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
圖1.1 ?兩個延時函數(shù)的調(diào)用序列
下面我們看看schedule_timeout函數(shù)的實現(xiàn),函數(shù)首先處理兩種特殊情況,一種是傳入的延時jiffies數(shù)是個負數(shù),則打印一句警告信息,然后馬上返回,另一種是延時jiffies數(shù)是MAX_SCHEDULE_TIMEOUT,表明需要一直延時,直接執(zhí)行調(diào)度即可:
[cpp]?view plain?copy
signed?long?__sched?schedule_timeout(signed?long?timeout)??
{??
struct?timer_list?timer;??
unsigned?long?expire;??
switch?(timeout)??
{??
case?MAX_SCHEDULE_TIMEOUT:??
schedule();??
goto?out;??
default:??
if?(timeout?0)?{??
printk(KERN_ERR?"schedule_timeout:?wrong?timeout?"??
"value?%lx\n",?timeout);??
dump_stack();??
current->state?=?TASK_RUNNING;??
goto?out;??
}??
}??
然后計算到期的jiffies數(shù),并在堆棧上建立一個低分辨率定時器,把到期時間設(shè)置到該定時器中,啟動定時器后,通過schedule把當前進程調(diào)度出cpu的運行隊列:
[cpp]?view plain?copy
expire?=?timeout?+?jiffies;??
setup_timer_on_stack(&timer,?process_timeout,?(unsigned?long)current);??
__mod_timer(&timer,?expire,?false,?TIMER_NOT_PINNED);??
schedule();??
到這個時候,進程已經(jīng)被調(diào)度走,那它如何返回繼續(xù)執(zhí)行?我們看到定時器的到期回調(diào)函數(shù)是process_timeout,參數(shù)是當前進程的task_struct指針,看看它的實現(xiàn):
[cpp]?view plain?copy
static?void?process_timeout(unsigned?long?__data)??
{??
wake_up_process((struct?task_struct?*)__data);??
}??
噢,沒錯,定時器一旦到期,進程會被喚醒并繼續(xù)執(zhí)行:
[cpp]?view plain?copy
del_singleshot_timer_sync(&timer);??
/*?Remove?the?timer?from?the?object?tracker?*/??
destroy_timer_on_stack(&timer);??
timeout?=?expire?-?jiffies;??
out:??
return?timeout?0???0?:?timeout;??
}??
schedule返回后,說明要不就是定時器到期,要不就是因為其它時間導(dǎo)致進程被喚醒,函數(shù)要做的就是刪除在堆棧上建立的定時器,返回剩余未完成的jiffies數(shù)。
說完了關(guān)鍵的schedule_timeout函數(shù),我們看看msleep如何實現(xiàn):
[cpp]?view plain?copy
signed?long?__sched?schedule_timeout_uninterruptible(signed?long?timeout)??
{??
__set_current_state(TASK_UNINTERRUPTIBLE);??
return?schedule_timeout(timeout);??
}??
void?msleep(unsigned?int?msecs)??
{??
unsigned?long?timeout?=?msecs_to_jiffies(msecs)?+?1;??
while?(timeout)??
timeout?=?schedule_timeout_uninterruptible(timeout);??
}??
msleep先是把毫秒轉(zhuǎn)換為jiffies數(shù),通過一個while循環(huán)保證所有的延時被執(zhí)行完畢,延時操作通過schedule_timeout_uninterruptible函數(shù)完成,它僅僅是在把進程的狀態(tài)修改為TASK_UNINTERRUPTIBLE后,調(diào)用上述的schedule_timeout來完成具體的延時操作,TASK_UNINTERRUPTIBLE狀態(tài)保證了msleep不會被信號喚醒,也就意味著在msleep期間,進程不能被kill掉。
看看msleep_interruptible的實現(xiàn):
[cpp]?view plain?copy
signed?long?__sched?schedule_timeout_interruptible(signed?long?timeout)??
{??
__set_current_state(TASK_INTERRUPTIBLE);??
return?schedule_timeout(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);??
}??
msleep_interruptible通過schedule_timeout_interruptible中轉(zhuǎn),schedule_timeout_interruptible的唯一區(qū)別就是把進程的狀態(tài)設(shè)置為了TASK_INTERRUPTIBLE,說明在延時期間有信號通知,while循環(huán)會馬上終止,剩余的jiffies數(shù)被轉(zhuǎn)換成毫秒返回。實際上,你也可以利用schedule_timeout_interruptible或schedule_timeout_uninterruptible構(gòu)造自己的延時函數(shù),同時,內(nèi)核還提供了另外一個類似的函數(shù),不用我解釋,看代碼就知道它的用意了:
[cpp]?view plain?copy
signed?long?__sched?schedule_timeout_killable(signed?long?timeout)??
{??
__set_current_state(TASK_KILLABLE);??
return?schedule_timeout(timeout);??
}??
2. ?hrtimer_nanosleep
第一節(jié)討論的msleep函數(shù)基于時間輪定時系統(tǒng),只能提供毫秒級的精度,實際上,它的精度取決于HZ的配置值,如果HZ小于1000,它甚至無法達到毫秒級的精度,要想得到更為精確的延時,我們自然想到的是要利用高精度定時器來實現(xiàn)。沒錯,linux為用戶空間提供了一個api:nanosleep,它能提供納秒級的延時精度,該用戶空間函數(shù)對應(yīng)的內(nèi)核實現(xiàn)是sys_nanosleep,它的工作交由高精度定時器系統(tǒng)的hrtimer_nanosleep函數(shù)實現(xiàn),最終的大部分工作則由do_nanosleep完成。調(diào)用過程如下圖所示:
圖 ?2.1 ?nanosleep的調(diào)用過程
與msleep的實現(xiàn)相類似,hrtimer_nanosleep函數(shù)首先在堆棧中創(chuàng)建一個高精度定時器,設(shè)置它的到期時間,然后通過do_nanosleep完成最終的延時工作,當前進程在掛起相應(yīng)的延時時間后,退出do_nanosleep函數(shù),銷毀堆棧中的定時器并返回0值表示執(zhí)行成功。不過do_nanosleep可能在沒有達到所需延時數(shù)量時由于其它原因退出,如果出現(xiàn)這種情況,hrtimer_nanosleep的最后部分把剩余的延時時間記入進程的restart_block中,并返回ERESTART_RESTARTBLOCK錯誤代碼,系統(tǒng)或者用戶空間可以根據(jù)此返回值決定是否重新調(diào)用nanosleep以便把剩余的延時繼續(xù)執(zhí)行完成。下面是hrtimer_nanosleep的代碼:
[cpp]?view plain?copy
long?hrtimer_nanosleep(struct?timespec?*rqtp,?struct?timespec?__user?*rmtp,??
const?enum?hrtimer_mode?mode,?const?clockid_t?clockid)??
{??
struct?restart_block?*restart;??
struct?hrtimer_sleeper?t;??
int?ret?=?0;??
unsigned?long?slack;??
slack?=?current->timer_slack_ns;??
if?(rt_task(current))??
slack?=?0;??
hrtimer_init_on_stack(&t.timer,?clockid,?mode);??
hrtimer_set_expires_range_ns(&t.timer,?timespec_to_ktime(*rqtp),?slack);??
if?(do_nanosleep(&t,?mode))??
goto?out;??
/*?Absolute?timers?do?not?update?the?rmtp?value?and?restart:?*/??
if?(mode?==?HRTIMER_MODE_ABS)?{??
ret?=?-ERESTARTNOHAND;??
goto?out;??
}??
if?(rmtp)?{??
ret?=?update_rmtp(&t.timer,?rmtp);??
if?(ret?<=?0)??
goto?out;??
}??
restart?=?¤t_thread_info()->restart_block;??
restart->fn?=?hrtimer_nanosleep_restart;??
restart->nanosleep.clockid?=?t.timer.base->clockid;??
restart->nanosleep.rmtp?=?rmtp;??
restart->nanosleep.expires?=?hrtimer_get_expires_tv64(&t.timer);??
ret?=?-ERESTART_RESTARTBLOCK;??
out:??
destroy_hrtimer_on_stack(&t.timer);??
return?ret;??
}??
接著我們看看do_nanosleep的實現(xiàn)代碼,它首先通過hrtimer_init_sleeper函數(shù),把定時器的回調(diào)函數(shù)設(shè)置為hrtimer_wakeup,把當前進程的task_struct結(jié)構(gòu)指針保存在hrtimer_sleeper結(jié)構(gòu)的task字段中:
[cpp]?view plain?copy
void?hrtimer_init_sleeper(struct?hrtimer_sleeper?*sl,?struct?task_struct?*task)??
{??
sl->timer.function?=?hrtimer_wakeup;??
sl->task?=?task;??
}??
EXPORT_SYMBOL_GPL(hrtimer_init_sleeper);??
static?int?__sched?do_nanosleep(struct?hrtimer_sleeper?*t,?enum?hrtimer_mode?mode)??
{??
hrtimer_init_sleeper(t,?current);??
然后,通過一個do/while循環(huán)內(nèi):啟動定時器,掛起當前進程,等待定時器或其它事件喚醒進程。這里的循環(huán)體實現(xiàn)比較怪異,它使用hrtimer_active函數(shù)間接地判斷定時器是否到期,如果hrtimer_active返回false,說明定時器已經(jīng)過期,然后把hrtimer_sleeper結(jié)構(gòu)的task字段設(shè)置為NULL,從而導(dǎo)致循環(huán)體的結(jié)束,另一個結(jié)束條件是當前進程收到了信號事件,所以,當因為是定時器到期而退出時,do_nanosleep返回true,否則返回false,上述的hrtimer_nanosleep正是利用了這一特性來決定它的返回值。以下是do_nanosleep循環(huán)體的代碼:
[cpp]?view plain?copy
do?{??
set_current_state(TASK_INTERRUPTIBLE);??
hrtimer_start_expires(&t->timer,?mode);??
if?(!hrtimer_active(&t->timer))??
t->task?=?NULL;??
if?(likely(t->task))??
schedule();??
hrtimer_cancel(&t->timer);??
mode?=?HRTIMER_MODE_ABS;??
}?while?(t->task?&&?!signal_pending(current));??
__set_current_state(TASK_RUNNING);??
return?t->task?==?NULL;??
}??
除了hrtimer_nanosleep,高精度定時器系統(tǒng)還提供了幾種用于延時/掛起進程的api:
schedule_hrtimeout ? ?使得當前進程休眠指定的時間,使用CLOCK_MONOTONIC計時系統(tǒng);
schedule_hrtimeout_range ? ?使得當前進程休眠指定的時間范圍,使用CLOCK_MONOTONIC計時系統(tǒng);
schedule_hrtimeout_range_clock ? ?使得當前進程休眠指定的時間范圍,可以自行指定計時系統(tǒng);
usleep_range?使得當前進程休眠指定的微妙數(shù),使用CLOCK_MONOTONIC計時系統(tǒng);
它們之間的調(diào)用關(guān)系如下:
圖 2.2 ?schedule_hrtimeout_xxxx系列函數(shù)
最終,所有的實現(xiàn)都會進入到schedule_hrtimeout_range_clock函數(shù)。需要注意的是schedule_hrtimeout_xxxx系列函數(shù)在調(diào)用前,最好利用set_current_state函數(shù)先設(shè)置進程的狀態(tài),在這些函數(shù)返回前,進城的狀態(tài)會再次被設(shè)置為TASK_RUNNING。如果事先把狀態(tài)設(shè)置為TASK_UNINTERRUPTIBLE,它們會保證函數(shù)返回前一定已經(jīng)經(jīng)過了所需的延時時間,如果事先把狀態(tài)設(shè)置為TASK_INTERRUPTIBLE,則有可能在尚未到期時由其它信號喚醒進程從而導(dǎo)致函數(shù)返回。主要實現(xiàn)該功能的函數(shù)schedule_hrtimeout_range_clock和前面的do_nanosleep函數(shù)實現(xiàn)原理基本一致。大家可以自行參考內(nèi)核的代碼,它們位于:kernel/hrtimer.c。
?
評論
查看更多