Note: This article is a study note of the book "detailed explanation of Linux device driver development: Based on the latest Linux 4.0 kernel by song Baohua", and most of the content is in the book.
Books can be viewed directly in wechat reading: Detailed explanation of Linux device driver development: Based on the latest Linux 4 0 kernel - Song Baohua - wechat reading (qq.com)
Asynchronous notification and asynchronous I/O in Linux device driver
Using asynchronous notification in the device driver can enable the driver to actively notify the application for access when accessing the device.
Applications using non blocking I/O do not need whether the rotation training equipment can be accessed, and blocking access can be replaced by asynchronous notification similar to "interrupt".
In addition to asynchronous notification, the application can also return immediately after initiating the I/O request. After that, query the I/O completion status, or it will be transferred back after I/O is completed. This process is called asynchronous I/O.
1.1 introduction to asynchronous notification
Asynchronous notification: once the device is ready, it will actively notify the application. The application does not need to query the device status. It is more accurately called "signal driven asynchronous I/O".
Signal is a simulation of interrupt mechanism at the software level. In principle, a process receiving a signal is the same as a processor receiving an interrupt request. The signal is asynchronous. A process does not have to wait for the signal to arrive through any operation.
Blocking I/O means waiting until the device is accessible. Polling () means whether a non pollable device can be accessed.
The following figure shows the time sequence of blocking I/O, non blocking I/O combined with polling, and asynchronous notification based on SIGIO.
1.2 Linux asynchronous notification programming
1.2.1 Linux signal
Asynchronous notification in Linux is realized by signals. The signals available in Linux and their definitions are as follows:
In addition to SIGSTOP and SIGKILL signals, the process can ignore or capture all other signals. A signal captured means that when a signal arrives, there is corresponding code to process it. If a signal is not captured by the process, the kernel will adopt the default behavior.
1.2.2 signal reception
In the user program, the signal() function can be used to set the processing function of the corresponding signal. The function prototype is:
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
Parameter signum: Specifies the value of the signal
Parameter handler: Specifies the processing function for the signal value, if SIG_IGN, indicating that the signal is ignored; If SIG_DFL indicates that the system default mode is adopted for signal processing; If it is a user-defined function, the function is executed after signal acquisition.
Return value: if the call is successful, the handler value of the last processing function bound for signal signum is returned. If it fails, SIG is returned_ ERR.
When the process is executing, pressing Ctrl+C will send SIGINT signal to it, and the process running kill will send SIGTERM signal to it. The code to capture these two signals and output the signal value is as follows:
void sigterm_handler(int signo) { printf("Have caught sig N.0.%d\n", signo); exit(0); } int main(void) { signal(SIGINT, sigterm_handler); signal(SIGTERM, sigterm_handler); while(1); return 0; }
Compilation and testing:
$ ./a.out ^C^CHave caught sig N.0.2
sigaction() function: it can be used to change the behavior of a process after receiving a specific signal. The prototype of the function is:
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
Parameter signum: the value of the signal, which can be any specific effective signal except SIGKILL and SIGSTOP.
Parameter act: pointer to an instance of structure sigaction. The processing function for a specific signal is specified in the instance of structure sigaction. If it is empty, the process will process the signal in the default way.
Parameter oldact: the object pointed to is used to save the original processing function of the corresponding signal, which can be specified as NULL.
If the second and third parameters are set to NULL, this function can be used to check the validity of the signal.
Return value: 0 for success and - 1 for failure.
1.2.3 application instance of asynchronous notification
The standard input file descriptor stdin is processed through signal(SIGIO, input_handler)_ Fiflno initiates the signaling mechanism. After the user input, the application will receive the SIGIO signal and its processing function input_handler() will be called.
#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <unistd.h> #define MAX_LEN 100 void input_handler(int num) { char data[MAX_LEN]; int len; /* Read and output STDIN_FIFLNO */ len = read(STDIN_FILENO, &data, MAX_LEN); data[len] = 0; printf("input data:%s\n", data); } int main() { int oflags; /* Start signal driven mechanism */ signal(SIGIO, input_handler); //SIGIO signal installation input_handler() as a handler function fcntl(STDIN_FILENO, F_SETOWN, getpid()); //Set this process as stdin_ Owner of fifeno document oflags = fcntl(STDIN_FILENO, F_GETFL); fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); //Set FASYNC /* Finally, enter an endless loop to keep the process from terminating */ while(1); }
Compilation and testing:
$ gcc sigio_handler.c $ ./a.out i am chinese. input data:i am chinese. i love linux driver input data:i love linux driver
It can be seen that after the user inputs a string of characters, the standard input device releases the SIGIO signal, which "interrupts" to drive the input in the application_ Handler () is executed and displayed in user input.
Therefore, to process the signal released by a device in the user space, the following three items must be completed:
1) Through F_SETOWN I/O control command sets the owner of the device file as this process, so that the signal sent from the device driver can be received by this process.
2) Through F_SETFL I/O commands the device file to support FASYNC, that is, asynchronous notification mode.
3) Connect signal and signal processing function through signal() function.
1.2.4 asynchronous notification in device driver
In the interaction between device driver and application program, the source of signal is the device driver and the capture is the application program. Therefore, the release of the signal should be carried out in the equipment drive.
The device supports asynchronous notification mechanism, and the driver needs to support the following three tasks:
1) Support F_SETOWN command can handle file - > F in this control command_ Owner is the ID of the corresponding process, which has been completed by the kernel and does not need to be processed by the device driver.
2) Support F_SETFL command: whenever the FASYNC flag changes, the fasync() function in the driver is executed, and the fasync() function should be implemented in the device driver.
3) Call kill when device resources are available_ The fasync() function fires the corresponding signal.
The three tasks in the driver correspond to the three tasks in the application one by one. The asynchronous notification processing and user space interaction process of the device driver are as follows:
The function of asynchronous programming in device driver involves a data structure fasync_struct and two functions fasync_helper() and kill_fasync():
(1) Function to handle FASYNC flag change
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp);
(2) Release signal function
void kill_fasync(struct fasync_struct **fp, int sig, int band);
Kill should be called when device resources are available_ Fasync() releases SIGIO signal. When readable, the third parameter band is set to POLL_IN; When writable, the third parameter band is set to POLL_OUT.
fasync_ The struct data structure pointer is generally placed in the device structure. The device structure template supporting asynchronous notification is as follows:
struct xxx_dev { struct cdev cdev; ... ....; struct fasync_struct *async_queue; /* Asynchronous structure pointer */ ... ...; };
Device driven faync() function:
int (*fasync) (int, struct file *, int);
Template of fasync() function of device driver supporting asynchronous notification:
static int xxx_fasync(int fd, struct file *filp, int mode) { struct xxx_dev *dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue); }
Device driver signal release template supporting asynchronous notification:
Call kill when the resource is available_ The fasync() function releases the SIGIO signal.
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct xxx_dev *dev = filp->private_data; ... ...; /* Generate asynchronous read signal */ if (dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN); }
Finally, when the file is closed, the device driver fasync() function is invoked in the release () function of the device to delete the file from the list of asynchronous notification. The device driver release() function template supporting asynchronous notification is as follows:
static int xxx_release(struct inode *inode, struct file *filp) { /* Removes a file from the list of asynchronous notifications */ xxx_fasync(-1, filp, 0); ... ...; return 0; }
1.3 global FIFO driver supporting asynchronous notification
1.3.1 rewriting of device driven globalfifo
First, you need to add the asynchronous data structure pointer to global FIFO_ Dev device structure:
/* Equipment structure */ struct globalfifo_dev { struct cdev cdev; unsigned int current_len; /* Length of valid data in current FIFO */ unsigned char mem[GLOBALFIFO_SIZE]; struct mutex mutex; wait_queue_head_t r_wait; wait_queue_head_t w_wait; struct fasync_struct *async_queue; /* Asynchronous structure pointer */ };
Then, write the fasync() function driven by the globalfifo device for asynchronous notification:
static int globalfifo_fasync(int fd, struct file *filp, int mode) { struct globalfifo_dev *dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue); }
Then, rewrite the write function driven by the global FIFO device. After the global FIFO device is correctly written, it can be read. It needs to support the release of SIGIO signal and capture it to the application.
/** * Write device * @param[in] filp: File structure pointer * @param[in] buf: The user space memory address cannot be read or written directly in the kernel * @param[in] size: Bytes written * @param[in/out] ppos: The write position is equivalent to the offset of the file header * @return The number of bytes actually written is returned if successful, and the error code is returned if there is an error */ static ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { int ret = 0; struct globalfifo_dev *dev = filp->private_data; DECLARE_WAITQUEUE(wait, current); mutex_lock(&dev->mutex); add_wait_queue(&dev->w_wait, &wait); while (dev->current_len == GLOBALFIFO_SIZE) { if (filp->f_flags & O_NONBLOCK) { ret = -EAGAIN; goto out; } __set_current_state(TASK_INTERRUPTIBLE); mutex_unlock(&dev->mutex); schedule(); if (signal_pending(current)) { ret = -ERESTARTSYS; goto out2; } mutex_lock(&dev->mutex); } if (count > GLOBALFIFO_SIZE - dev->current_len) count = GLOBALFIFO_SIZE - dev->current_len; /* Copy from user space cache to kernel space cache */ if (copy_from_user(dev->mem + dev->current_len, buf, count)) { ret = -EFAULT; goto out; } else { dev->current_len += count; printk(KERN_INFO "written %lu bytes(s) from %u\n", count, dev->current_len); wake_up_interruptible(&dev->r_wait); if (dev->async_queue) { kill_fasync(&dev->async_queue, SIGIO, POLL_IN); printk(KERN_INFO "%s kill SIGIO\n", __func__); } ret = count; } out: mutex_unlock(&dev->mutex); out2: remove_wait_queue(&dev->w_wait, &wait); set_current_state(TASK_RUNNING); return ret; }
The release() function of the globalfifo device driver needs to call globalfifo_ The fasync() function deletes the file from the asynchronous notification list:
static int globalfifo_release(struct inode *inode, struct file *filp) { /* Removes a file from the list of asynchronous notifications */ globalfifo_fasync(-1, filp, 0); return 0; }
Complete device driver code:
#include <linux/module.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/poll.h> /* Direct use as provisional order */ #define MEM_CLEAR 0x1 #define GLOBALFIFO_MAJOR 230 #define GLOBALFIFO_SIZE 0x1000 static int globalfifo_major = GLOBALFIFO_MAJOR; module_param(globalfifo_major, int, S_IRUGO); /* Equipment structure */ struct globalfifo_dev { struct cdev cdev; unsigned int current_len; /* Length of valid data in current FIFO */ unsigned char mem[GLOBALFIFO_SIZE]; struct mutex mutex; wait_queue_head_t r_wait; wait_queue_head_t w_wait; struct fasync_struct *async_queue; /* Asynchronous structure pointer */ }; struct globalfifo_dev *globalfifo_devp; static int globalfifo_fasync(int fd, struct file *filp, int mode) { struct globalfifo_dev *dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue); } static int globalfifo_open(struct inode *inode, struct file *filp) { /* Get globalfifo using the private data of the file as_ Instance pointer of dev */ filp->private_data = globalfifo_devp; return 0; } static int globalfifo_release(struct inode *inode, struct file *filp) { /* Removes a file from the list of asynchronous notifications */ globalfifo_fasync(-1, filp, 0); return 0; } /** * Device ioctl function * @param[in] filp: File structure pointer * @param[in] cmd: Command, currently only MEM is supported_ CLEAR * @param[in] arg: Command parameters * @return 0 is returned if successful, and the error code is returned if there is an error */ static long globalfifo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct globalfifo_dev *dev = filp->private_data; switch (cmd) { case MEM_CLEAR: mutex_lock(&dev->mutex); dev->current_len = 0; memset(dev->mem, 0, GLOBALFIFO_SIZE); mutex_unlock(&dev->mutex); printk(KERN_INFO "globalfifo is set to zero\n"); break; default: return -EINVAL; } return 0; } /** * Queries whether reading or writing to one or more file descriptors will block * @param[in] filp: File structure pointer * @param[in] wait: Polling table pointer * @return Returns a bitmask indicating whether non blocking read or write is possible */ static unsigned int globalfifo_poll(struct file *filp, struct poll_table_struct *wait) { unsigned int mask = 0; struct globalfifo_dev *dev = filp->private_data; mutex_lock(&dev->mutex); /* The process blocked by calling select can be blocked by r_wait and w_wait wake up */ poll_wait(filp, &dev->r_wait, wait); poll_wait(filp, &dev->w_wait, wait); if (dev->current_len != 0) { /* The device can read without blocking, and normal data can be read */ mask |= POLLIN | POLLRDNORM; } if (dev->current_len != GLOBALFIFO_SIZE) { /* The device can write without blocking */ mask |= POLLOUT | POLLWRNORM; } mutex_unlock(&dev->mutex); return mask; } /** * Reading device * @param[in] filp: File structure pointer * @param[out] buf: The user space memory address cannot be read or written directly in the kernel * @param[in] size: Bytes read * @param[in/out] ppos: The read position is equivalent to the offset of the file header * @return The number of bytes actually read is returned if successful, and the error code is returned if there is an error */ static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) { int ret = 0; unsigned long count = size; struct globalfifo_dev *dev = filp->private_data; DECLARE_WAITQUEUE(wait, current); mutex_lock(&dev->mutex); add_wait_queue(&dev->r_wait, &wait); while (dev->current_len == 0) { if (filp->f_flags & O_NONBLOCK) { ret = -EAGAIN; goto out; } __set_current_state(TASK_INTERRUPTIBLE); mutex_unlock(&dev->mutex); schedule(); if (signal_pending(current)) { ret = -ERESTARTSYS; goto out2; } mutex_lock(&dev->mutex); } if (count > dev->current_len) count = dev->current_len; /* Kernel space to user space cache copy */ if (copy_to_user(buf, dev->mem, count)) { ret = -EFAULT; goto out; } else { memcpy(dev->mem, dev->mem + count, dev->current_len - count); dev->current_len -= count; printk(KERN_INFO "read %lu bytes(s) from %u\n", count, dev->current_len); wake_up_interruptible(&dev->w_wait); ret = count; } out: mutex_unlock(&dev->mutex); out2: remove_wait_queue(&dev->r_wait, &wait); set_current_state(TASK_RUNNING); return ret; } /** * Write device * @param[in] filp: File structure pointer * @param[in] buf: The user space memory address cannot be read or written directly in the kernel * @param[in] size: Bytes written * @param[in/out] ppos: The write position is equivalent to the offset of the file header * @return The number of bytes actually written is returned if successful, and the error code is returned if there is an error */ static ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { int ret = 0; struct globalfifo_dev *dev = filp->private_data; DECLARE_WAITQUEUE(wait, current); mutex_lock(&dev->mutex); add_wait_queue(&dev->w_wait, &wait); while (dev->current_len == GLOBALFIFO_SIZE) { if (filp->f_flags & O_NONBLOCK) { ret = -EAGAIN; goto out; } __set_current_state(TASK_INTERRUPTIBLE); mutex_unlock(&dev->mutex); schedule(); if (signal_pending(current)) { ret = -ERESTARTSYS; goto out2; } mutex_lock(&dev->mutex); } if (count > GLOBALFIFO_SIZE - dev->current_len) count = GLOBALFIFO_SIZE - dev->current_len; /* Copy from user space cache to kernel space cache */ if (copy_from_user(dev->mem + dev->current_len, buf, count)) { ret = -EFAULT; goto out; } else { dev->current_len += count; printk(KERN_INFO "written %lu bytes(s) from %u\n", count, dev->current_len); wake_up_interruptible(&dev->r_wait); if (dev->async_queue) { kill_fasync(&dev->async_queue, SIGIO, POLL_IN); printk(KERN_INFO "%s kill SIGIO\n", __func__); } ret = count; } out: mutex_unlock(&dev->mutex); out2: remove_wait_queue(&dev->w_wait, &wait); set_current_state(TASK_RUNNING); return ret; } /** * File offset settings * @param[in] filp: File structure pointer * @param[in] offset: Offset value size * @param[in] orig: Start offset position * @return The current location of the file is returned if successful, and the error code is returned if there is an error */ static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig) { loff_t ret = 0; switch (orig) { case 0: /* Set offset from header position */ if (offset < 0) { ret = -EINVAL; break; } if ((unsigned int)offset > GLOBALFIFO_SIZE) { ret = -EINVAL; break; } filp->f_pos = (unsigned int)offset; ret = filp->f_pos; break; case 1: /* Set offset from current position */ if ((filp->f_pos + offset) > GLOBALFIFO_SIZE) { ret = -EINVAL; break; } if ((filp->f_pos + offset) < 0) { ret = -EINVAL; break; } filp->f_pos += offset; ret = filp->f_pos; break; default: ret = -EINVAL; break;; } return ret; } static const struct file_operations globalfifo_fops = { .owner = THIS_MODULE, .llseek = globalfifo_llseek, .read = globalfifo_read, .write = globalfifo_write, .unlocked_ioctl = globalfifo_ioctl, .open = globalfifo_open, .release = globalfifo_release, .poll = globalfifo_poll, .fasync = globalfifo_fasync, }; static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index) { int err, devno = MKDEV(globalfifo_major, index); /* Initialize cdev */ cdev_init(&dev->cdev, &globalfifo_fops); dev->cdev.owner = THIS_MODULE; /* Register device */ err = cdev_add(&dev->cdev, devno, 1); if (err) printk(KERN_NOTICE "Error %d adding globalfifo%d", err, index); } /* Driver module loading function */ static int __init globalfifo_init(void) { int ret; dev_t devno = MKDEV(globalfifo_major, 0); /* Get device number */ if (globalfifo_major) ret = register_chrdev_region(devno, 1, "globalfifo"); else { ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo"); globalfifo_major = MAJOR(devno); } if (ret < 0) return ret; /* Request memory */ globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL); if (!globalfifo_devp) { ret = -ENOMEM; goto fail_malloc; } globalfifo_setup_cdev(globalfifo_devp, 0); mutex_init(&globalfifo_devp->mutex); init_waitqueue_head(&globalfifo_devp->r_wait); init_waitqueue_head(&globalfifo_devp->w_wait); return 0; fail_malloc: unregister_chrdev_region(devno, 1); return ret; } module_init(globalfifo_init); /* Driver module unloading function */ static void __exit globalfifo_exit(void) { cdev_del(&globalfifo_devp->cdev); kfree(globalfifo_devp); /* Release device number */ unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); } module_exit(globalfifo_exit); MODULE_AUTHOR("MrLayfolk"); MODULE_LICENSE("GPL v2");
1.3.2 user space globalfifo driver verification
The user space application program outputs the signal value after receiving the signal sent by globalfifo.
#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <unistd.h> #define MAX_LEN 100 static void signalio_handler(int signum) { printf("receive a signal, signal number:%d\n", signum); } int main() { int fd, oflags; /* Open the device file in a non blocking manner */ fd = open("/dev/globalfifo", O_RDONLY | S_IRUSR | S_IWUSR); if (fd != -1) { /* Start signal driven mechanism */ signal(SIGIO, signalio_handler); //SIGIO signal installation input_handler() as a handler function fcntl(fd, F_SETOWN, getpid()); //Set this process as stdin_ Owner of fifeno document oflags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, oflags | FASYNC); //Set FASYNC while (1) { sleep(100); } } else { printf("device open failure\n"); } return 0; }
1.3.3 compilation test
(1) Compile the device driver ko and insert ko
$ make $ insmod globalfifo.ko
(2) Create device node
$ mknod /dev/globalfifo c 230 0
(3) Compile the user program and run it
$ gcc globalfifo_app.c $ ./a.out
(4) Write data to device driver, signalio_handler() will be called
$ echo "hello" > /dev/globalfifo $ echo "hello" > /dev/globalfifo $ echo "hello" > /dev/globalfifo $ ./a.out receive a signal, signal number:29 receive a signal, signal number:29 receive a signal, signal number:29