我們知道低分辨率定時器和高精度定時器的實現原理,內核為了方便其它子系統,在時間子系統中提供了一些用于延時或調度的API,例如msleep,hrtimer_nanosleep等等,這些API基于低分辨率定時器或高精度定時器來實現,本章的內容就是討論這些方便、好用的API是如何利用定時器系統來完成所需的功能的。
1. ?msleep
msleep相信大家都用過,它可能是內核用使用最廣泛的延時函數之一,它會使當前進程被調度并讓出cpu一段時間,因為這一特性,它不能用于中斷上下文,只能用于進程上下文中。要想在中斷上下文中使用延時函數,請使用會阻塞cpu的無調度版本mdelay。msleep的函數原型如下:
[cpp]?view plain?copy
void?msleep(unsigned?int?msecs)??
延時的時間由參數msecs指定,單位是毫秒,事實上,msleep的實現基于低分辨率定時器,所以msleep的實際精度只能也是1/HZ級別。內核還提供了另一個比較類似的延時函數msleep_interruptible:
[cpp]?view plain?copy
unsigned?long?msleep_interruptible(unsigned?int?msecs)??
延時的單位同樣毫秒數,它們的區別如下:
函數延時單位返回值是否可被信號中斷msleep毫秒無否msleep_interruptible毫秒未完成的毫秒數是最主要的區別就是msleep會保證所需的延時一定會被執行完,而msleep_interruptible則可以在延時進行到一半時被信號打斷而退出延時,剩余的延時數則通過返回值返回。兩個函數最終的代碼都會到達schedule_timeout函數,它們的調用序列如下圖所示:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
圖1.1 ?兩個延時函數的調用序列
下面我們看看schedule_timeout函數的實現,函數首先處理兩種特殊情況,一種是傳入的延時jiffies數是個負數,則打印一句警告信息,然后馬上返回,另一種是延時jiffies數是MAX_SCHEDULE_TIMEOUT,表明需要一直延時,直接執行調度即可:
[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數,并在堆棧上建立一個低分辨率定時器,把到期時間設置到該定時器中,啟動定時器后,通過schedule把當前進程調度出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();??
到這個時候,進程已經被調度走,那它如何返回繼續執行?我們看到定時器的到期回調函數是process_timeout,參數是當前進程的task_struct指針,看看它的實現:
[cpp]?view plain?copy
static?void?process_timeout(unsigned?long?__data)??
{??
wake_up_process((struct?task_struct?*)__data);??
}??
噢,沒錯,定時器一旦到期,進程會被喚醒并繼續執行:
[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返回后,說明要不就是定時器到期,要不就是因為其它時間導致進程被喚醒,函數要做的就是刪除在堆棧上建立的定時器,返回剩余未完成的jiffies數。
說完了關鍵的schedule_timeout函數,我們看看msleep如何實現:
[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先是把毫秒轉換為jiffies數,通過一個while循環保證所有的延時被執行完畢,延時操作通過schedule_timeout_uninterruptible函數完成,它僅僅是在把進程的狀態修改為TASK_UNINTERRUPTIBLE后,調用上述的schedule_timeout來完成具體的延時操作,TASK_UNINTERRUPTIBLE狀態保證了msleep不會被信號喚醒,也就意味著在msleep期間,進程不能被kill掉。
看看msleep_interruptible的實現:
[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中轉,schedule_timeout_interruptible的唯一區別就是把進程的狀態設置為了TASK_INTERRUPTIBLE,說明在延時期間有信號通知,while循環會馬上終止,剩余的jiffies數被轉換成毫秒返回。實際上,你也可以利用schedule_timeout_interruptible或schedule_timeout_uninterruptible構造自己的延時函數,同時,內核還提供了另外一個類似的函數,不用我解釋,看代碼就知道它的用意了:
[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
第一節討論的msleep函數基于時間輪定時系統,只能提供毫秒級的精度,實際上,它的精度取決于HZ的配置值,如果HZ小于1000,它甚至無法達到毫秒級的精度,要想得到更為精確的延時,我們自然想到的是要利用高精度定時器來實現。沒錯,linux為用戶空間提供了一個api:nanosleep,它能提供納秒級的延時精度,該用戶空間函數對應的內核實現是sys_nanosleep,它的工作交由高精度定時器系統的hrtimer_nanosleep函數實現,最終的大部分工作則由do_nanosleep完成。調用過程如下圖所示:
圖 ?2.1 ?nanosleep的調用過程
與msleep的實現相類似,hrtimer_nanosleep函數首先在堆棧中創建一個高精度定時器,設置它的到期時間,然后通過do_nanosleep完成最終的延時工作,當前進程在掛起相應的延時時間后,退出do_nanosleep函數,銷毀堆棧中的定時器并返回0值表示執行成功。不過do_nanosleep可能在沒有達到所需延時數量時由于其它原因退出,如果出現這種情況,hrtimer_nanosleep的最后部分把剩余的延時時間記入進程的restart_block中,并返回ERESTART_RESTARTBLOCK錯誤代碼,系統或者用戶空間可以根據此返回值決定是否重新調用nanosleep以便把剩余的延時繼續執行完成。下面是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的實現代碼,它首先通過hrtimer_init_sleeper函數,把定時器的回調函數設置為hrtimer_wakeup,把當前進程的task_struct結構指針保存在hrtimer_sleeper結構的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循環內:啟動定時器,掛起當前進程,等待定時器或其它事件喚醒進程。這里的循環體實現比較怪異,它使用hrtimer_active函數間接地判斷定時器是否到期,如果hrtimer_active返回false,說明定時器已經過期,然后把hrtimer_sleeper結構的task字段設置為NULL,從而導致循環體的結束,另一個結束條件是當前進程收到了信號事件,所以,當因為是定時器到期而退出時,do_nanosleep返回true,否則返回false,上述的hrtimer_nanosleep正是利用了這一特性來決定它的返回值。以下是do_nanosleep循環體的代碼:
[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,高精度定時器系統還提供了幾種用于延時/掛起進程的api:
schedule_hrtimeout ? ?使得當前進程休眠指定的時間,使用CLOCK_MONOTONIC計時系統;
schedule_hrtimeout_range ? ?使得當前進程休眠指定的時間范圍,使用CLOCK_MONOTONIC計時系統;
schedule_hrtimeout_range_clock ? ?使得當前進程休眠指定的時間范圍,可以自行指定計時系統;
usleep_range?使得當前進程休眠指定的微妙數,使用CLOCK_MONOTONIC計時系統;
它們之間的調用關系如下:
圖 2.2 ?schedule_hrtimeout_xxxx系列函數
最終,所有的實現都會進入到schedule_hrtimeout_range_clock函數。需要注意的是schedule_hrtimeout_xxxx系列函數在調用前,最好利用set_current_state函數先設置進程的狀態,在這些函數返回前,進城的狀態會再次被設置為TASK_RUNNING。如果事先把狀態設置為TASK_UNINTERRUPTIBLE,它們會保證函數返回前一定已經經過了所需的延時時間,如果事先把狀態設置為TASK_INTERRUPTIBLE,則有可能在尚未到期時由其它信號喚醒進程從而導致函數返回。主要實現該功能的函數schedule_hrtimeout_range_clock和前面的do_nanosleep函數實現原理基本一致。大家可以自行參考內核的代碼,它們位于:kernel/hrtimer.c。
?
評論
查看更多