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

San*_*Kim 5 c c++ linux serial-port

我在 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);

}
Run Code Online (Sandbox Code Playgroud)

但是代码不满足字节间时间,导致近 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*/);
    }
Run Code Online (Sandbox Code Playgroud)

这是结果

write1 start : 34.755201
write1 end   : 34.756046
write2 start : 34.756587  
write2 end   : 34.757349  
Run Code Online (Sandbox Code Playgroud)

这个结果突然满足了 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*/);
    }
Run Code Online (Sandbox Code Playgroud)

其结果是令人惊讶的不同,请参阅之间的间隔write1 startwrite2 start,这是18ms

write1 start : 40.210111
write2 start : 40.228332
write2 end   : 40.229187
Run Code Online (Sandbox Code Playgroud)

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

为什么会出现这种奇怪的情况?

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

因为我看到了一些答案,所以有些人误解了我的问题。

我不担心printf()开销。

简单地说。

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

也就是说,不知何故,在调用之前的printftcsetattr()化妆tcsetattr()回报堵塞得更快。

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

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

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

但它并没有打印影响该问题的内容。

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

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

它可能会延迟一些。

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

这是我假设的场景,可能吗?

Arm*_*ali 3

\n

这是我假设的场景,可能吗?

\n
\n\n

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

\n\n
\n

uart_wait_until_sent()drivers/tty/serial/serial_core.c了为什么在涉及“耗尽”时对字符之间的间隔进行量化:使用仅毫秒分辨率的延迟来轮询串行端口驱动程序以获取 TIOCSER_TEMP(发送器空)状态。

\n
\n\n

这几乎是正确的,只是轮询周期分辨率不是毫秒,而是 jiffies:

\n\n
         while (!port->ops->tx_empty(port)) {\n                 msleep_interruptible(jiffies_to_msecs(char_time));\n                 \xe2\x80\xa6\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
\n

但如果我打电话特意延迟,在我打电话之前tcsetattr(),缓冲区状态已经为空(因为在延迟时间内,串行缓冲区中的数据被传输),因此不存在阻塞。

\n
\n\n

换句话说,如果您将过程延迟足够长的时间,那么当它到达发射器空测试时!port->ops->tx_empty(port)发射器已经是空的,则不会发生睡眠;否则它会休眠至少 1 jiffy。

\n