2017-02-20 51 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?