Linux時間子系統之七:定時器的應用–msleep(),hrtimer_nanosleep()

來源:互聯網
上載者:User

我們已經在前面幾章介紹了低解析度定時器和高精度定時器的實現原理,核心為了方便其它子系統,在時間子系統中提供了一些用於延時或調度的API,例如msleep,hrtimer_nanosleep等等,這些API基於低解析度定時器或高精度定時器來實現,本章的內容就是討論這些方便、好用的API是如何利用定時器系統來完成所需的功能的。

/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/

1.  msleep

msleep相信大家都用過,它可能是核心用使用最廣泛的延時函數之一,它會使當前進程被調度並讓出cpu一段時間,因為這一特性,它不能用於中斷上下文,只能用於進程上下文中。要想在中斷上下文中使用延時函數,請使用會阻塞cpu的無調度版本mdelay。msleep的函數原型如下:

void msleep(unsigned int msecs)

延時的時間由參數msecs指定,單位是毫秒,事實上,msleep的實現基於低解析度定時器,所以msleep的實際精度只能也是1/HZ層級。核心還提供了另一個比較類似的延時函數msleep_interruptible:

unsigned long msleep_interruptible(unsigned int msecs)

延時的單位同樣毫秒數,它們的區別如下:

函數 延時單位 傳回值 是否可被訊號中斷
msleep 毫秒
msleep_interruptible 毫秒 未完成的毫秒數

最主要的區別就是msleep會保證所需的延時一定會被執行完,而msleep_interruptible則可以在延時進行到一半時被訊號打斷而退出延時,剩餘的延時數則通過傳回值返回。兩個函數最終的代碼都會到達schedule_timeout函數,它們的調用序列如所示:
                                             

                                                                            圖1.1  兩個延時函數的調用序列

下面我們看看schedule_timeout函數的實現,函數首先處理兩種特殊情況,一種是傳入的延時jiffies數是個負數,則列印一句警告資訊,然後馬上返回,另一種是延時jiffies數是MAX_SCHEDULE_TIMEOUT,表明需要一直延時,直接執行調度即可:

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的運行隊列:

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指標,看看它的實現:

static void process_timeout(unsigned long __data){wake_up_process((struct task_struct *)__data);}

噢,沒錯,定時器一旦到期,進程會被喚醒並繼續執行:

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如何?:

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的實現:

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構造自己的延時函數,同時,核心還提供了另外一個類似的函數,不用我解釋,看代碼就知道它的用意了:

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的代碼:
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欄位中:

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迴圈體的代碼:

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。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.