【问题标题】:File descriptor is in blocking mode, but read() is not blocking文件描述符处于阻塞模式,但 read() 没有阻塞
【发布时间】:2019-11-17 10:46:45
【问题描述】:

我正在编写一些软件来处理 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);)

【问题讨论】:

  • 你指定了O_NDELAY,所以你知道了。
  • @BenVoigt 无论我是否指定O_NDELAY,问题都仍然存在。我尝试删除它。我不完全理解这个选项和O_NONBLOCK 之间的区别。
  • 你的程序实际上比"read() [that] do not block'有更多(未报告的)问题。写操作没有准确传输,第二次写可以返回一个 errno 5,输入/输出错误。第一次读取可以检索一个字节,但它是一个垃圾值。程序行为取决于先前的 termios 设置。请参阅我的扩展答案。

标签: linux serial-port debian beagleboneblack termios


【解决方案1】:

我遇到的问题是致电read()不要阻止

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

...根据我的研究,这应该是在阻塞模式下阅读。 (fcntl(fd, F_SETFL, 0);)

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

我的猜测是我在某处错误配置了一个选项。

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


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

read() 尝试从文件描述符 fd 读取最多 count 字节到从 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);

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

【讨论】:

  • 是否可以将时间设置为无限?
  • 是的。如果您研究提供的链接,VMIN &gt; 0 and VTIME = 0 是一个可以永远等待的计数读取。但是您的程序并没有以稳健的方式处理数据。每个 write() 之前的 tcflush() 和之后的 tcdrain() 将有助于同步操作很多。
  • 啊好吧 - 我还是有点困惑。我在 Python 中写了类似的东西,对 read(2) 的调用似乎是“阻塞”的,并且似乎等待无限时间来读取 2 个字节。有可能做到这一点吗?如果我将 VMIN 设置为 2,那么如果我想读取(4),那么它可能无法工作。
  • 忘记 Python 是做什么的。正如我已经在 read() 中写的字节长度不控制最小计数;这就是 VMIN 的用途。尝试在一个 read() 中准确读取 N 个字节,然后在下一个 read() 中准确读取 M 个字节是不切实际的,因为这需要重新编程 VMIN .您最好像本例那样缓冲数据:stackoverflow.com/questions/43280740/…
  • 好的,你能解释一下为什么你改变了 c_lflag 和 c_iflag 选项吗?特别是为什么现在缺少 ECHOE 而添加了 ECHONL?为什么 IGNPAR 现在既没有设置也没有取消设置,而是取消了其他各种选项?
猜你喜欢
  • 2021-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-23
  • 1970-01-01
  • 1970-01-01
  • 2011-07-15
  • 1970-01-01
相关资源
最近更新 更多