Linux平台延时之sleep、usleep、nanosleep、select比较

时间:2023-03-09 19:30:37
Linux平台延时之sleep、usleep、nanosleep、select比较
2015-05-05 15:28 369人阅读 评论(0) 收藏 举报
Linux平台延时之sleep、usleep、nanosleep、select比较 分类:
C基础(39) Linux平台延时之sleep、usleep、nanosleep、select比较

Linux平台延时之sleep、usleep、nanosleep、select比较

1、sleep的精度是秒

2、usleep的精度是微妙,不精确

3、select的精度是微妙,精确

struct timevaldelay;

delay.tv_sec =0;

delay.tv_usec =20 * 1000; // 20 ms

select(0, NULL,NULL, NULL, &delay);

4、nanosleep的精度是纳秒,不精确

unix、linux系统尽量不要使用usleep和sleep而应该使用nanosleep,使用nanosleep应注意判断返回值和错误代码,否则容易造成cpu占用率100%。

无论是WinCE还是Linux操作系统,应用线程的运行总是涉及到两个基本的参数:一个是系统分配给线程的时间片,一个是系统调度的时间间隔。Linux和WinCE下这两个参数有所不同,如下表所示:

WinCE

嵌入式Linux

线程的运行时间片

100ms

10ms

系统调度间隔

1ms

10ms

Linux 高精確的時序(sleep, usleep,nanosleep)

原文地址:http://www.linux.org.tw/CLDP/MiniHOWTO/prog/IO-Port-Programming/IO-Port-Programming-4.html

4. 高精確的時序

4.1 延遲時間

首先, 我會說不保證你在使用者模式 (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) 使用說明文件. 有一個特殊的核心支援硬體的即時排程; 詳細的資訊請參考網頁 http://luz.cs.nmt.edu/~rtlinux/

休息中 (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;

}

你可以詢問參考此值以便延遲你想要的時鐘周期數.

4.2 時間的量測

想要時間精確到一秒鐘, 使用函式 time() 或許是最簡單的方法. 想要時間更精確, 函式 gettimeofday() 大約可以精確到微秒 (但是如前所述會受到 CPU 排程的影響). 至於 Pentiums 處理器, 使用上面的程式碼片斷就可以精確到一個時鐘周期.

如果你要你執行中的行程 (process) 在一段時間到了之後能夠被通知 (get a signal), 你得使用函式 setitimer() 或 alarm() . 細節請參考函式的使用說明文件.

--------------------------------------------------

再论精确延时(usleep,nanosleep,select)

/*

make:  gcc -o test_sleeptest_sleep.c

*/

/*       #include  "comm_main.h"*/

#include <stdio.h>;

#include <stdlib.h>;

#include <time.h>;

#include <sys/time.h>;

#include <errno.h>;

#include <string.h>;

#include <unistd.h>;

#include <sys/types.h>;

#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;

intret = 0;

unsigned int nDelay = 0;        /*usec */

fd_set rfds;

struct timeval tv;

intfd = 1;

inti = 0;

struct timespec req;

unsigned int delay[20] =

{500000, 100000, 50000, 10000, 1000, 900, 500, 100, 10, 1, 0 };

intnReduce = 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

由此可以得出如下结论,在 linux2.4 下:

1、支持usleep,nanosleep,select

2、select 的 精确度为 10毫秒。在10毫秒以上很精确

3、usleep,nanosleep  很不精确

同样,通过其它测试程序能得出如下结论:

sleep  可以在多线程中使用,只阻塞本线程,不影响所属进程中的其它线程

我只有以上3种测试环境,有其它测试环境的帮我在其它环境下测试一下,将分析结果贴出来。

测试程序在之后发上来

什么场合下会用到pthread_yield

pthread_yield 使当前的线程自动放弃剩余的CPU时间从而让另一个线程运行,但个人感觉 usleep 足以完成这个功能,到底 pthread_yield 用在什么场合呢?有人在实战项目中用过或者见过使用 pthread_yield 的么

pthread_yield() causes the calling threadto relinquish the CPU. The thread is placed at the end of the run queue for itsstatic priority and another thread is scheduled to run.

个人觉得可以用pthread_yield来减少一些低优先级的实时线程对CPU的消耗,不过需要注意的是,pthread_yield并不能代替usleep(),例如队列检查中,如果发现队列空时采用yield放弃时间片,也仍会导致CPU 100%。

参考链接:与windows中的Sleep(0)等价的pthread_yield

网上有用函数pthread_yield()可能出现的问题。

参考链接:Mutex takes a long while to unlock

========================

我在以前的一个项目中使用过pthread_yield。

pthread_yield可以用于实现同步。当在一个父进程中,开辟了2个子线程,而只有这2个子线程都执行完了之后,父进程才能进行下一步的工作。这个时候,在父进程开启这两个子线程之后,需要调用pthread_yield让父进程等待这2个子线程执行完。