1. 程式人生 > >Linux下如何實現秒以下精確定時與休眠

Linux下如何實現秒以下精確定時與休眠

Linux中提供的休眠函式是sleep和alarm,但是他們僅僅提供以秒為單位的休眠,這中休眠有些程序顯然太長了,那麼怎樣才能使程序以更小的時間解析度休眠呢?

下面就做分別介紹。

一、間隔定時器
1.setitimer
settitimer建立一個間隔式定時器,這種定時器會在未來某個時間到期,並於此後(可選擇地)每隔一段時間到期一次

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
引數 which:

1. ITIMER_REAL  建立真實倒計時定時器。到期產生SIGALARM訊號

2.ITIMER_VIRTUAL 建立以程序虛擬時間(使用者模式下的cpu時間)倒計時定時器。到期產生SIGVTALRM訊號

3.ITIMER_PROF 建立一個profiling定時器,以程序時間(使用者態與核心態cpu時間總和)倒計時。到期產生SIGPROF訊號。

所有這些訊號的預設處理都會終止程序。

引數 new_value和old_value都是指向struct itimerval的指標。

struct itimerval {
    struct timeval it_interval;
    struct timeval it_value;
}

其中的timeval結構體如下:由秒和微妙組成

struc timeval {
    time_t tv_sec;//秒
    suseconds tv_usec;//微妙
}

引數new_value的下屬結構it_value指定了距離定時器到期的延時時間。it_interval說明該定時器是否為週期性定時器。如果it_interval的兩個欄位均為0,那麼就是一次性定時器。只要任何一個欄位非零,那麼在每次定時器到期之後,都會將定時器重置為指定間隔後再次到期。

程序只能擁有上述3中定時器的一種。當第2次呼叫settitimer時時將new_value的兩個欄位均設定為0,那麼會遮蔽任何已有的定時器。

引數old_value是一個輸出引數,如果引數不為NULL,返回之前定時器的設定。如果old_value的兩個欄位都為0說明該定時器處於遮蔽狀態。如果old_value.it_interval的兩個欄位都為0,說明之前定時器設定的是一次性定時器。如果不關心前一次的定時器設定,將old_value設定為NULL就可以了。

定時器會從初始值(it_value)倒計時一直到0為止。遞減為0時,會將相應的訊號傳送給程序,隨後,如果it_interval非0,那麼會再次將it_value載入至定時器,重新計時。

我們可以在任何時候呼叫gettitimer函式,瞭解定時器當前狀態、距離下次到期的剩餘時間。

int getitimer(int which, struct itimerval *curr_value)
which指定是哪種定時器,curr_value返回就是剩餘時間。

2. alarm
系統呼叫alarm函式建立一次性實時定時器提供了一個簡單的介面

unsigned int alarm(unsigned int seconds);
引數seconds表示定時器到期的秒數。到期後傳送SIGALRM訊號。

呼叫alarm會覆蓋對定時器的前一次設定,呼叫alarm(0)可遮蔽現有定時器。

3. setitimer alarm聯絡
alarm和setitimer針對同一程序共享一個實時定時器,這意味著,無論呼叫兩者哪個都覆蓋了之前的設定。

二、POSIX間隔式定時器
使用setitimer來設定定時器,有幾個制約:

1.針對ITIMER_REAL ITIMER_VIRTUAL ITIMER_PROF這3類定時器,每種只能設定一個

2.只能通過傳送訊號的方式來通知定時器,另外也不能改變到期的訊號

3.如果一個間隔式定時器到期多次,且相應的訊號阻塞,那麼只會呼叫一次訊號處理函式

4.分別率只能達到微妙級

因此我們可以使用timer_create函式建立定時器

timer_create函式,可以傳送訊號給程序也可以給執行緒,可以開啟執行緒呼叫函式處理定時器到期。

這裡具體函式使用就不說了

timer_settime函式設定定時器並且開啟

timer_gettime獲取定時器當前值

timer_delete刪除定時器

三、利用檔案描述符進行通知的定時器
linux特有的timerfd API,可以從檔案描述符中讀取所建立定時器的到期通知。可以使用select poll epoll將這樣描述符和其他描述符一同進行監控,非常方便。

這幾個API和之前的timer_create timer_setime timer_gettime相類似。

int timerfd_create(int clockid, int flags)
clockid可以設定為CLOCK_REALTIME CLOCK_MONOTONIC

int timerfd_settime(int fd, int flags, const struct itimerspec *new_value,
                        struct itimerspec *old_value)
new_value設定定時器,old_value獲取定時器前一設定。

int timerfd_gettime(int fd, struct itimerspec *curr_value)
timerfd_gettime獲取定時器到期的剩餘時間

定時器到期後我們可以通過read檔案描述符來獲取定時器到期資訊。
 

 還有方法是使用select來提供精確定時和休眠:

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

 n指監視的檔案描述符範圍,通常設為所要select的fd+1,readfds,writefds和exceptfds分別是讀,寫和異常檔案描述符集,timeout為超時時間。

 可能用到的關於檔案描述符集操作的巨集有:

 FD_CLR(int fd, fd_set *set);    清除fd
 FD_ISSET(int fd, fd_set *set);  測試fd是否設定
 FD_SET(int fd, fd_set *set);     設定fd
 FD_ZERO(fd_set *set);             清空描述符集 

 我們此時用不到這些巨集,因為我們並不關心檔案描述符的狀態,我們關心的是select超時。所以我們需要把readfds,writefds和exceptfds都設為NULL,只指定timeout時間就行了。至於n我們可以不關心,所以你可以把它設為任何非負值。實現程式碼如下:

int usTimer(long us)
{
    struct timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = us;

    return select(0,NULL,NULL,NULL,&timeout);
}

 結語:

     不推薦使用setitimer,Linux系統提供的timer有限(每個程序至多能設3個不同型別的timer),再者ssetitimer實現起來沒有 select簡單。