• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-167  評(píng)論-8  文章-0  trackbacks-0

            轉(zhuǎn)自:

            http://blog.chinaunix.net/space.php?uid=317451&do=blog&id=92667

            簡(jiǎn)介

            這篇文章主要記錄我在試圖解決如何盡可能精確地在某個(gè)特定的時(shí)間間隔執(zhí)行某項(xiàng)具體任務(wù)時(shí)的思路歷程,并在后期對(duì)相關(guān)的API進(jìn)行的歸納和總結(jié),以備參考。

            問(wèn)題引出

            很多時(shí)候,我們會(huì)有類似“每隔多長(zhǎng)時(shí)間執(zhí)行某項(xiàng)任務(wù)”的需求,乍看這個(gè)問(wèn)題并不難解決,實(shí)則并不容易,有很多隱含條件需要考慮,諸如:時(shí)間精度是多少?時(shí)間是否允許出現(xiàn)偏差,允許的偏差是多少,偏差之后如何處理?系統(tǒng)的負(fù)載如何?這個(gè)程序允許占用的系統(tǒng)資源是否有限制?這個(gè)程序運(yùn)行的硬件平臺(tái)如何?

            為了便于分析,我們鎖定題目為“每隔2妙打印當(dāng)前的系統(tǒng)時(shí)間(距離UNIX紀(jì)元的秒數(shù))”。

            基于sleep的樸素解法

            看到這個(gè)題目,我想大家的想法和我一樣,都是首先想到類似這樣的解法:


            #include <stdio.h>

            int main(int argc, char *argv[])
            {
                    while (1) {
                            printf("%d\n", time(NULL));
                            sleep(2);
                    }

                    return 0;
            }


            如果對(duì)時(shí)間精度要求不高,以上代碼確實(shí)能工作的很好。因?yàn)閟leep的時(shí)間精度只能到1s:

                   #include <unistd.h>

                   unsigned int sleep(unsigned int seconds);


            所以對(duì)于更高的時(shí)間精度(比如說(shuō)毫秒)來(lái)說(shuō),sleep就不能奏效了。如果沿著這個(gè)思路走下去,還分別有精確到微妙和納秒的函數(shù)usleep和nanosleep可用:

                  #include <unistd.h>

                   int usleep(useconds_t usec);

               Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

                   usleep(): _BSD_SOURCE || _XOPEN_SOURCE >= 500



                  #include <time.h>

                   int nanosleep(const struct timespec *req, struct timespec *rem);

               Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

                   nanosleep(): _POSIX_C_SOURCE >= 199309L


            既然有了能精確到納秒的nanosleep可用,上面的較低精度的函數(shù)也就可以休息了。實(shí)際上在Linux系統(tǒng)下,sleep和usleep就是通過(guò)一個(gè)系統(tǒng)調(diào)用nanosleep實(shí)現(xiàn)的。

            用帶有超時(shí)功能的API變相實(shí)現(xiàn)睡眠

            如果開(kāi)發(fā)者不知道有usleep和nanosleep,這個(gè)時(shí)候他可能會(huì)聯(lián)想到select類的系統(tǒng)調(diào)用:

                   According to POSIX.1-2001 */
                   #include <sys/select.h>

                   /* According to earlier standards */
                   #include <sys/time.h>
                   #include <sys/types.h>
                   #include <unistd.h>

                   int select(int nfds, fd_set *readfds, fd_set *writefds,
                              fd_set *exceptfds, struct timeval *timeout);


                  #include <poll.h>

                   int poll(struct pollfd *fds, nfds_t nfds, int timeout);


                   #include <sys/epoll.h>

                   int epoll_wait(int epfd, struct epoll_event *events,
                                  int maxevents, int timeout);
                   int epoll_pwait(int epfd, struct epoll_event *events,
                                  int maxevents, int timeout,
                                  const sigset_t *sigmask);


            從函數(shù)原型和相關(guān)手冊(cè)來(lái)看,poll和epoll_wait能提供的時(shí)間精度為毫秒,select比他們兩個(gè)略勝一籌,為微秒,和前述的usleep相當(dāng)。但是,果真如此么?這需要我們深入到Linux的具體實(shí)現(xiàn),在內(nèi)核里,這幾個(gè)系統(tǒng)調(diào)用的超時(shí)功能都是通過(guò)內(nèi)核中的動(dòng)態(tài)定時(shí)器實(shí)現(xiàn)的,而動(dòng)態(tài)定時(shí)器的時(shí)間精度是由當(dāng)前內(nèi)核的HZ數(shù)決定的。如果內(nèi)核的HZ是100,那么動(dòng)態(tài)定時(shí)器的時(shí)間精度就是1/HZ=1/100=10毫秒。目前,X86系統(tǒng)的HZ最大可以定義為1000,也就是說(shuō)X86系統(tǒng)的動(dòng)態(tài)定時(shí)器的時(shí)間精度最高只能到1毫秒。由此來(lái)看,select用來(lái)指示超時(shí)的timeval數(shù)據(jù)結(jié)構(gòu),只是看起來(lái)很美,實(shí)際上精度和poll/epoll_wait相當(dāng)。

            基于定時(shí)器的實(shí)現(xiàn)

            除了基于sleep的實(shí)現(xiàn)外,還有基于能用信號(hào)進(jìn)行異步提醒的定時(shí)器實(shí)現(xiàn):

            #include <stdio.h>
            #include <signal.h>

            int main(int argc, char *argv[])
            {
                    sigset_t block;

                    sigemptyset(&block);
                    sigaddset(&block, SIGALRM);
                    sigprocmask(SIG_BLOCK, &block, NULL);

                    while (1) {
                            printf("%d\n", time(NULL));
                            alarm(2);
                            sigwaitinfo(&block, NULL);
                    }

                    return 0;
            }


            顯然,上面的代碼并沒(méi)有利用信號(hào)進(jìn)行異步提醒,而是通過(guò)先阻塞信號(hào)的傳遞,然后用sigwaitinfo等待并將信號(hào)取出的方法將異步化同步。這樣做的目的是為了盡可能減少非必要的信號(hào)調(diào)用消耗,因?yàn)檫@個(gè)程序只需要執(zhí)行這個(gè)簡(jiǎn)單的單一任務(wù),所以異步除了帶來(lái)消耗外,并無(wú)任何好處。

            讀者可能已經(jīng)發(fā)現(xiàn)上面的代碼無(wú)非是把最初的代碼中的sleep換成了alarm和sigwaitinfo兩個(gè)調(diào)用,除了復(fù)雜了代碼之外,好像并沒(méi)有什么額外的好處。alarm的時(shí)間精度只能到1s,并且alarm和sigwaitinfo的確也可以看成是sleep的一種實(shí)現(xiàn),實(shí)際上有的sleep確實(shí)是透過(guò)alarm來(lái)實(shí)現(xiàn)的,請(qǐng)看sleep的手冊(cè)頁(yè):


            BUGS
                   sleep()  may be implemented using SIGALRM; mixing calls to alarm(2) and
                   sleep() is a bad idea.

                   Using longjmp(3) from a signal handler or  modifying  the  handling  of
                   SIGALRM while sleeping will cause undefined results.

            但是,這只是表象,本質(zhì)他們是不同的,sleep是撥了一個(gè)臨時(shí)實(shí)時(shí)定時(shí)器并等待定時(shí)器到期,而alarm是用進(jìn)程唯一的實(shí)時(shí)定時(shí)器來(lái)定時(shí)喚醒等待信號(hào)到來(lái)的進(jìn)程執(zhí)行。

            如果需要更高的時(shí)間精度,可以采用精度為微秒的alarm版本ualarm:

                   #include <unistd.h>

                   useconds_t ualarm(useconds_t usecs, useconds_t interval);

               Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

                   ualarm(): _BSD_SOURCE || _XOPEN_SOURCE >= 500


            或者是直接用setitimer操縱進(jìn)程的實(shí)時(shí)定時(shí)器:

                  #include <sys/time.h>

                   int getitimer(int which, struct itimerval *value);
                   int setitimer(int which, const struct itimerval *value,
                                 struct itimerval *ovalue);


            細(xì)心的你應(yīng)該已經(jīng)注意到了,ualarm和setitimer都額外提供了間隔時(shí)間的設(shè)置以便于間隔定時(shí)器用SIGALRM周期性的喚醒進(jìn)程,這對(duì)于我們的需求有什么意義呢?請(qǐng)聽(tīng)我慢慢道來(lái)。一般來(lái)說(shuō),需要定時(shí)執(zhí)行的任務(wù)所消耗的時(shí)間都很短,至少都會(huì)少于間隔時(shí)間,否則這個(gè)需求就是無(wú)法實(shí)現(xiàn)的。我們前面的程序?qū)崿F(xiàn),都是假設(shè)任務(wù)消耗時(shí)間為0,實(shí)際上的任務(wù)并不總是像打印當(dāng)前系統(tǒng)時(shí)間這么簡(jiǎn)單,即便它們持續(xù)的時(shí)間真的短到相對(duì)來(lái)說(shuō)可以忽略不計(jì),如果這些小的忽略不計(jì)累積起來(lái),也還是可能會(huì)造成長(zhǎng)時(shí)間后的大偏差,所以我們有必要將這段時(shí)間計(jì)算進(jìn)來(lái)。一種補(bǔ)救的措施是在任務(wù)執(zhí)行的前后執(zhí)行g(shù)ettimeofday得到系統(tǒng)的時(shí)間,然后做差得到任務(wù)消耗時(shí)間并在接下來(lái)的“sleep”中將其扣除。問(wèn)題看似解決了,但是我們畢竟沒(méi)有將系統(tǒng)進(jìn)行上下文切換的時(shí)間和計(jì)算消耗時(shí)間的時(shí)間考慮進(jìn)來(lái),這樣的話,還是會(huì)存在較大的誤差。另一種計(jì)算量相對(duì)小些的算法是:直接通過(guò)時(shí)間間隔計(jì)算下一次超時(shí)的絕對(duì)時(shí)間,然后根據(jù)當(dāng)前的絕對(duì)時(shí)間算出需要等待的時(shí)間并睡眠。但是,這也只是修修補(bǔ)補(bǔ)而已,并沒(méi)有從根本上解決問(wèn)題。間隔定時(shí)器的出現(xiàn)從根本上解決了上面所提的問(wèn)題,它自身就提供周期喚醒的功能,從而避免了每次都計(jì)算的負(fù)擔(dān)。因?yàn)閡alarm已經(jīng)被放棄,所以用setitimer再次改寫代碼:


            #include <stdio.h>
            #include <signal.h>
            #include <sys/time.h>

            int main(int argc, char *argv[])
            {
                    sigset_t block;
                    struct itimerval itv;

                    sigemptyset(&block);
                    sigaddset(&block, SIGALRM);
                    sigprocmask(SIG_BLOCK, &block, NULL);

                    itv.it_interval.tv_sec = 2;
                    itv.it_interval.tv_usec = 0;
                    itv.it_value = itv.it_interval;
                    setitimer(ITIMER_REAL, &itv, NULL);

                    while (1) {
                            printf("%d\n", time(NULL));
                            sigwaitinfo(&block, NULL);
                    }

                    return 0;
            }


            進(jìn)程的間隔計(jì)時(shí)器能夠提供的時(shí)間精度為微秒,對(duì)于大多數(shù)的應(yīng)用來(lái)說(shuō),應(yīng)該已經(jīng)足夠,如果需要更高的時(shí)間精度,或者需要多個(gè)定時(shí)器,那么每個(gè)進(jìn)程一個(gè)的實(shí)時(shí)間隔定時(shí)器就無(wú)能為力了,這個(gè)時(shí)候我們可以選擇POSIX實(shí)時(shí)擴(kuò)展中的定時(shí)器:

                  #include <signal.h>
                   #include <time.h>

                   int timer_create(clockid_t clockid, struct sigevent *restrict evp,
                          timer_t *restrict timerid); 
                   int timer_getoverrun(timer_t timerid);
                   int timer_gettime(timer_t timerid, struct itimerspec *value);
                   int timer_settime(timer_t timerid, int flags,
                          const struct itimerspec *restrict value,
                          struct itimerspec *restrict ovalue);


            它實(shí)際上就是進(jìn)程間隔定時(shí)器的增強(qiáng)版,除了可以定制時(shí)鐘源(nanosleep也存在能定制時(shí)鐘源的版本:clock_nanosleep)和時(shí)間精度提高到納秒外,它還能通過(guò)將evp->sigev_notify設(shè)定為如下值來(lái)定制定時(shí)器到期后的行為:
            • SIGEV_SIGNAL: 發(fā)送由evp->sigev_sino指定的信號(hào)到調(diào)用進(jìn)程,evp->sigev_value的值將被作為siginfo_t結(jié)構(gòu)體中si_value的值。
            • SIGEV_NONE:什么都不做,只提供通過(guò)timer_gettime和timer_getoverrun查詢超時(shí)信息。
            • SIGEV_THREAD: 以evp->sigev_notification_attributes為線程屬性創(chuàng)建一個(gè)線程,在新建的線程內(nèi)部以evp->sigev_value為參數(shù)調(diào)用evp->sigev_notification_function。
            • SIGEV_THREAD_ID:和SIGEV_SIGNAL類似,不過(guò)它只將信號(hào)發(fā)送到線程號(hào)為evp->sigev_notify_thread_id的線程,注意:這里的線程號(hào)不一定是POSIX線程號(hào),而是線程調(diào)用gettid返回的實(shí)際線程號(hào),并且這個(gè)線程必須實(shí)際存在且屬于當(dāng)前的調(diào)用進(jìn)程。
            更新后的程序如下(需要連接實(shí)時(shí)擴(kuò)展庫(kù): -lrt):

            #include <stdio.h>
            #include <signal.h>
            #include <time.h>
            #include <errno.h>
            #include <sched.h>

            int main(int argc, char *argv[])
            {
                    timer_t timer;
                    struct itimerspec timeout;
                    sigset_t block;
                    struct sched_param param;

                    sigemptyset(&block);
                    sigaddset(&block, SIGALRM);
                    sigprocmask(SIG_BLOCK, &block, NULL);

                    timer_create(CLOCK_MONOTONIC, NULL, &timer);
                    timeout.it_interval.tv_sec = 2;
                    timeout.it_interval.tv_nsec = 0;
                    timeout.it_value = timeout.it_interval;
                    timer_settime(timer, 0, &timeout, NULL);

                    while (1) {
                            fprintf(stderr, "%d\n", time(NULL));
                            sigwaitinfo(&block, NULL);
                    }

                    return 0;
            }


            至于時(shí)鐘源為什么是CLOCK_MONOTONIC而不是CLOCK_REALTIME,主要是考慮到系統(tǒng)的實(shí)時(shí)時(shí)鐘可能會(huì)在程序運(yùn)行過(guò)程中更改,所以存在一定的不確定性,而CLOCK_MONOTONIC則不會(huì),較為穩(wěn)定。

            至此為止,我們已經(jīng)找到了目前Linux提供的精度最高的定時(shí)器API,它應(yīng)該能滿足大多數(shù)情況的要求了。

            其它問(wèn)題

            傳統(tǒng)信號(hào)的不可靠性

            傳統(tǒng)UNIX信號(hào)是不可靠的,也就是說(shuō)如果當(dāng)前的信號(hào)沒(méi)有被處理,那么后續(xù)的同類信號(hào)將被丟失,而不是被排隊(duì),而實(shí)時(shí)信號(hào)則沒(méi)有這個(gè)問(wèn)題,它是被排隊(duì)的。聯(lián)系到當(dāng)前應(yīng)用,如果信號(hào)丟失,則是因?yàn)槿蝿?wù)消耗了過(guò)多的處理器時(shí)間,而這個(gè)不確定性是那個(gè)任務(wù)帶來(lái)的,需要改進(jìn)的應(yīng)該是那個(gè)任務(wù)。

            系統(tǒng)負(fù)載過(guò)高

            如果系統(tǒng)的負(fù)載過(guò)高,使得我們的程序因?yàn)椴荒艿玫郊皶r(shí)的調(diào)度導(dǎo)致時(shí)間精度降低,我們不妨通過(guò)nice提高當(dāng)前程序的優(yōu)先級(jí),必要時(shí)可以通過(guò)sched_setscheduler將當(dāng)前進(jìn)程切換成優(yōu)先級(jí)最高的實(shí)時(shí)進(jìn)程已確保得到及時(shí)調(diào)度。

            硬件相關(guān)的問(wèn)題

            硬件配置也極大的影響著定時(shí)器的精度,有的比較老的遺留系統(tǒng)可能沒(méi)有比較精確的硬件定時(shí)器,那樣的話我們就無(wú)法期待它能提供多高的時(shí)鐘精度了。相反,如果系統(tǒng)的配置比較高,比如說(shuō)對(duì)稱多處理系統(tǒng),那么即使有的處理器負(fù)載比較高,我們也能通過(guò)將一個(gè)處理器單獨(dú)分配出來(lái)處理定時(shí)器來(lái)提高定時(shí)器的精度。

            更高的時(shí)間精度

            雖然,Linux的API暗示它能夠提供納秒級(jí)的時(shí)間精度,但是,由于種種不確定因素,它實(shí)際上并不能提供納秒級(jí)的精度,比較脆弱。如果你需要更高強(qiáng)度的實(shí)時(shí)性,請(qǐng)考慮采用軟實(shí)時(shí)系統(tǒng)、硬實(shí)時(shí)系統(tǒng)、專有系統(tǒng),甚至是專業(yè)硬件。

            注意:

            為了簡(jiǎn)便,以上所有代碼都沒(méi)有出錯(cuò)處理,請(qǐng)讀者在現(xiàn)實(shí)的應(yīng)用中自行加入出錯(cuò)處理,以提高程序的健壯性。尤其注意sleep類的返回值,它們可能沒(méi)到期就返回,這個(gè)時(shí)候你應(yīng)該手動(dòng)計(jì)算需要再睡眠多長(zhǎng)才能滿足原始的睡眠時(shí)間要求,如果該API并沒(méi)有返回剩余的時(shí)間的話。
            posted on 2011-08-01 11:32 老馬驛站 閱讀(1662) 評(píng)論(0)  編輯 收藏 引用 所屬分類: linux
            精品熟女少妇av免费久久| 亚洲日本久久久午夜精品| 国产成人久久精品一区二区三区| 国内精品久久久久久99蜜桃| 99久久国产综合精品成人影院| 99久久精品免费| 国产亚洲美女精品久久久2020| 久久ZYZ资源站无码中文动漫| 91精品国产91久久| 人妻无码精品久久亚瑟影视 | 久久99国产精品久久99果冻传媒| 99久久久久| 亚洲精品无码久久久久| 国产精品va久久久久久久| 一本色道久久99一综合| 久久免费99精品国产自在现线| 久久久久亚洲av无码专区导航| 久久狠狠一本精品综合网| 久久96国产精品久久久| 亚洲狠狠婷婷综合久久久久| 久久精品亚洲乱码伦伦中文| 成人国内精品久久久久一区| 亚洲精品无码久久一线| 日韩欧美亚洲国产精品字幕久久久| 久久99国产精品久久99果冻传媒| 久久99久国产麻精品66| 亚洲国产一成久久精品国产成人综合| 精品久久久久久久久午夜福利| 久久久精品国产sm调教网站| 国内精品人妻无码久久久影院导航| 久久天天躁狠狠躁夜夜2020老熟妇| 国产精品美女久久久久| 亚洲精品乱码久久久久久中文字幕| 伊人热热久久原色播放www| 99久久精品九九亚洲精品| 伊人久久免费视频| 久久久国产精品| 久久香综合精品久久伊人| 一级做a爰片久久毛片看看| 亚洲国产成人精品91久久久 | 亚洲国产精品综合久久网络|