Linux中提供的休眠函數是sleep(),但是僅僅提供以秒為單位的休眠,這種休眠對有些進程顯然太長了,那麼怎樣才能使進程以更小的時間分辨率休眠呢?
我知道的方法有2種,下面就做分別介紹。
第一種方法是使用定時器,Linux提供的定時器函數是:
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);which指定那種定時器。Linux提供3種定時器:
TIMER_REAL: 准確定時器,超時會發出SIGALRM信號;
TIMER_VIRTUAL: 虛擬定時器,只記進程時間,所以會根據進程執行時間而變化,不能實現准確定時,超時發出SIGVTALRM信號;
TIMER_PROF: 梗概計時器,它會根據進程時間和系統時間而變化,不能實現准確定時,超時發出SIGPROF信號;
在進程中應該捕捉所設定時器會發出的信號,因為進程收到定時器超時發出的信號後,默認動作是終止。
value是設置定時器時間,相關結構如下:
struct itimerval { struct timeval it_interval; struct timeval it_value;};struct timeval { long tv_sec; long tv_usec;};it_interval 指定間隔時間,it_value指定初始定時時間。如果只指定it_value,就是實現一次定時;如果同時指定it_interval,則超時後,系統會重新初始化it_value為it_interval,實現重復定時;兩者都清零,則會清除定時器。
tv_sec提供秒級精度,tv_usec提供微秒級精度,以值大的為先,注意1s = 1000000us。
ovalue用來保存先前的值,常設為NULL。
如果是以setitimer()提供的定時器來休眠,只需用pause()阻塞等待定時器信號就可以了。第二種方法是使用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); 清除fdFD_ISSET(int fd, fd_set *set); 測試fd是否設置FD_SET(int fd, fd_set *set); 設置fdFD_ZERO(fd_set *set); 清空描述符集我們此時用不到這些宏,因為我們並不關心文件描述符的狀態,我們關心的是select()超時。所以我們需要把readfds,writefds和exceptfds都設為NULL,只指定timeout時間就行了。至於n我們可以不關心,所以你可以把它設為任何非負值。實現代碼如下:
int usSleep(long us) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = us; return select(0, NULL, NULL, NULL, &tv);}呵呵,怎麼樣,是不是很簡單?
結語:
setitimer() 和select()都能實現進程的精確休眠,本文分別對他們進行了簡單介紹,並給出了一個簡單的基於select()的實現。我不推薦使用 setitimer,因為一者Linux系統提供的timer有限(每個進程至多能設3個不同類型的timer),再者setitimer()實現起來沒有select()簡單。
編輯於2008年9月12日
-------------------------------------------------------------------
非常感謝33和ffff的反饋,ms與us的錯誤已經更正。由於小弟學藝不精,給大家造成的誤解多原諒,另外也歡迎提出你的見解,我們一起交流進步。我已經把標題改了,原來的標題已經不適合現在的內容,因為我想把其他sleep函數也添加進來。首先,就如ffff提出的那樣,用usleep()可以實現進程的秒以下休眠。
int usleep(unsigned long usec);同sleep()一樣,它在進程收到任何信號時都會返回。不過Linux手冊上說,它並不會像sleep()一樣會返回沒休眠的秒數,而是返回-1,並把errno設為EINTR。 uClibc的實現如下:
int usleep (__useconds_t usec){ const struct timespec ts = { .tv_sec = (long int) (usec / 1000000), .tv_nsec = (long int) (usec % 1000000) * 1000ul }; return(nanosleep(&ts, NULL));}可見usleep()內部是由nanosleep()函數實現的,接下來我們會討論nanosleep()函數。我還見過如下用select()實現的,也支持了我前面所說的方法。
int usleep( unsigned long usec ){ static struct { /* `timeval' */ long tv_sec; /* seconds */ long tv_usec; /* microsecs */ } delay; /* _select() timeout */ delay.tv_sec = usec / 1000000L; delay.tv_usec = usec % 1000000L; return select(0, (long *)0, (long *)0, (long *)0, &delay);}接下來我們再來看看nanosleep()。nanosleep()可以實現納秒(1/1000 000 000)級的休眠,所以它比usleep()更精確。但是你的CPU至少要1GHz以上才能支持這麼高的分辨率。nanosleep()的原型如下:
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);第一個參數指定要休眠的時間,第二個參數返回被信號中斷時還沒有休眠夠的時間,timespec結構如下:
struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */};tv_sev 用於指定秒數,tv_nsec用於指定納秒數(0-999 999 999),注意不像前面所說的其它函數,nanosleep()對timespec結構的2個成員都要使用,所以你不能為tv_nsec指定大於999 999 999的數值,大於此數的應該送給tv_sec。所有的sleep函數遇信號都會返回,sleep()和alarm()遇信號返回剩余的時間,而usleep(),nanosleep()和 select()遇信號都會返回-1。