Linux Driver Technology (IV) _Asynchronous Notification Technology

Keywords: Linux

The full name of asynchronous notification is "signal-driven asynchronous IO". When the desired resources are available, the driver will actively notify the designated application program, which corresponds to the "signal" of the application layer. The signal "SIGIO" is used here. The steps are

  1. The application layer program registers itself as a process that receives SIGIO signals from device files
  2. Driver implements the corresponding interface in order to have the ability to send SIGIO signals to all applications registered to receive SIGIO signals from this device.
  3. When the driver calls the sending function at the appropriate location, the application can receive the SIGIO signal.

The framework of the whole mechanism:

Application Layer Receives SIGIO

Like other signals, the application layer needs to register a signal processing function.
The way to register is signal() or sigaction().

In addition, the application layer needs to add itself to the driver's notification list with the following code

fcntl(dev_fd,F_SETOWN,getpid());
int oflags = fcntl(dev_fd,F_GETFL);
fcntl(dev_fd,F_SETFL,oflags|FASYNC);
...
while(1);

With the above work done, the application layer program can wait for SIGIO to arrive.

Driver sends SIGIO

The application layer registers well, and the final transmission depends on the processing mode of the device driver. In order to support the asynchronous notification mechanism, referring to the interface of the application layer, the driver involves three tasks.

  1. Supporting the F_SETOWN command, you can set FILP - > f_owner as the ID of the corresponding process under this command. This part of the kernel has been done.
  2. Supports F_SETFL. Whenever the FASYNC flag changes, fasync() in the driver will be executed. so, fasync() in the driver will be implemented.
  3. SIGIO is sent through kill_fasync() when device resources are available

To implement these three functions in the kernel, the driver needs to use one structure + two API s, struct fasync_struct, fasync_helper() and kill_fasync().

struct fasync_struct {                                    
        spinlock_t              fa_lock;
        int                     magic;
        int                     fa_fd;
        struct fasync_struct    *fa_next; /* singly linked list */
        struct file             *fa_file;
        struct rcu_head         fa_rcu;
};

The function of fasync_helper() is to register an object of fasync_struct into the kernel. When the application layer executes fcntl(dev_fd, F_SETFL, oflags|FASYNC), it calls back the fops.fasync(), so it is usually put into the implementation of fasync().

/**
 *fasync_helper - Register a fasync_struct object into the kernel
 *@fd:File descriptor, passed in by fasync
 *@filp:file Pointer, passed in by fasync
 *@sig:Signal type, usually SIGIO
 *@dev_fasync:A pointer to the fasync_struct object pointer prepared beforehand
 */
int fasync_helper(int fd, struct file * filp, int sig, struct fasync_struct ** dev_fasync);   

The following API is to release SIGIO and put it in different positions according to different requirements.

/**
 *kill_fasync - Release a signal
 *@dev_fasync:A pointer to the fasync_struct object pointer registered in the kernel beforehand using fasync_helper
 *@filp:file Pointer, passed in by fasync
 *@sig:Signal type, usually SIGIO
 *@flag:Logo, typically, if a resource can read POLLIN, if a resource can write POLLOUT
 */
void kill_fasync(struct fasync_struct **dev_fasync, int sig, int flag);

Driving template

The following driver template is designed to signal the application layer when hardware interrupts arrive (resources are available). There are many situations where resources are available in practice.

static struct fasync_struct *fasync = NULL;

static irqreturn_t handler(int irq, void *dev)
{
    kill_fasync(&fasync, SIGIO, POLLIN);
    return IRQ_HANDLED;
}
static int demo_fasync(int fd, struct file *filp, int mode)
{
    return fasync_helper(fd, filp, mode, &fasync);
}
struct file_operations fops = {
    ...
    .fasync = demo_fasync,
    ...
}
static int __init demo_init(void)
{
    ...
    request_irq(irq, handler, IRQF_TRIGGER_RISING, "demo", NULL);
    ...
}

Posted by thinkmarsh on Mon, 25 Mar 2019 17:18:29 -0700