带有 TCSADRAIN 标志的 tcsetattr() 阻塞时间很奇怪

tcsetattr() blocking time with TCSADRAIN flag is weird

本文关键字:时间 标志 TCSADRAIN tcsetattr 带有      更新时间:2023-10-16

我在Linux中使用串行端口编码。

并且通信的要求是5ms inter-byte time.

它要求我在调用之前根据字节的值更改每个字节write()奇偶模式(偶数和奇数(。

所以我像下面这样编码(我简单地描述代码(

void setWakeupMode(int fd, bool mode) {
    struct termios tio;
    bzero(&tio, sizeof(tio));
    tcgetattr(fd, &tio);

    if (mode == false) {
        tio.c_cflag &= ~PARODD;
    } else if (mode == true) {
        tio.c_cflag |= PARODD;
    }
    if(tcsetattr(fd, TCSADRAIN, &tio) < 0){
        perror("tcsetattr Error");
    }
}
int main(){
    unsigned char a[2] = {0x01, 0x0F};
    write(fd, a, 1);
    setWakeupMode(fd, true);
    write(fd, a+1, 1);
}

但是代码不能满足字节间时间,导致近 20 毫秒。

所以我尝试打印每个系统调用之间的确切时间,如下所示。

   int main(){
        unsigned char a[2] = {0x01, 0x0F};
        printf("write1 start : %s", /*time*/);
        write(fd, a, 1);
        printf("write1 end  : %s", /*time*/); 
        setWakeupMode(fd, true);
        printf("write2 start : %s", /*time*/);
        write(fd, a+1, 1);
        printf("write2 end : %s, /*time*/);
    }

这就是结果

write1 start : 34.755201
write1 end   : 34.756046
write2 start : 34.756587  
write2 end   : 34.757349  

这个结果突然满足5ms的字节间时间,导致1ms inter-byte time

所以我尝试了几种方法。

最后,我认识到,只有当我在 tcsetattr(( 之前打印一些东西时,字节间时间才得到满足。

例如,如果我删除如下所示printf("write1 end : %s, /*time*/);

   int main(){
        unsigned char a[2] = {0x01, 0x0F};
        printf("write1 start : %s", /*time*/);
        write(fd, a, 1);
        // printf("write1 end  : %s", /*time*/);  //remove this 
        setWakeupMode(fd, true);
        printf("write2 start : %s", /*time*/);
        write(fd, a+1, 1);
        printf("write2 end : %s", /*time*/);
    }

结果出奇的不同,看write1 startwrite2 start之间的间隔,这是18ms.

write1 start : 40.210111
write2 start : 40.228332
write2 end   : 40.229187

如果我使用 std::cout 而不是 printf,结果是相同的。

为什么会发生这种奇怪的情景?

-------------------------------编辑--------------------------------

由于我看到了一些答案,有些人误解了我的问题。

我不担心开销printf()

简单地说。

  1. 我想用 1 字节调用 write() s,但 write() s 之间的间隔必须在 5ms 以内
  2. 在调用write()之前,我必须使用tcsetattr()更改奇偶校验模式
  3. 但间隔结果是18ms,几乎tcsetattr()时间被阻止。
  4. 但是如果我在tcsetattr()之前打电话给printf()std::cout,间隔减少到1~2ms

也就是说,不知何故,在tcsetattr()之前调用 printf tcsetattr()更快地从阻塞中返回。

--------------------------更新----------------------------

我在这个问题上取得了进展。

说我必须放printf()std::cout,以使阻塞时间缩短tcsetattr()

但它并没有打印一些东西来影响这个问题。

它只是需要一些延迟,也就是说,如果我在调用tcsetattr()之前放usleep(500),它也会对字节间时间减少1~2ms产生影响,从tcsetattr()更快地返回。

我假设,如果我用TCSADRAIN标志调用tcsetattr(),它会等到串行缓冲区中的所有数据都传输完毕,然后更改为我想要的设置。

它可能会造成一些延迟。

但是如果我专门调用延迟,在我调用tcsetattr()之前,缓冲区状态已经是空的(因为在延迟时间内,串行缓冲区中的数据被传输(,因此没有阻塞。

这是我假设的情况,可能吗?

这是我假设的情况,可能吗?

你一定是对的。 锯末 写道:

uart_wait_until_sent(( in drivers/tty/serial/serial_core.c 解释了为什么当涉及"耗尽"时,字符之间的间隔会被量化:串行端口驱动程序使用仅毫秒分辨率的延迟轮询TIOCSER_TEMP(发射器空(状态。

这几乎是对的,除了轮询周期分辨率不是毫秒,而是 jiffies:

         while (!port->ops->tx_empty(port)) {
                 msleep_interruptible(jiffies_to_msecs(char_time));
                 …

因此,如果 HZ 为 100,char_time为 1(最小值(,则每 10 毫秒检查一次状态。正如你所写:

但是如果我打电话专门延迟,在我打电话给tcsetattr()之前, 缓冲区状态已经为空(因为在延迟时间内,数据 在串行缓冲区中被传输(,因此没有阻塞。

换句话说,如果你延迟这个过程的时间足够长,以至于当它到达发射机空测试时,!port->ops->tx_empty(port)发射机已经是空的,就不会发生睡眠;否则它至少会休眠 1 分钟。

你为什么不试试 sprintf/snprintf 而不是 printf??因为关于snprintf,你可以加快时间,如果你缩短了字符串字节?