C - select() 似乎阻塞的时间超过超时时间 [英] C - select() seems to block for longer than timeout

查看:112
本文介绍了C - select() 似乎阻塞的时间超过超时时间的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个需要

  • 使用 select() 等待串行

  • wait for serial with select()

读取串行数据(RS232 波特率为 115200),

read serial data (RS232 at 115200 baud),

时间戳(clock_gettime()),

timestamp it (clock_gettime()),

在 SPI 上读取 ADC,

read an ADC on SPI,

解释一下,

通过另一个 tty 设备发送新数据

send new data over another tty device

循环和重复

ADC 现在无关紧要.

The ADC is irrelevant for now.

在循环结束时,我再次使用 select() 以 0 超时来轮询并查看数据是否已经可用,如果是,则意味着我已经溢出,即我希望循环在更多数据之前结束,并且循环开始时的 select() 会阻塞并在它到达时立即获取.

At the end of the loop I use select() again with a 0 timeout to poll and see whether data is available already, if it is it means I have overrun , I.e. I expect the loop to end before more data and for the select() at the start of the loop to block and get it as soon as it arrives.

数据应该每 5ms 到达一次,我的第一个 select() 超时计算为 (5.5ms - 循环时间) - 应该是大约 4ms.

The data should arrive every 5ms, my first select() timeout is calculated as (5.5ms - loop time) - which should be about 4ms.

我没有超时,但有很多超限.

I get no timeouts but many overruns.

检查时间戳发现 select() 阻塞的时间比超时长(但仍然返回>0).看起来 select() 在超时之前获取数据后返回晚了.

Examining the timestamps reveals that select() blocks for longer than the timeout (but still returns>0). It looks like select() returns late after getting data before timeout.

这可能会在 1000 次重复中发生 20 次.可能是什么原因?我该如何解决?

This might happen 20 times in 1000 repeats. What might be the cause? How do I fix it?

这是代码的简化版本(我做了比这更多的错误检查!)

Here is cut down version of the code (I do much more error checking than this!)

#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()
");
            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
", 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!
");
                snprintf(output, 25, ",,,%d.%04d

", ftime.tv_sec, ftime.tv_nsec/100000);
                write(fd_sc, output, strnlen(output, 24)); 
            }

        }

    }


    return 0;

}

我在输出的串行流上看到的时间戳是相当规则的(通常下一个循环会捕捉到偏差).输出片段:

The timestamps I see on the serial stream that I output is fairly regular (a deviation is caught up by the next loop usually). A snippet of output:

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

在这里,最多 6.1922 秒一切正常.下一个样本是 6.2063 - 上一次之后的 14.1 毫秒,但它没有超时,也没有从 6.1922-6.2063 的前一个循环捕获轮询 select() 的溢出.我的结论是,最后一个循环在采样时间范围内,并且 select 在没有超时的情况下返回了 -10ms 太长的时间.

Here, up to 6.1922s everything is OK. The next sample is 6.2063 - 14.1ms after the last, but it didn't time out nor did the previous loop from 6.1922-6.2063 catch the overrun with the polling select(). My conclusion is that the last loop was withing the sampling time and select took -10ms too long return without timing out.

,,,0.0010 表示循环之后的循环时间 (ftime) - 我真的应该检查出错时的循环时间是多少.我明天试试.

The ,,,0.0010 indicates the loop time (ftime) of the loop after - I should really be checking what the loop time was when it went wrong. I'll try that tomorrow.

推荐答案

传递给 select 的超时是一个粗略的下限 — select 允许延迟你的进程略多于此.特别是,如果您的进程被不同的进程(上下文切换)或内核中的中断处理抢占,则会延迟.

The timeout passed to select is a rough lower bound — select is allowed to delay your process for slightly more than that. In particular, your process will be delayed if it is preempted by a different process (a context switch), or by interrupt handling in the kernel.

以下是 Linux 手册页关于该主题的说明:

Here is what the Linux manual page has to say on the subject:

注意超时间隔会向上取整到系统时钟粒度和内核调度延迟意味着阻塞间隔可能会超出少量.

Note that the timeout interval will be rounded up to the system clock granularity, and kernel scheduling delays mean that the blocking interval may overrun by a small amount.

这里是 POSIX 标准:

And here's the POSIX standard:

实施可能还对超时间隔的粒度进行了限制.如果请求的超时间隔需要比实现支持,实际超时间隔为向上取整到下一个支持的值.

Implementations may also place limitations on the granularity of timeout intervals. If the requested timeout interval requires a finer granularity than the implementation supports, the actual timeout interval shall be rounded up to the next supported value.

在通用系统上很难避免这种情况.通过将进程锁定在内存中 (mlockall) 并将进程设置为实时优先级(使用 sched_setscheduler),您将获得合理的结果,尤其是在多核系统上使用 SCHED_FIFO,并记得经常睡眠以让其他进程有机会运行).

Avoiding that is difficult on a general purpose system. You will get reasonable results, especially on a multi-core system, by locking your process in memory (mlockall) and setting your process to a real-time priority (use sched_setscheduler with SCHED_FIFO, and remember to sleep often enough to give other processes a chance to run).

一种更困难的方法是使用专用于运行实时代码的实时微控制器.有些人声称使用该技术在相当便宜的硬件上以 20MHz 可靠采样.

A much more difficult approach is to use a real-time microcontroller that is dedicated to running the real-time code. Some people claim to reliably sample at 20MHz on fairly cheap hardware using that technique.

这篇关于C - select() 似乎阻塞的时间超过超时时间的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆