Basic input and output 59
3.7 To block or not to block
When attempting to read in data from a device, it is important to decide
from the start whether you intend to block on the read or not. This refers to
the situation where you commit the thread of execution to wait until some
data is available. In an HLL program it may involve a call such as
scanf( )
which will not return until it has satisfied the complete parameter list. The
host operating system handles the complex details of I/O within a multi-
tasking environment, but it does still appear like a dedicated polling loop to
the programmer. Such single-minded perseverance may not be to your liking
when your program has many other things to do. A simple solution is to
make sure that there is always some data waiting to be read before going
into a blocking read. In the Windows environment, it is still possible to use
the old DOS function
kbhit() which returns TRUE or FALSE depending
on whether a new keyboard character is ready and waiting to be read. But
with Linux a different and more generally applicable approach is used. The
blocking time for a device can be set to a very low value which makes it
appear as if there is no delay when data is unavailable. When returning from
a
read() or other input function, either the data itself or the number of items
received must be checked to ascertain if data has been successfully read.
/* Example showing use of Linux tcsetattr() function to disable
keyboard blocking & buffering, allowing single char entry
*/
#include <stdio.h>
#include <sys/termios.h>
#include <ctype.h>
void setterm(void) {
struct termios kbd;
tcgetattr(0, &kbd); // file descriptor 0 = stdin
kbd.c_lflag &= (ICANON);
kbd.c_cc[VTIME] = 0;
kbd.c_cc[VMIN] = 0;
tcsetattr(0, TCSANOW, &kbd); // console device
}
main ( ) {
int letter = ’a’;
setterm( );
while (letter != ’q’) {
letter = getchar( );
putchar(’.’);
}
}
Reconfiguring keyboard input on Linux
60 Real-time systems development
It is often necessary to avoid both blocking and buffering when reading
console keyboard input. The code on p. 59 achieves this. But for other devices,
such as serial ports, the code can also be adjusted using the file descriptor for
that device, as in the figure below. Note that setting the device to NOBLOCK
does not cancel input buffering, a CR/ENTER will still be required to flush
the characters out of the buffer and make them accessible by an application
read.
int fd;
struct termios tty;
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd <0) {perror("failed to open /dev/ttyS0"); exit(-1); {
tcgetattr(fd, &tty);
tty.c_lflag &= (ICANON);
tty.c_cc[VTIME] = 0;
tty.c_cc[VMIN] = 0;
tcsetattr(fd, TCSANOW, &tty);
Setting the COM port with tcgetattr()
A similar problem exists when using an output function such as putc().
If the output device is not ready to dispatch data, perhaps because it is still
busy transmitting the previous item, it will block the next write request and
not return from the call until it is free to service it. To avoid the situation of a
program blocking on every output function, operating systems are capable of
carrying out an availability check using status checking functions, such as or
GetOutputState() in Windows. Unfortunately, these are not part of the
standard set of C library calls. Each operating system has its own approach
to the ‘blocking’ problem. Linux offers the powerful
ioctl() facilities for
reconfiguring channels, but to set non-blocking, there is also the
fcntl()
function illustrated opposite code fragment. This function is also useful when
initializing signals. We will return to it later.
It is important, however, to note that task blocking is an essential part
of fair scheduling within most multi-tasking systems. If the higher priority
tasks do not yield, or block, it would be impossible for the lower priority
tasks ever to gain access to the CPU. Indeed, on many computers, should
a task not yield or block, even equal priority sister tasks will fail to run if
time-slicing has not been enabled. They would suffer from what is termed
starvation.
When multi-threading is supported, as it is with Java, a common
approach for dealing with several blocking activities is to spawn off a thread
for each. Thus, with three inputs, two outputs and a bi-directional network
socket all to be watched, the program can spawn six threads. Each thread
blocks on one channel until data is available, then the thread is rescheduled so

Get Real-Time Systems Development now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.