Multiplexed IO

Applications often need to block on more than one file descriptor. Whithout the aid of threads, a single process can’t even dream on blocking on more than one file descriptor at the same time. The thing is that working with multiple file descriptors is fine, so long as they are always ready to be read from or written to. But as soon as one file descriptor that is not yet ready is encountered - say, if a read() system call is issued, and there is not yet any data — the process will block, no longer able to service the other file descriptors. And this, we all know, can be a pain in the ass sometimes.

Here’s a real world example. Imagine blocking on a file descriptor related to interprocess communication while stdin has data pending. The application won’t know that keyboard input is pending until the blocked IPC file descriptor ultimately returns data - but what if the blocked operation never returns?

There are more than one solution to that problem, but ideally what we would like to do is that the program could sleep, freeing the processor for other tasks to be woken up only when one or more file descriptors were ready to perform I/O. An so, multiplexed I/O was born. You can go and research the subject, but in escence what multiplexed I/O does is:

  1. Multiplexed I/O: Tell me when any of these file descriptors become ready fo I/O.
  2. Nothing ready? Sleep until one or more file descriptors are ready.
  3. Woken up! What is ready?
  4. Handle all file descriptors ready for I/O, without blocking.
  5. Go back to step 1.

Linux systems provides three multiplexed I/O solutions: the select, poll, and epoll interfaces. In this post I will only show you a piece of code I have written for work that uses select, but keep in mind that poll and it’s cousin epoll are much more featuring solutions.

At work I’m doing a project on access control with fingerprint identification. The requirement is simple. A user puts her fingerprint on the sensor and then she is prompted to press on one of two buttons for entrance or departure. The catch is that the user has only a relatively small time window to press the required button (5 seconds per requirement), otherwise the system goes to identification mode waiting for another user to place her finger.

As per the requirement of user spec, we have to wait 5 seconds for the user to press the required button and then go on with things, but we can’t block the thread while on waiting. One solution, of course, is launching a new thread for the button pressing functionality, but, at least in this case that is overkill. Multiplexed IO to the rescue…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
bool isButtonPressed()
{
int fd;
struct input_event event;
ssize_t bytesRead;
int ret;
struct timeval tv;
fd_set readfds;
int buttonType = 0;
fd = open("/dev/input/event0", O_RDONLY);
check(fd != -1, "Error opening /dev/input/event0 for reading");
debug("/dev/input/event0 oppened for reading");
/* Wait on fd for input */
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
/* Wait up to five seconds */
tv.tv_sec = 5;
tv.tv_usec = 0;
repeat:
ret = select(fd + 1, &readfds, NULL, NULL, &tv);
if (ret == -1) {
log_error("select call on /dev/input/event0: an error ocurred");
goto error;
} else if (!ret) {
debug("select call on /dev/input/event0: TIMEOUT");
if (fd) { if (close(fd) != 0) { log_error("Error closing /dev/input/event0"); } }
debug("/dev/input/event0 closed because of timeout");
return false;
}
/* File descriptor is now ready */
if (FD_ISSET(fd, &readfds)) {
bytesRead = read(fd, &event, sizeof(struct input_event));
check(bytesRead != -1, "Read input error: call to read returned -1.");
check(bytesRead == sizeof(struct input_event),
"Read input error: bytes read is not an input_event.");
switch (event.code) {
case BTN_5:
debug("IN Button Pressed");
buttonType = 5;
break;
case BTN_6:
debug("OUT Button Pressed");
buttonType = 6;
break;
default:
debug("UNKNOWN Button Pressed");
goto repeat;
}
}
if (buttonType == 0) {
if (fd) { if (close(fd) != 0) { log_error("Error closing /dev/input/event0"); } }
debug("/dev/input/event0 closed because UNKNOWN button was pressed");
return false;
} else {
if (fd) { if (close(fd) != 0) { log_error("Error closing /dev/input/event0"); } }
debug("/dev/input/event0 closed");
return true;
}
error:
if (fd) { if (close(fd) != 0) { log_error("Error closing /dev/input/event0"); } }
debug("/dev/input/event0 closed in error handler");
return false;
}

And there you have it, we were able to wait on IO without blocking. It is worth noting that this solution only works in Linux, because current versions of Linux modify the timeout automatically with the time remaining. Thus, if the timeout was set for 5 seconds, and 3 seconds elapsed before a file descriptor became ready, tv.tv_sec would containt 2 upon the call’s return.

And just like that, Multiplexed IO saves the day and we are now ready for another beer…

Thank you.

jvillasantegomez@gmail.com