xyf
xyf

Reputation: 714

VTIME and VMIN don't seem to change the blocking behaviour of the posix read() call

I am trying to understand how VMIN and VTIME works to control the blocking behaviour of the read() posix call.

In my example, I have set VTIME to 10 (and have tried other combinations too) which should block the read for 1 second until it's unblocked, yes? That's my understanding but that doesn't seem to be the case.

I opened minimum on my host, and I see read() would only unblock as soon as I hit enter in minicom as opposed to waiting for 1 second to unblock.

Is my understanding incorrect? If not so, what could be wrong?

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;
}

Upvotes: 1

Views: 792

Answers (1)

Blabbo the Verbose
Blabbo the Verbose

Reputation: 391

I cannot duplicate the claimed behaviour, using the following example program:

// 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;
}

Specify the serial or terminal or pseudoterminal device path on the command line; use $(tty) for the current terminal/pseudoterminal.

Comparing OP's, Serial_Open() and my tty_open(), I do believe the terminal settings are essentially the same (and moreover, any differences there are do not explain the difference in behaviour).

You can compile the above example (example.c) as e.g. gcc -Wall -O2 example.c -o example and then run it via ./example $(tty) to read input from the same terminal window.

This leads me to believe the problem is in the other end: that whatever OP uses to generate the data read by this end, is line-buffered.

Upvotes: 1

Related Questions