I need to wait for n bytes of data (count is known) on a serial port or socket on Linux. Currently I use a loop with poll, measure the time and decrement the timeout:
static int int_read_poll(int fd, uint8_t *buffer, size_t count, int timeout)
{
struct pollfd pfd;
int rc;
pfd.fd = fd;
pfd.events = POLLIN;
rc = poll(&pfd, 1, timeout);
if (rc < 0) {
perror("poll");
return 0;
}
if (rc > 0) {
if (pfd.revents & POLLIN) {
rc = read(fd, buffer, count);
return rc;
}
}
return 0;
}
static int int_read_waitfor(int fd, uint8_t *buffer, size_t count, int timeout)
{
int rc;
struct timespec start, end;
int delta_ms;
int recv = 0;
do {
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
rc = int_read_poll(fd, buffer + recv, count - recv, timeout);
clock_gettime(CLOCK_MONOTONIC_RAW, &end);
delta_ms = (end.tv_nsec - start.tv_nsec) / 1000000;
if (!rc || (rc < 0)) return 0;
recv += rc;
timeout -= delta_ms;
if (timeout <= 0)
return 0;
} while (recv != count);
return recv;
}
On a serial port, poll returns on each single byte and causes many iterations.
Is there a more elegant way to solve that problem?
I am aware that depending on the baudrate, timeout might not decrement in that code portion. Counting nanoseconds might be a better approach.
Thanks to all for your valuable hints!
After some testing, I finally decided not to use signals as they may interfere with the application once I port my functions into a library or publish them as source.
I eventually found a neat solution which uses poll and termios (only four syscalls):
static int int_read_waitfor(int fd, uint8_t *buffer, size_t count, int timeout)
{
struct termios tm;
struct pollfd pfd;
int rc;
tcgetattr(fd, &tm);
tm.c_cc[VTIME] = 1; // inter-character timeout 100ms, valid after first char recvd
tm.c_cc[VMIN] = count; // block until n characters are read
tcsetattr(fd, TCSANOW, &tm);
pfd.fd = fd;
pfd.events = POLLIN;
rc = poll(&pfd, 1, timeout);
if (rc > 0) {
rc = read(fd, buffer, count);
if (rc == -1) {
perror("read");
return 0;
}
return rc;
}
return 0;
}
Unlike network sockets which are usually packet based, serial ports (n.b.: in non-canonical mode) are character based. It is expected that a loop with poll is iterated for every arriving character, in particular at low baud rates.
I my application I send a comand over a serial line to a device and wait for an answer. If no answer is received, a timeout will occur and maybe we'll do a retry.
The termios option "VMIN" is handy as I can specify how many characters I like to reveive. Normally read would block until n chars have arrived.
If there is no answer, the command will block forever.
The termios option "VTIME" in conjunction with VMIN > 0 is specifying the intercharacter timeout in deciseconds (1 = 100ms). This is handy but the timeout will start only after reception of the first character. Otherwise an intercharacter timeout would make no sense.
So if I would use only termios options, read would block of the slave serial device is dead.
To circumvent that problem, I use poll in front of read. Once the first character has arrived (poll returns with rc=1), I start reading. "VTIME" is active as well and will enforce the intercharacter time of 100ms (the lowest possible setting).
As a bonus the timeout handling is optimized:
Lets assume a timeout of 400ms