在某些时候串行 POSIX 写入文件描述符似乎会停止程序

Posted

技术标签:

【中文标题】在某些时候串行 POSIX 写入文件描述符似乎会停止程序【英文标题】:Serial POSIX write to the file descriptor at some point seems to halt the program 【发布时间】:2021-11-02 00:22:59 【问题描述】:

我有两个不同的程序,一个通过mq_send 写入消息队列,而另一个通过mq_receive 使用它。

我遇到的一个问题是Consumer 线程中的串行写入似乎在写入串行端口一段时间后导致挂起。

消费者线程在似乎挂起之前能够接收 51 次。当我注释掉串行写入部分时,程序运行良好,没有任何中断。

有趣的是,在修改MAX_MSG_SIZE 值后,我能够收到或多或少的消息。此外,man page 仅谈论接收缓冲区大小大于mq_msgsize

这是否意味着串行端口以某种方式阻塞,特别是如果串行端口的接收端没有读取数据?

我没有看到任何函数出现任何错误。

在实际代码中,生产者根据定时器中定义的特定时间间隔写入消息队列,并且存在重叠时间段,其中消息以非常接近的优先级写入。

// common.h

#define MAX_MESSAGES                            10
#define MAX_MSG_SIZE                            72
#define MSG_BUFFER_SIZE                         MAX_MSG_SIZE + 10
#define QUEUE_PERMISSIONS                       0660

#define COMMON_QUEUE                            "/MSG_QUEUE"

typedef enum

    LEFT,
    RIGHT
 CmdType;

typedef struct

    CmdType cmd;
    uint16_t length;
    uint8_t payload[64];
 Pkt;

消费者

#include "common.h"

int fd;  // file descriptor for serial port

static struct mq_attr attr = .mq_flags = 0, .mq_maxmsg = MAX_MESSAGES, .mq_msgsize = MAX_MSG_SIZE, .mq_curmsgs = 0;

void *Consumer(void *args)
   
    uint8_t rcvBuffer[MSG_BUFFER_SIZE];
    mqd_t *mqd = (mqd_t *) args;
    int ret;

    while(1)
       
        ret = mq_receive(*mqd, rcvBuffer, sizeof(rcvBuffer), 0);
        if (ret == -1)
        
            perror ("mq_receive failed");
        
        printf ("Received data: %d\n", *rcvBuffer);

        // serial write 
        int sizeWritten = write(fd, rcvBuffer, sizeof(rcvBuffer));
        if (sizeWritten < 0)
         
            perror ("Write to serial failed");
        
    


int UartInit()

    fd = open("/dev/ttyUSB4", O_RDWR); 

    if (fd == -1)
    
        perror ("Serial port failed to open");
        return -1;
    

    if(tcgetattr(fd, &tty) != 0)    
    
      perror("Error from tcgetattr");
      return -1;
    

    cfsetspeed(tty, B115200);     // baud rate to 115200

    tty->c_cflag |= CS8;

    tty->c_cflag &= ~CSTOPB;

    tty->c_cflag |= CRTSCTS;       // HW flow control

    tty->c_cflag &= ~PARENB;

    tty.c_iflag |= ICRNL;                // enable translating carriage return to newline on input
    tty.c_iflag |= IGNBRK;               // ignore break condition
    tty.c_iflag &= ~BRKINT;
    tty.c_iflag &= ~INPCK;               // disable input parity check
    tty.c_iflag &= ~PARMRK;             // ignore input bytes with parity or framing errors are marked when passed to the program
    tty.c_iflag &= ~ISTRIP;             // ignore stripping off the 8th bit
    
    tty.c_oflag |= OCRNL;              // Map CR to NL on output.
    tty.c_oflag |= ONLCR;             // Map NL to CR-NL on output.
    tty.c_oflag &= ~OPOST;             // Disable implementation-defined output processing
    
    tty.c_lflag &= ~ICANON;            // Disable canonical mode
    tty.c_lflag &= ~ISIG;
    tty.c_lflag &= ~IEXTEN;            // Disable implementation-defined input processing
    tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ECHOCTL|ECHOPRT|ECHOKE);
    
    // settimg serial read blocking behavior
    tty.c_cc[VTIME] = 0; 
   
    if (tcsetattr(fd, TCSANOW, &tty) != 0)
    
        printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
        return -1;
    
    return 1


int main() 

    mqd_t mqd;
    
    int ret = UartInit(); 
    
    if ((mqd = mq_open(COMMON_QUEUE, O_RDWR | O_CREAT, QUEUE_PERMISSIONS, &attr)) == -1) 
    
        perror ("Message queue failed to instantiate");
        return -1;
    
        
    if (pthread_create(&consumerTd, NULL, Consumer, (void *) &mqd) != 0)
    
        return -1;
    
    
    pthread_join(consumerTd, 0);
    return 0;

【问题讨论】:

【参考方案1】:

您的代码看起来像是在串行端口上启用流量控制。如果连接的另一端没有表明它已准备好接受数据,则您的串行端口将不会传输任何内容。您将慢慢填满串行端口的传输缓冲区,一旦没有更多空间来缓冲数据,尝试写入更多数据将阻塞并等待空间或返回错误(在您的情况下,是前者)。

我建议检查以确保连接的双方都使用相同的流量控制设置,您的串行电缆实际上具有连接的流量控制引脚(许多便宜的只连接TX/RX/GND 线),并且您的串口支持硬件流控制(USB 转串口适配器通常不支持)。禁用流控制可以避免挂断,但您可能会在对方尚未准备好接收数据时传输数据。

【讨论】:

谢谢。但是如果在接收端禁用了硬件流控制,我不应该收到消息吗?我会在接收端检查,但仍然很好奇接收消息的可能性。 @xyf 如果对方没有开启流量控制,它会随时发送消息。只要你这边准备好接收消息,你就会收到。当您的一方没有准备好(发射机无论如何都会发送而您错过了消息),或者如果您正在等待另一方表明它“准备好接收”(即不支持信号,因此它永远不会被设置)。

以上是关于在某些时候串行 POSIX 写入文件描述符似乎会停止程序的主要内容,如果未能解决你的问题,请参考以下文章

每次按下“写入”按钮时如何将字符串行写入文件

write -在一个文件描述符上执行写操作

在 Linux 中使用 Qt 的 Close() 文件描述符

POSIX 串行编程,非标准波特率

Linux应用程序编程

从串行端口(C/C++)读取时,如何实现 read() 超时