Linux/C/C++ : Why use blocking or non-blocking read over serial/usb

206 views Asked by At

I'm using <unistd.h> and <termios.h> to read()/write() terminated text-strings from a device over serial/USB (/dev/ttyACM0).

First I send a command with write().

When using blocking mode (canonical mode?) I simply call read() and wait for the terminated data string to return. When using non-blocking mode (non-canonical mode ?) I create a loop and do usleep() while waiting for the data string to return (ending in \n).

Both these ways work as expected but I'm now wondering why I should use one or the other?

In my case I need the complete fetched data string to continue in the rest of my simple (no threads) program so it doesn't seem to matter if I use blocking or non-blocking calls.

If my program was using threads then it still doesn't seem to matter if the thread getting the data is using blocking or non-blocking?

The only case I can think of that would benefit from using non-blocking calls would be if I'm receiving a stream of data that I can process part by part.

Or is it a benefit to use usleep() (or something similar) in a loop instead of doing a blocking call?

1

There are 1 answers

0
sawdust On

... I'm now wondering why I should use one or the other?

Your question is formulated as a choice between blocking versus nonblocking modes. You also associate canonical mode with blocking mode and noncanonical mode with nonblocking mode.
But canonical vs raw mode is orthogonal to blocking vs nonblocking mode.

See Linux Blocking vs. non Blocking Serial Read.
So there are three schemes to consider, but the nonblocking modes are typically misused. The fourth scheme, nonblocking noncanonical mode, is a degenerate mode that can be done in blocking noncanonical mode with VMIN=0 and VTIME=0.

In my case I need the complete fetched data string to continue ...

That is not unique to your situation.
Most binary protocols require reception of the entire message in order to complete message validation, e.g. calculate a checksum or CRC. Only after the message has been validated (i.e. the message packet has been received intact) is the message processed.

The choice between canonical versus raw mode would typically be predicated by the received data. That is, is it lines of (ASCII) text?
If the received data is not ASCII-encoded text delimited with EOL (end of line) character(s), then canonical mode is essentially impractical. The bytes of data are not processed in any manner when using noncanonical mode. This non-processing of data is typical for other peripherals. But it's a big decision/choice when using a (POSIX) serial terminal!


Your userspace program merely fetches bytes from a termios buffer, and is several kernel layers removed from the hardware.
Nonblocking syscalls typically end up inefficiently polling the system buffer with added latency, instead of using the built-in event-driven syscall.
The kernel is already using interrupts (and perhaps DMA) and event-driven handlers to efficiently process the received data. When the received data satisfies the termios criteria for the serial terminal, the read() syscall is unblocked and scheduler is notified that the process is ready to execute.

A multiprocessing OS relies on processes to share the available CPU(s). The typical process is expected to be a mix of computation and I/O. While that I/O is performed, that process does not need to use the CPU. That system time to perform that I/O can allow another process to use the CPU.
But a process can insist on retaining control of the CPU while its I/O is performed by using nonblocking mode. The process can hog the CPU, and make the system less efficient for multiprocessing.

In general a user process cannot improve upon the built-in event-driven scheme that the blocking mode offered by the OS. The use of "usleep() (or something similar) in a loop instead of doing a blocking call" adds more processing in userspace to an operation that is already handled in kernel space. The added complexity of nonblocking mode is typically slower and less efficient, and never faster or more efficient.


The only case I can think of that would benefit from using non-blocking calls would be if I'm receiving a stream of data that I can process part by part.

No, you're confusing "non-blocking calls" with raw (or noncanonical) mode. Raw mode, using VMIN and VTIME attributes, allows control of when the read() returns.

The occasional and special reasons for using nonblocking mode could include:

  • the process/thread is performing concurrent I/O on more than one device;
  • a read timeout is needed in canonical mode;
  • the process really has something to do, and this I/O and processing cannot be separate threads.

Otherwise the typical program has a difficult time justifying not using blocking mode.