linux - 文件描述符处于阻塞模式,但 read() 未阻塞

标签 linux serial-port debian beagleboneblack termios

我正在编写一些软件来处理 Beaglebone 系统的串行端口读/写。操作系统是 Debian 9。我正在用 --std=gnu99 用 C 编写代码。

这是我的代码:

// reference
// https://www.cmrr.umn.edu/~strupp/serial.html

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>

int open_port(void)
{    
    int fd;

    fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NDELAY);
    // removing O_NDELAY no difference
    if(fd == -1)
    {
        perror("open_port: Unable to open /dev/ttyS1 - ");
    }
    else
    {
        fcntl(fd, F_SETFL, 0);
    }

    return fd;
}

int main()
{

    // open fd for serial port
    int fd = open_port();


    // set baud rate
    struct termios options;
    // get current options
    tcgetattr(fd, &options);
    // set input and output baud
    cfsetispeed(&options, B115200);
    cfsetospeed(&options, B115200);
    // enable reciever and set local mode
    // CLOCAL: ignore modem control lines
    // CREAD: enable reciever
    options.c_cflag |= (CLOCAL | CREAD);

    // set partity bit
    //options.c_cflag &= PARENB;
    options.c_cflag &= ~PARENB;
    // use even parity
    //options.c_cflag &= ~PARODD;
    // use only 1 stop bit
    options.c_cflag &= ~CSTOPB;
    // set character size to 8 bit
    options.c_cflag &= ~CSIZE;
    options.c_cflag &= CS8;
    // disable flow control
    //options.c_cflag &= ~CNEW_RTSCTS; // does not work?

    // note: check local options, some may be required
    // disable canonical input (use raw)
    // disable echoing of characters
    // disable echoing of erase characters
    // disable signals SIGINTR SIGSUSP SIGDSUSP, SIGQUIT
    // input characters are passed through exactly as recieved
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    // disable parity check
    options.c_iflag &= IGNPAR;

    // disable flow control (software)
    options.c_iflag &= ~(IXON | IXOFF | IXANY);

    // set raw output, no output processing
    options.c_oflag &= ~OPOST;

    // set options
    tcsetattr(fd, TCSANOW, &options);

    char data[] = {0x01, 0x03, 0x00, 0x00};
    int n = write(fd, data, 4);
    if(n =! 4)
    {
        printf("write fail %d\n", n);
    }

    char buffer[40];
    n = read(fd, buffer, 2);
    if(n != 2)
    {
        printf("read fail %d\n", n);
    }
    if(buffer[0] == 0x03 && buffer[1] == 0x00)
    {

    }
    else
    {
        printf("uart error\n");
    }

    char data2[] = {0x00, 0x00, 0x00, 0x01};
    n = write(fd, data2, 4);
    if(n != 4)
    {
        printf("write fail %d\n", n);
    }

    n = read(fd, buffer, 2);
    if(n != 2)
    {
        printf("read fail %d\n", n);
    }
    if(buffer[0] == 0x03 && buffer[1] == 0x00)
    {

    }
    else
    {
        printf("uart error\n");
    }

    printf("process complete\n");

    // to close
    close(fd);
}

我遇到的问题是调用 read() 不要阻止。这不是期望的行为。这些调用应该阻塞,直到收到 2 个字节的数据。

我的猜测是我在某处错误配置了一个选项。但是我不知道错误在哪里,根据我的研究,这应该是在阻塞模式下阅读。 (fcntl(fd, F_SETFL, 0);)

最佳答案

The issue I have is calls to read() do not block.

这实际上是一个结论,而不是一个观察。
大概您的程序正在报告零字节读取,这在技术上不是错误。

... from what I have researched, this should be reading in blocking mode. (fcntl(fd, F_SETFL, 0);)

正确,您已经为阻塞模式配置了文件描述符。

My guess would be I have misconfigured an option somewhere.

是的,您的 termios 配置不完整。 (它使用正确的位运算,但遗憾的是不完整且有错误,请参阅附录。)
当配置为非规范模式时,您还必须指定 VMINVTIME 参数。
如果未指定(如在您的代码中),VMIN = 0 和 VTIME = 0 的默认值可能会生效,这相当于非阻塞读。


请注意,read() 系统调用中的字节长度对完成行为的影响很小(除了限制要返回的字节数)。
ma​​nread(2) 页面:

read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.

因此 count 参数实际上是一个最大值。
实际读取的字节数可能介于零和 count 之间,具体取决于为 VMINVTIME 配置的值(以及可用的实际数据)。

参见 Linux Blocking vs. non Blocking Serial Read更多细节。


要在每个系统调用中至少读取两个字节(没有时间限制),请在 termios 配置中包含以下内容:

options.c_cc[VMIN] = 2;
options.c_cc[VTIME] = 0;

tcsetattr() 系统调用之前插入到您的程序中的上述两个语句将导致您的程序永远阻塞,直到每个 read(fd, buffer, 2) 中可以返回恰好两个字节)语句。
read(fd, buffer, 4) 语句也将永远阻塞,并返回 2、3 或 4 字节的数据(取决于与数据接收和缓冲相关的程序时序)。


附录

事实证明,您的 termios 初始化中存在一些错误。
正如发布的那样,您的程序既不能正确传输也不能正确接收任何数据,并且您没有检测到这些错误或忽略了它们。

您程序中的以下语句抹杀了所有先前的c_cflag 操作,并无意中将波特率重新配置为B0 并将字符大小重新配置为CS5 :

    options.c_cflag &= CS8;

正确的语法是:

    options.c_cflag |= CS8;

声明

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

应该用更多属性扩展以匹配cfmakeraw():

    options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);

程序中的以下语句禁用 c_iflag 中除 IGNPAR 之外的所有属性,并使 IGNPAR 属性处于未知/模糊状态:

    // disable parity check
    options.c_iflag &= IGNPAR;

要匹配cfmakeraw(),应该改成:

    options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                       | INLCR | IGNCR | ICRNL);

通过上述所有更正,我可以让您的程序按预期执行。

关于linux - 文件描述符处于阻塞模式,但 read() 未阻塞,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56926117/

相关文章:

c - 有没有办法查询串口可用的数据量?

debian - 您如何使用 saltstack 添加包 repo 和 apt key ?

linux - 什么时候应该使用 TCP_NODELAY,什么时候应该使用 TCP_CORK?

linux - 如何在shell脚本文件的每一行末尾添加一个字符

python - 在 pyvisa 中为串行端口的 write_ascii_values 分配值

c++ - 将类添加到基本 Qt GUI 应用程序时出现 Qt 链接器错误

linux - iptables 删除不在列表中的主机域

python - PyMongo 连接被拒绝

c - 如何有效地将 ram_base 文件复制到磁盘

android - proc/pid/maps 条目的顺序