2017-02-20 54 views
0

我寫在需要Ç - 選擇()似乎阻塞長於超時

  • 等待串行使用select()

  • 讀取串行數據(RS232數據採訪節目115200波特),

  • 時間戳它(clock_gettime()),

  • 讀取的ADC上SPI,

  • 解釋它,

  • 在另一個tty設備

  • 循環發送新數據,並重復

的ADC是無關緊要的了。

在循環結束時,我再次使用select()和0超時輪詢並查看數據是否已經可用,如果這意味着我有溢出,即,即。我期望循環在更多數據之前結束,並且在循環開始時select()會在循環開始時阻塞並在它到達時立即獲取它。

數據應該每隔5ms到達一次,我的第一個select()超時計算爲(5.5ms - 循環時間) - 應該是4ms。

我沒有超時但很多超時。

檢查時間戳顯示select()阻塞超過超時(但仍返回> 0)。 它看起來像select()在超時之前獲取數據後返回。

1000次重複可能發生20次。 可能是什麼原因?我如何解決它?

編輯: 這裏被砍倒的代碼版本(!我做更多的錯誤檢查比這個)

#include <bcm2835.h> /* for bcm2835_init(), bcm2835_close() */ 

int main(int argc, char **argv){ 

    int err = 0; 

    /* Set real time priority SCHED_FIFO */ 
    struct sched_param sp; 
    sp.sched_priority = 30; 
    if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp)){ 
     perror("pthread_setschedparam():"); 
     err = 1; 
    } 

    /* 5ms between samples on /dev/ttyUSB0 */ 
    int interval = 5; 

    /* Setup tty devices with termios, both totally uncooked, 8 bit, odd parity, 1 stop bit, 115200baud */ 
    int fd_wc=setup_serial("/dev/ttyAMA0"); 
    int fd_sc=setup_serial("/dev/ttyUSB0"); 

    /* Setup GPIO for SPI, SPI mode, clock is ~1MHz which equates to more than 50ksps */ 
    bcm2835_init(); 
    setup_mcp3201spi(); 

    int collecting = 1; 

    struct timespec starttime; 
    struct timespec time; 
    struct timespec ftime; 
    ftime.tv_nsec = 0; 

    fd_set readfds; 
    int countfd; 
    struct timeval interval_timeout; 
    struct timeval notime; 

    uint16_t p1; 
    float w1; 

    uint8_t *datap = malloc(8); 
    int data_size; 
    char output[25]; 

    clock_gettime(CLOCK_MONOTONIC, &starttime); 

    while (!err && collecting){ 
     /* Set timeout to (5*1.2)ms - (looptime)ms, or 0 if looptime was longer than (5*1.2)ms */ 
     interval_timeout.tv_sec = 0; 
     interval_timeout.tv_usec = interval * 1200 - ftime.tv_nsec/1000; 
     interval_timeout.tv_usec = (interval_timeout.tv_usec < 0)? 0 : interval_timeout.tv_usec; 
     FD_ZERO(&readfds); 
     FD_SET(fd_wc, &readfds);  
     FD_SET(0, &readfds); /* so that we can quit, code not included */ 
     if ((countfd=select(fd_wc+1, &readfds, NULL, NULL, &interval_timeout))<0){ 
      perror("select()"); 
      err = 1; 
     } else if (countfd == 0){ 
      printf("Timeout on select()\n"); 
      fflush(stdout); 
      err = 1; 
     } else if (FD_ISSET(fd_wc, &readfds)){ 
      /* timestamp for when data is just available */ 
      clock_gettime(CLOCK_MONOTONIC, &time) 
      if (starttime.tv_nsec > time.tv_nsec){ 
       time.tv_nsec = 1000000000 + time.tv_nsec - starttime.tv_nsec; 
       time.tv_sec = time.tv_sec - starttime.tv_sec - 1; 
      } else { 
       time.tv_nsec = time.tv_nsec - starttime.tv_nsec; 
       time.tv_sec = time.tv_sec - starttime.tv_sec; 
      } 

      /* get ADC value, which is sampled fast so corresponds to timestamp */ 
      p1 = getADCvalue(); 

      /* receive_frame, receiving is slower so do it after getting ADC value. It is timestamped anyway */ 
      /* This function consists of a loop that gets data from serial 1 byte at a time until a 'frame' is collected. */ 
      /* it uses select() with a very short timeout (enough for 1 byte at baudrate) just to check comms are still going */ 
      /* It never times out and behaves well */ 
      /* The interval_timeout is passed because it is used as a timeout for responding an ACK to the device */ 
      /* That select also never times out */ 
      ireceive_frame(&datap, fd_wc, &data_size, interval_timeout.tv_sec, interval_timeout.tv_usec); 

      /* do stuff with it */ 
      /* This takes most of the time in the loop, about 1.3ms at 115200 baud */ 
      snprintf(output, 24, "%d.%04d,%d,%.2f\n", time.tv_sec, time.tv_nsec/100000, pressure, w1); 
      write(fd_sc, output, strnlen(output, 23)); 

      /* Check how long the loop took (minus the polling select() that follows */ 
      clock_gettime(CLOCK_MONOTONIC, &ftime); 
      if ((time.tv_nsec+starttime.tv_nsec) > ftime.tv_nsec){ 
       ftime.tv_nsec = 1000000000 + ftime.tv_nsec - time.tv_nsec - starttime.tv_nsec; 
       ftime.tv_sec = ftime.tv_sec - time.tv_sec - starttime.tv_sec - 1; 
      } else { 
       ftime.tv_nsec = ftime.tv_nsec - time.tv_nsec - starttime.tv_nsec; 
       ftime.tv_sec = ftime.tv_sec - time.tv_sec - starttime.tv_sec; 
      } 

      /* Poll with 0 timeout to check that data hasn't arrived before we're ready yet */ 
      FD_ZERO(&readfds); 
      FD_SET(fd_wc, &readfds); 
      notime.tv_sec = 0; 
      notime.tv_usec = 0; 
      if (!err && ((countfd=select(fd_wc+1, &readfds, NULL, NULL, &notime)) < 0)){ 
       perror("select()"); 
       err = 1; 
      } else if (countfd > 0){ 
       printf("OVERRUN!\n"); 
       snprintf(output, 25, ",,,%d.%04d\n\n", ftime.tv_sec, ftime.tv_nsec/100000); 
       write(fd_sc, output, strnlen(output, 24)); 
      } 

     } 

    } 


    return 0; 

} 

時間戳我的串行流看到我的輸出是相當規則(偏差通常會被下一個循環追上)。一段輸出:

6.1810,0,225.25 
6.1867,0,225.25 
6.1922,0,225.25 
6,2063,0,225.25 
,,,0.0010 

在這裏,一切都很好。下一個樣例是6.2063 - 最後一個14.1ms,但它沒有超時,6.1922-6.2063的前一個循環也沒有超出輪詢select()。我的結論是,最後一個循環與採樣時間有關,並且選擇花費了-10毫秒太長時間返回而沒有超時。

,,, 0.0010表示循環之後的循環時間(ftime) - 我真的應該檢查循環時間是什麼時候出錯的。我明天會試試。

+0

你是如何設置你的掩碼?,並且你是否爲time參數傳遞一個空指針,或者將struct的值設置爲零。每個都會導致它自己的行爲。 – ryyker

+0

您可能需要包含一個_ [SSCCE](http://sscce.org/)_來進一步說明問題。顯然,超時問題可能會導致您的問題。這也將吸引更多的觀衆。 – ryyker

+0

將Raspberry-pi添加到標籤。 – ryyker

回答

1

超時傳遞到select是一個粗略的下限 - select被允許延遲您的過程略多於此。特別是,如果進程被一個不同的進程(上下文切換)搶佔,或者通過內核中的中斷處理,你的進程將被延遲。

這裏是在Linux手冊頁有話要說:

注意超時間隔將被四捨五入爲系統時鐘 粒度和內核調度延遲意味着阻塞 間隔可能會超出一小部分。

而這裏的POSIX標準:

實現可以 也對超時間隔的粒度限制。如果 請求的超時時間間隔要求實現支持的粒度要比 更精細,則實際的超時時間間隔應爲 四捨五入到下一個支持的值。

在通用系統上避免這種情況很困難。通過將進程鎖定在內存中(mlockall),並將進程設置爲實時優先級(使用sched_setschedulerSCHED_FIFO,並記住經常進入睡眠狀態以使其他進程有效),您將獲得合理結果,特別是在多核系統上。運行機會)。

一個更困難的方法是使用專用於運行實時代碼的實時微控制器。有些人聲稱使用該技術reliably sample at 20MHz on fairly cheap hardware

+0

我讀過了,因此我的評論詢問了關於ryykers的搶先答案。如果發生這種情況,select()是不是會超時,而只是返回遲到?而且真的會有這麼多事情發生,超過5ms?我已經設置SCHED_FIFO已經沒有什麼區別了。我明天會嘗試mlockall(即使我使用的內存很少)。我根本沒有睡覺,但我一直以爲只是在select()中花費時間也是一樣。我只在這裏嘗試了250samples/sec –

+0

是的,我寫了一篇寫作延遲時間爲10ms的驅動程序,對此我感到驚訝。看起來像RPi上的SPI驅動程序有一些問題 - 請參閱https://www.raspberrypi.org/forums/viewtopic.php?t=19489 – jch

+0

我正在使用bcm2835庫(http://www.airspayce。 com/mikem/bcm2835 /)沒有相同的問題(我相信)。明天我會嘗試不讀取ADC,儘管我已經看到更多的報告200個樣本/秒。我將盡快用一些代碼編輯我的問題。 –

1

如果struct timeval的值設置爲零,那麼select不會阻塞,但如果超時參數是NULL指針,它將...

如果超時參數不是NULL指針,它指向類型timeval結構的目的,指定的最大時間間隔爲 等待用於選擇來完成。如果超時參數指向 成員爲0的struct timeval類型的對象,則select()不會阻止 。如果超時參數爲NULL指針,則選擇() 塊,直到某個事件導致其中一個掩碼以 返回有效(非零)值或者直到發生需要爲 的信號爲止。如果之前的任何事件發生時 會導致掩膜之一被設置爲非零值的時間限制到期,選擇() 成功完成並返回0

更多here

編輯來解決評論,並添加新信息:

一些值得注意的要點。

第一個 - 在評論中,有一個建議將sleep()添加到您的工作循環中。這是一個很好的建議。原因stated here,儘管處理線程入口點,仍然適用,因爲你正在實例化一個連續的循環。

- Linux的select()是一個系統調用一個有趣的implemantation歷史,因此具有範圍從實現而變化的行爲,其中一些可能有助於您所看到的意外行爲。我不知道它的Linux的主要血線Arch Linux從何而來,但man7.org page for select()包括以下兩個部分,其中按你的描述似乎來形容那些可能有助於延遲您所遇到的條件。

校驗和錯誤:

Under Linux, select() may report a socket file descriptor as "ready 
for reading", while nevertheless a subsequent read blocks. This could 
for example happen when data has arrived but upon examination has wrong 
checksum and is discarded. 

競爭條件:(介紹和討論PSELECT())

...Suppose the signal handler sets a global flag and returns. Then a test 
of this global flag followed by a call of select() could hang indefinitely 
if the signal arrived just after the test but just before the call... 

鑑於你的意見的說明,並根據如何您的版本Linux被實現,其中一個實現功能可能是一個可能的貢獻者。

+0

我想你錯了,但超時設置爲0它不會阻止。這就是我期待的,以及我在循環結束時的第二個select()上得到的結果。問題是第一個select()的超時時間爲非零。它返回的時間比超時(有時5ms)晚得多,而沒有超時。我假設數據在超時之前到達,但無法確認。 –

+0

@StefanHartman - 是的,謝謝。我剛剛發現了這種錯誤類型。它相信現在是正確的。檢查你是否正確設置了口罩。我認爲這一切都發生在一個單線程的應用程序? – ryyker

+0

是的,單線程。掩碼和超時設置和每個循環重複正確重置。是否有可能在select()期間某個時間線程被搶佔,以便在沒有超時的情況下返回遲到?這是一個覆盆子pi。 –

0

對不起,我不能評論(沒有足夠的觀點),但你有多少fd? 你確定你在select()中設置了fd_max + 1嗎?我曾經犯過這樣的錯誤。 你發什麼數據? select只是告訴你它是否可用於讀取,但是他可能超出了fifo?