gpt4 book ai didi

C - select() 似乎阻塞的时间比超时时间长

转载 作者:行者123 更新时间:2023-12-01 23:15:01 24 4
gpt4 key购买 nike

我正在写一个数据采集程序,需要

  • 使用 select() 等待串行

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

  • 为其添加时间戳 (clock_gettime()),

  • 读取 SPI 上的 ADC,

  • 解释它,

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

  • 循环和重复

ADC 目前无关紧要。

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

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

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

检查时间戳表明 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.1922s 一切正常。下一个样本是 6.2063 - 比上一个样本晚 14.1 毫秒,但它没有超时,之前的 6.1922-6.2063 循环也没有用轮询 select() 捕获溢出。我的结论是,最后一个循环在采样时间内,select 花费了 -10ms 太长的返回时间而没有超时。

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

最佳答案

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

这是 Linux 手册页关于这个主题的内容:

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 标准:

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,并记住要经常休眠以便让其他进程有机会运行)。

一种更困难的方法是使用专用于运行实时代码的实时微 Controller 。有人声称reliably sample at 20MHz on fairly cheap hardware使用该技术。

关于C - select() 似乎阻塞的时间比超时时间长,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42347283/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com