Though the combination of blocking and nonblocking operations and the select method are sufficient for querying the device most of the time, some situations aren’t efficiently managed by the techniques we’ve seen so far.
Let’s imagine, for example, a process that executes a long computational loop at low priority, but needs to process incoming data as soon as possible. If the input channel is the keyboard, you are allowed to send a signal to the application (using the `INTR’ character, usually CTRL-C), but this signaling ability is part of the tty abstraction, a software layer that isn’t used for general char devices. What we need for asynchronous notification is something different. Furthermore, any input data should generate an interrupt, not just CTRL-C.
User programs have to execute two steps to enable asynchronous
notification from an input file. First, they specify a process as the
“owner” of the file. When a process invokes the
F_SETOWN
command using the
fcntl system call, the process ID of the owner
process is saved in filp->f_owner
for later
use. This step is necessary for the kernel to know just who to notify.
In order to actually enable asynchronous notification, the user
programs must set the FASYNC
flag in the device by
means of the F_SETFL
fcntl
command.
After these two calls have been executed, the input file can request
delivery of a SIGIO
signal whenever new data
arrives. The signal is sent to the process (or process group, if the
value is negative) stored in filp->f_owner
.
For example, the following lines of code in a user program enable
asynchronous notification to the current process for the
stdin
input file:
signal(SIGIO, &input_handler); /* dummy sample; sigaction() is better */ fcntl(STDIN_FILENO, F_SETOWN, getpid()); oflags = fcntl(STDIN_FILENO, F_GETFL); fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);
The program named asynctest in the sources
is a simple program that reads stdin
as shown. It
can be used to test the asynchronous capabilities of
scullpipe. The program is similar to
cat, but doesn’t terminate on end-of-file;
it responds only to input, not to the absence of input.
Note, however, that not all the devices support asynchronous
notification, and you can choose not to offer it. Applications usually
assume that the asynchronous capability is available only for sockets
and ttys. For example, pipes and FIFOs don’t support it, at least in
the current kernels. Mice offer asynchronous notification because some
programs expect a mouse to be able to send SIGIO
like a tty does.
There is one remaining problem with input notification. When a process
receives a SIGIO
, it doesn’t know which input file
has new input to offer. If more than one file is enabled to
asynchronously notify the process of pending input, the application
must still resort to poll or
select to find out what happened.
A more relevant topic for us is how the device driver can implement asynchronous signaling. The following list details the sequence of operations from the kernel’s point of view:
When
F_SETOWN
is invoked, nothing happens, except that a value is assigned tofilp->f_owner
.When
F_SETFL
is executed to turn onFASYNC
, the driver’s fasync method is called. This method is called whenever the value ofFASYNC
is changed infilp->f_flags
, to notify the driver of the change so it can respond properly. The flag is cleared by default when the file is opened. We’ll look at the standard implementation of the driver method soon.When data arrives, all the processes registered for asynchronous notification must be sent a
SIGIO
signal.
While implementing the first step is trivial—there’s nothing to do on the driver’s part—the other steps involve maintaining a dynamic data structure to keep track of the different asynchronous readers; there might be several of these readers. This dynamic data structure, however, doesn’t depend on the particular device involved, and the kernel offers a suitable general-purpose implementation so that you don’t have to rewrite the same code in every driver.
The general implementation offered by Linux is based on one data
structure and two functions (which are called in the second and third
steps described earlier). The header that declares related material is
<linux/fs.h>
—nothing new here—and
the data structure is called struct fasync_struct
. As we did with wait queues, we need to insert
a pointer to the structure in the device-specific data
structure. Actually, we’ve already seen such a field in Section 5.2.5.
The two functions that the driver calls correspond to the following prototypes:
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa); void kill_fasync(struct fasync_struct **fa, int sig, int band);
fasync_helper
is invoked to add files to or remove
files from the list of interested processes when the
FASYNC
flag changes for an open file. All of its
arguments except the last are provided to the
fasync method and can be passed through
directly. kill_fasync
is used to signal the
interested processes when data arrives. Its arguments are the signal
to send (usually SIGIO
) and the band, which is
almost always POLL_IN
(but which may be used to
send “urgent” or out-of-band data in the networking code).
Here’s how scullpipe implements the fasync method:
int scull_p_fasync(fasync_file fd, struct file *filp, int mode) { Scull_Pipe *dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue); }
It’s clear that all the work is performed by
fasync_helper. It wouldn’t be possible, however,
to implement the functionality without a method in the driver, because
the helper function needs to access the correct pointer to
struct fasync_struct *
(here
&dev->async_queue
), and only the driver can
provide the information.
When data arrives, then, the following statement must be executed to signal asynchronous readers. Since new data for the scullpipe reader is generated by a process issuing a write, the statement appears in the write method of scullpipe.
if (dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
It might appear that we’re done, but there’s still one thing
missing. We must invoke our fasync method when
the file is closed to remove the file from the list of active
asynchronous readers. Although this call is required only if
filp->f_flags
has FASYNC
set,
calling the function anyway doesn’t hurt and is the usual
implementation. The following lines, for example, are part of the
close method for
scullpipe:
/* remove this filp from the asynchronously notified filp's */ scull_p_fasync(-1, filp, 0);
The data structure underlying asynchronous notification is almost
identical to the structure struct wait_queue
,
because both situations involve waiting on an event. The difference is
that struct file
is used in place of
struct task_struct
. The struct file
in the queue is then used to retrieve
f_owner
, in order to signal the process.
Get Linux Device Drivers, Second Edition 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.