Linux 高精確的時序(sleep, usleep,nanosleep)
原文地址:
高精確的時序
延遲時間
首先, 我會說不保證你在使用者模式 (user-mode) 中執行的行程 (process) 能夠精確地控制時序因為 Linux
是個多工的作業環境. 你在執行中的行程 (process) 隨時會因為各種原因被暫停大約 10 毫秒到數秒 (在系統負荷非常高的時候).
然而, 對於大多數使用 I/O 埠的應用而言, 這個延遲時間實際上算不了什麼. 要縮短延遲時間, 你得使用函式 nice 將你在執行中的行程
(process ) 設定成高優先權(請參考 nice(2)
使用說明文件) 或使用即時排程法 (real-time scheduling) (請看下面).
如果你想獲得比在一般使用者模式 (user-mode) 中執行的行程 (process) 還要精確的時序,
有一些方法可以讓你在使用者模式 (user-mode) 中做到 `即時' 排程的支援. Linux 2.x
版本的核心中有軟體方式的即時排程支援; 詳細的說明請參考 sched_setscheduler(2)
使用說明文件. 有一個特殊的核心支援硬體的即時排程; 詳細的資訊請參考網頁
休息中 (Sleeping) : sleep()
與 usleep()
現在, 讓我們開始較簡單的時序函式呼叫. 想要延遲數秒的時間, 最佳的方法大概是使用函式 sleep()
. 想要延遲至少數十毫秒的時間 (10 ms 似乎已是最短的延遲時間了), 函式 usleep()
應該可以使用. 這些函式是讓出 CPU 的使用權給其他想要執行的行程 (processes) (``自己休息去了''), 所以沒有浪費掉 CPU 的時間. 細節請參考 sleep(3)
與 usleep(3)
的說明文件.
如果讓出 CPU 的使用權因而使得時間延遲了大約 50 毫秒 (這取決於處理器與機器的速度, 以及系統的負荷), 就浪費掉 CPU
太多的時間, 因為 Linux 的排程器 (scheduler) (單就 x86 架構而言) 在將控制權發還給你的行程 (process)
之前通常至少要花費 10-30 毫秒的時間. 因此, 短時間的延遲, 使用函式 usleep(3)
所得到的延遲結果通常會大於你在參數所指定的值, 大約至少有 10 ms.
nanosleep()
在 Linux 2.0.x 一系列的核心發行版本中, 有一個新的系統呼叫 (system call), nanosleep()
(請參考 nanosleep(2)
的說明文件), 他讓你能夠休息或延遲一個短的時間 (數微秒或更多).
如果延遲的時間 <= 2 ms, 若(且唯若)你執行中的行程 (process) 設定了軟體的即時排程 (就是使用函式 tt/sched_setscheduler()/), 呼叫函式 nanosleep()
時不是使用一個忙碌迴圈來延遲時間; 就是會像函式 usleep()
一樣讓出 CPU 的使用權休息去了.
這個忙碌迴圈使用函式 udelay()
(一個驅動程式常會用到的核心內部的函式) 來達成, 並且使用 BogoMips 值 (BogoMips 可以準確量測這類忙碌迴圈的速度) 來計算迴圈延遲的時間長度. 其如何動作的細節請參考 /usr/include/asm/delay.h
).
使用 I/O 埠來延遲時間
另一個延遲數微秒的方法是使用 I/O 埠. 就是從埠位址 0x80 輸入或輸出任何 byte 的資料 (請參考前面)
等待的時間應該幾乎只要 1 微秒這要看你的處理器的型別與速度. 如果要延遲數微秒的時間你可以將這個動作多做幾次.
在任何標準的機器上輸出資料到該埠位址應該不會有不良的後果纔對 (而且有些核心的設備驅動程式也在使用他). {in|out}[bw]_p()
等函式就是使用這個方法來產生時間延遲的 (請參考檔案 asm/io.h
).
實際上, 一個使用到埠位址範圍為 0-0x3ff 的 I/O 埠指令幾乎只要 1 微秒的時間, 所以如果你要如此做, 例如, 直接使用並列埠, 只要加上幾個 inb()
函式從該埠位址範圍讀入 byte 的資料即可.
使用組合語言來延遲時間
如果你知道執行程式所在機器的處理器型別與時鐘速度, 你可以執行某些組合語言指令以便獲得較短的延遲時間 (但是記住, 你在執行中的行程
(process) 隨時會被暫停, 所以有時延遲的時間會比實際長). 如下面的表格所示, 內部處理器的速度決定了所要使用的時鐘周期數; 如,
一個 50 MHz 的處理器 (486DX-50 或 486DX2-50), 一個時鐘周期要花費 1/50000000 秒 (=200 奈秒).
指令 i386 時鐘周期數 i486 時鐘周期數
nop 3 1
xchg %ax,%ax 3 3
or %ax,%ax 2 1
mov %ax,%ax 2 1
add %ax,0 2 1
(對不起, 我不知道 Pentiums 的資料, 或許與 i486 接近吧. 我無法在 i386 的資料上找到只花費一個時鐘周期的指令. 如果能夠就請使用花費一個時鐘周期的指令, 要不然就使用管線技術的新式處理器也是可以縮短時間的.)
上面的表格中指令 nop
與 xchg
應該不會有不良的後果. 指令最後可能會改變旗號暫存器的內容, 但是這沒關係因為 gcc 會處理. 指令 nop
是個好的選擇.
想要在你的程式中使用到這些指令, 你得使用 asm("instruction")
. 指令的語法就如同上面表格的用法; 如果你想要在單一的 asm()
敘述中使用多個指令, 可以使用分號將他們隔開. 例如, asm("nop ; nop ; nop ; nop")
會執行四個 nop
指令, 在 i486 或 Pentium 處理器中會延遲四個時鐘周期 (或是 i386 會延遲 12 個時鐘周期).
gcc 會將 asm()
翻譯成單行組合語言程式碼, 所以不會有呼叫函式的負荷.
在 Intel x86 架構中不可能有比一個時鐘周期還短的時間延遲.
在 Pentiums 處理器上使用函式 rdtsc
對於 Pentiums 處理器而言, 你可以使用下面的 C 語言程式碼來取得自從上次重新開機到現在經過了多少個時鐘周期:
extern __inline__ unsigned long long int rdtsc()
{
unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
return x;
}
你可以詢問參考此值以便延遲你想要的時鐘周期數.
時間的量測
想要時間精確到一秒鐘, 使用函式 time()
或許是最簡單的方法. 想要時間更精確, 函式 gettimeofday()
大約可以精確到微秒 (但是如前所述會受到 CPU 排程的影響). 至於 Pentiums 處理器, 使用上面的程式碼片斷就可以精確到一個時鐘周期.
如果你要你執行中的行程 (process) 在一段時間到了之後能夠被通知 (get a signal), 你得使用函式 setitimer()
或 alarm()
. 細節請參考函式的使用說明文件.
--------------------------------------------------
再论精确延时(usleep,nanosleep,select)
/*
make: gcc -o test_sleep test_sleep.c
*/
/* #include "comm_main.h" */
#include
;
#include ;
#include ;
#include ;
#include ;
#include ;
#include ;
#include ;
#define PRINT_USEAGE { \
fprintf(stderr,"\n Usage: %s usec ",argv[0]); \
fprintf(stderr,"\n\n";\
}
int
main (int argc, char **argv)
{
unsigned int nTimeTestSec = 0; /* sec */
unsigned int nTimeTest = 0; /* usec */
struct timeval tvBegin;
struct timeval tvNow;
int ret = 0;
unsigned int nDelay = 0; /* usec */
fd_set rfds;
struct timeval tv;
int fd = 1;
int i = 0;
struct timespec req;
unsigned int delay[20] =
{ 500000, 100000, 50000, 10000, 1000, 900, 500, 100, 10, 1, 0 };
int nReduce = 0; /* 误差 */
#if 0
if (argc < 2)
{
PRINT_USEAGE;
exit (1);
}
nDelay = atoi (argv[1]);
#endif
fprintf (stderr, "%18s%12s%12s%12s\n", "function", "time(usec)", "realTime",
"reduce";
fprintf (stderr,
"-------------------------------------------------------------------\n";
for (i = 0; i < 20; i++)
{
if (delay <= 0)
break;
nDelay = delay;
/* test usleep */
gettimeofday (&tvBegin, NULL);
ret = usleep (nDelay);
if (-1 == ret)
{
fprintf (stderr, " usleep error . errno=%d [%s]\n", errno,
strerror (errno));
}
gettimeofday (&tvNow, NULL);
nTimeTest =
(tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec -
tvBegin.tv_usec;
nReduce = nTimeTest - nDelay;
fprintf (stderr, "\t usleep %8u %8u %8d\n", nDelay, nTimeTest,
nReduce);
/* test nanosleep */
gettimeofday (&tvBegin, NULL);
req.tv_sec = nDelay / 1000000;
req.tv_nsec = (nDelay % 1000000) * 1000;
ret = nanosleep (&req, NULL);
if (-1 == ret)
{
fprintf (stderr, "\t nanosleep %8u not support\n", nDelay);
}
else
{
gettimeofday (&tvNow, NULL);
nTimeTest =
(tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec -
tvBegin.tv_usec;
nReduce = nTimeTest - nDelay;
fprintf (stderr, "\t nanosleep %8u %8u %8d\n", nDelay,
nTimeTest, nReduce);
}
/* test select */
gettimeofday (&tvBegin, NULL);
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
tv.tv_sec = 0;
tv.tv_usec = nDelay;
ret = select (0, NULL, NULL, NULL, &tv);
if (-1 == ret)
{
fprintf (stderr, " select error . errno=%d [%s]\n", errno,
strerror (errno));
}
gettimeofday (&tvNow, NULL);
nTimeTest =
(tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec -
tvBegin.tv_usec;
nReduce = nTimeTest - nDelay;
fprintf (stderr, "\t select %8u %8u %8d\n", nDelay, nTimeTest,
nReduce);
}
return 0;
}
测试
IBM AIX 3.4 单CPU
sleep 可以在多线程中使用,只阻塞本线程,不影响所属进程中的其它线程
不支持 nanosleep
支持 usleep 和 select
以下采用 gettimeofday 对 usleep 和 select 的实际精确情况进行测试分析
function time(usec) realTime reduce
-------------------------------------------------------------------
usleep 500000 500026 26
nanosleep 500000 not support
select 500000 500026 26
usleep 100000 100021 21
nanosleep 100000 not support
select 100000 100025 25
usleep 50000 50021 21
nanosleep 50000 not support
select 50000 50107 107
usleep 10000 10099 99
nanosleep 10000 not support
select 10000 10025 25
usleep 1000 1021 21
nanosleep 1000 not support
select 1000 1024 24
usleep 900 920 20
nanosleep 900 not support
select 900 1024 124
usleep 500 523 23
nanosleep 500 not support
select 500 1024 524
usleep 100 119 19
nanosleep 100 not support
select 100 1023 923
usleep 10 31 21
nanosleep 10 not support
select 10 1024 1014
usleep 1 19 18
nanosleep 1 not support
select 1 1026 1025
由此可以得出,在AIX 3.4下:
select 只能精确到毫秒级别
usleep 可以精确到微秒级
在1毫秒以上,两者的精确度基本一样
同上,在 linux 2.4.20-8smp 双CPU 下测试
function time(usec) realTime reduce
-------------------------------------------------------------------
usleep 500000 506453 6453
nanosleep 500000 509930 9930
select 500000 499990 -10
usleep 100000 110023 10023
nanosleep 100000 109955 9955
select 100000 99992 -8
usleep 50000 59971 9971
nanosleep 50000 59990 9990
select 50000 50025 25
usleep 10000 19991 9991
nanosleep 10000 19988 9988
select 10000 9956 -44
usleep 1000 19990 18990
nanosleep 1000 19989 18989
select 1000 10024 9024
usleep 900 20009 19109
nanosleep 900 19972 19072
select 900 9943 9043
usleep 500 19975 19475
nanosleep 500 19971 19471
select 500 10012 9512
usleep 100 19975 19875
nanosleep 100 19976 19876
select 100 9943 9843
usleep 10 19988 19978
nanosleep 10 19961 19951
select 10 10011 10001
usleep 1 19978 19977
nanosleep 1 19985 19984
select 1 9932 9931
在 2.4.21-4.ELsmp #1 SMP 4 CPU 下测试
function time(usec) realTime reduce
-------------------------------------------------------------------
usleep 500000 501267 1267
nanosleep 500000 509964 9964
select 500000 499981 -19
usleep 100000 109944 9944
nanosleep 100000 109925 9925
select 100000 99963 -37
usleep 50000 59904 9904
nanosleep 50000 59973 9973
select 50000 49956 -44
usleep 10000 19988 9988
nanosleep 10000 20008 10008
select 10000 10020 20
usleep 1000 19988 18988
nanosleep 1000 19980 18980
select 1000 9943 8943
usleep 900 19975 19075
nanosleep 900 19986 19086
select 900 9905 9005
usleep 500 19989 19489
nanosleep 500 19910 19410
select 500 10000 9500
usleep 100 19355 19255
nanosleep 100 19902 19802
select 100 9988 9888
usleep 10 19977 19967
nanosleep 10 19988 19978
select 10 9943 9933
usleep 1 20007 20006
nanosleep 1 19947 19946
select 1 9980 9979
由此可以得出如下结论,在 linux 2.4 下:
1、支持 usleep,nanosleep,select
2、select 的 精确度为 10毫秒。在10毫秒以上很精确
3、usleep, nanosleep 很不精确
同样,通过其它测试程序能得出如下结论:
sleep 可以在多线程中使用,只阻塞本线程,不影响所属进程中的其它线程
我只有以上3种测试环境,有其它测试环境的帮我在其它环境下测试一下,将分析结果贴出来。
测试程序在之后发上来
阅读(5883) | 评论(0) | 转发(0) |