Chinaunix首页 | 论坛 | 博客
  • 博客访问: 533048
  • 博文数量: 95
  • 博客积分: 1415
  • 博客等级: 上尉
  • 技术积分: 1202
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-20 01:23
文章分类

全部博文(95)

文章存档

2010年(28)

2009年(67)

我的朋友

分类: LINUX

2009-08-25 09:53:17

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 的資料上找到只花費一個時鐘周期的指令. 如果能夠就請使用花費一個時鐘周期的指令, 要不然就使用管線技術的新式處理器也是可以縮短時間的.)

上面的表格中指令 nopxchg 應該不會有不良的後果. 指令最後可能會改變旗號暫存器的內容, 但是這沒關係因為 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种测试环境,有其它测试环境的帮我在其它环境下测试一下,将分析结果贴出来。
测试程序在之后发上来
阅读(5874) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~