【问题标题】:VTIME and VMIN don't seem to change the blocking behaviour of the posix read() callVTIME 和 VMIN 似乎没有改变 posix read() 调用的阻塞行为
【发布时间】:2021-10-11 16:29:25
【问题描述】:

我试图了解VMINVTIME 如何控制read() posix 调用的阻塞行为。

在我的示例中,我已将 VTIME 设置为 10(并且也尝试了其他组合),这应该会阻止读取 1 秒,直到它被解锁,是吗?这是我的理解,但事实并非如此。

我在我的主机上打开了最低限度,我看到read() 只会在我在 minicom 中点击 enter 后立即解锁,而不是等待 1 秒解锁。

我的理解不正确吗?如果不是这样,可能有什么问题?

int Serial_Open(char *port)
{
    int serial_port = open(port, O_RDWR);
    struct termios tty;

    // Read in existing settings, and handle any error
    if(tcgetattr(serial_port, &tty) != 0) 
    {
        printf("Error from tcgetattr: %s\n", strerror(errno));
    }

    tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
    tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
    tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size 
    tty.c_cflag |= CS8; // 8 bits per byte (most common)
    tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
    tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)

    tty.c_lflag &= ~ICANON;
    tty.c_lflag &= ~ECHO; // Disable echo
    tty.c_lflag &= ~ECHOE; // Disable erasure
    tty.c_lflag &= ~ECHONL; // Disable new-line echo
    tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
    tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes

    tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
    tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed

    tty.c_cc[VTIME] = 10;    // Wait for up to 1s, returning as soon as any data is received.
    tty.c_cc[VMIN] = 0;

    cfsetspeed(&tty, B115200);

    if (tcsetattr(serial_port, TCSANOW, &tty) != 0) 
    {
        printf("Error tcsetattr %s\n", strerror(errno));
    }
  return serial_port;
}

int main(void)
{
  char buffer[100];
  int ret;

  int fd = Serial_Open("/dev/ttyUSB4");
  
  while(1)
  {
        ret = read(fd, buffer, sizeof(buffer));
        if (ret <= 0)
        {
           printf ("No data or error\n");
        }
        else
        {
           printf ("Rxd data: %s\n", buffer);
        }
  }
  return 1;
}

【问题讨论】:

  • "...应该阻止读取 1 秒,直到解锁,是吗?这是我的理解..." -- 不。您的理解不正确。阅读 man 页面了解 termios。顺便说一句,您的代码不会像发布的那样编译: Serial_Open() 被声明为 void 例程。 read() 不返回字符串,因此您的 printf() 可以显示垃圾(以及陈旧数据)。
  • 我读到这个 ​​-> blog.mbedded.ninja/programming/operating-systems/linux/… - 它说 VMIN=0, VTIME&gt;0 是任何数量的字符的阻塞读取,超时由 VTIME 设置的任何值给出,在我的情况下是 1s .基本上read() 阻塞,直到任何数量的数据可用或发生超时。那么如果超时时间为1s,还没有收到数据,是不是应该在之后解除阻塞呢?
  • 再来一次:阅读 man 页面了解 termios。不要引用或依赖一些试图用更少的词来描述 man 页面的不知名人士。即使在快速修复之后,您的代码仍然会损坏。
  • "当调用 read(2) 时启动计时器。当至少一个字节数据可用时,或者当计时器到期时,read(2) 返回。"。这对我来说仍然很熟悉。我是否也错过了其他东西?
  • 您写道,您认为将 VTIME 设置为 10 应该“阻止读取 1 秒,直到它被解除阻止”。即使我们将此与从您的代码中提取的信息结合起来,您也将VMIN 设置为零并禁用规范模式,这听起来不正确。该配置应导致read() 阻止最多 秒,假设 尚未读取任何数据。可能会有不到一秒的阻塞,读也不一定会阻塞。也许这就是你想要表达的理解,但我并没有这样理解。

标签: c linux serial-port posix uart


【解决方案1】:

我无法复制声称的行为,使用以下示例程序:

// SPDX-License-Identifier: CC0-1.0

#define  _POSIX_C_SOURCE  200809L
#define  _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

static int             tty_fd = -1;
static struct termios  tty_old;

static void  tty_close(void)
{
    if (tty_fd == -1)
        return;

    tcsetattr(tty_fd, TCSAFLUSH, &tty_old);

    if (tty_fd != STDIN_FILENO  &&
        tty_fd != STDOUT_FILENO &&
        tty_fd != STDERR_FILENO)
        close(tty_fd);

    tty_fd = -1;
}

static int  tty_open(const char *ttypath)
{
    struct termios  tty_new;
    int             fd;

    if (tty_fd != -1)
        tty_close();

    if (!ttypath || !*ttypath)
        return errno = ENOENT;

    do {
        fd = open(ttypath, O_RDWR | O_NOCTTY | O_CLOEXEC);
    } while (fd == -1 && errno == EINTR);
    if (fd == -1)
        return errno;

    if (!isatty(fd)) {
        close(fd);
        return errno = ENOTTY;
    }

    if (tcgetattr(fd, &tty_old) == -1) {
        const int  saved_errno = errno;
        close(fd);
        return errno = saved_errno;
    }

    tty_new = tty_old;

    /* No input processing.  No input flow control.  Ignore parity.  Break reads as '\0'. */
    tty_new.c_iflag &= ~(IGNBRK | BRKINT | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IUCLC | IXON | IXANY | IXOFF);
    tty_new.c_iflag |= IGNPAR;

    /* No output processing. */
    // tty_new.c_oflag &= ~(OPOST | OLCUC | ONLCR | OCRNL | ONOCR | ONLRET);
    tty_new.c_oflag &= ~OPOST;

    /* 8 data bits, No parity, 1 stop bit, no hardware flow control, ignore modem control lines. */
    tty_new.c_cflag &= ~(CSIZE | PARENB | CSTOPB | CRTSCTS);
    tty_new.c_cflag |= CS8 | CREAD | CLOCAL;

    /* Raw mode, no signals, no echo. */
    tty_new.c_lflag &= ~(ICANON | ISIG | ECHO | IEXTEN);

    /* VMIN=0, VTIME=10 */
    tty_new.c_cc[VMIN] = 0;
    tty_new.c_cc[VTIME] = 10;

    if (tcsetattr(fd, TCSANOW, &tty_new) == -1) {
        const int  saved_errno = errno;
        close(fd);
        return errno = saved_errno;
    }

    /* Some of the above settings may not have been applied.
       We could check, but really, we don't care that much. */
    tty_fd = fd;

    return 0;
}

#ifndef  TTY_BUFSIZ
#define  TTY_BUFSIZ  128
#endif

static unsigned char           tty_buf[TTY_BUFSIZ];
static volatile unsigned char *tty_head = tty_buf;
static volatile unsigned char *tty_tail = tty_buf;

#define  TTY_NONE  -1
#define  TTY_EOF   -2
#define  TTY_ERROR -3

static int tty_getc_read(void)
{
    tty_head = tty_tail = tty_buf;
    if (tty_fd == -1)
        return TTY_EOF;

    ssize_t  n = read(tty_fd, tty_buf, sizeof tty_buf);
    if (n > 0) {
        tty_tail = tty_buf + n;
        return *(tty_head++);
    } else
    if (n == 0) {
        return TTY_NONE;
    } else
    if (n != -1 || (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)) {
        return TTY_ERROR;
    } else {
        return TTY_NONE;
    }
}

static inline int tty_getc(void)
{
    if (tty_tail > tty_head)
        return *(tty_head++);
    else
        return tty_getc_read();
}


static volatile sig_atomic_t  done = 0;

static void handle_done(int signum)
{
    if (!done)
        done = (signum > 0) ? signum : -1;
}

static int install_done(int signum)
{
    struct sigaction  act;

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);
    act.sa_handler = handle_done;
    act.sa_flags = 0;
    if (sigaction(signum, &act, NULL) == -1)
        return errno;

    return 0;
}

int main(int argc, char *argv[])
{
    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        const char *cmd = (argc > 0 && argv && argv[0] && argv[0][0]) ? argv[0] : "(this)";
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", cmd);
        fprintf(stderr, "       %s TTY-DEVICE\n", cmd);
        fprintf(stderr, "\n");
        fprintf(stderr, "This reads in raw mode from the TTY device.\n");
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    if (install_done(SIGINT) || install_done(SIGTERM) ||
        install_done(SIGHUP) || install_done(SIGQUIT)) {
        fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
        return EXIT_SUCCESS;
    }

    if (tty_open(argv[1])) {
        fprintf(stderr, "%s: Cannot open TTY: %s.\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }

    printf("Press CTRL+C to exit.\r\n");
    fflush(stdout);

    int  last_none = 0;

    while (!done) {
        int  ch = tty_getc();

        if (ch == TTY_NONE) {
            printf(".");
            fflush(stdout);
            last_none = 1;
            continue;
        } else
        if (last_none) {
            printf("\r\n");
            fflush(stdout);
            last_none = 0;
        }

        if (ch == TTY_EOF) {
            printf("End-of-input received.\r\n");
            fflush(stdout);
            break;
        } else
        if (ch == TTY_ERROR) {
            printf("Read error occurred.\r\n");
            fflush(stdout);
            break;
        } else
        if (ch == 3) {
            printf("Received 0x03 = \\003, assuming Ctrl+C. Exiting.\r\n");
            fflush(stdout);
            break;
        } else {
            if (ch >= 32 && ch <= 126)
                printf("Received 0x%02x = \\%03o = %3d = '%c'\r\n", (unsigned int)ch, (unsigned int)ch, ch, ch);
            else
                printf("Received 0x%02x = \\%03o = %3d\r\n", (unsigned int)ch, (unsigned int)ch, ch);
            fflush(stdout);
        }
    }
    if (last_none) {
        printf("\r\n");
        fflush(stdout);
    }

    tty_close();
    return EXIT_SUCCESS;
}

在命令行指定串口或终端或伪终端设备路径;对当前终端/伪终端使用$(tty)

比较 OP,Serial_Open() 和我的tty_open(),我确实认为终端设置基本相同(此外,存在的任何差异都不能解释行为上的差异)。

您可以将上面的示例(example.c)编译为例如gcc -Wall -O2 example.c -o example 然后通过./example $(tty) 运行它以从同一终端窗口读取输入。

这让我相信问题出在另一端:无论 OP 用于生成此端读取的数据,都是行缓冲的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-24
    • 2016-07-17
    • 1970-01-01
    • 2023-03-11
    • 2011-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多