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: Linux device driver development details: Based on the latest Linux 4 0 kernel - Song Baohua - wechat reading (qq.com)
Character devices refer to those devices that must be accessed in serial order, such as touch screen, tape drive, mouse, etc. For users, use the file system's operation interfaces open(), close(), read(), write(), etc.
Linux device driver - polling operation
1.1 INTRODUCTION
In the user mode program, select () and poll () system calls can be used to query whether the device can be accessed without blocking. I/O multiplexing is realized through select() and poll().
I/O multiplexing allows us to check multiple file descriptors at the same time to see whether any one of them can perform I/O operations. We can use select() and poll() to check file descriptors on ordinary files, terminals, pseudo terminals, pipes, FIFO s, sockets, and some other types of character devices. Both system calls allow the process to either wait for the file descriptor to become ready or specify a timeout in the call.
In the kernel, poll() in the device driver will be called by select() and poll() in user mode.
1.2 polling programming in applications
1.2.1 select() function
The most widely used system call in the application program is the select() function. The prototype is:
/* According to POSIX.1-2001 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
The system call select() will block until one or more file descriptor sets become ready.
The parameters nfds, readfds, writefds and exceptfds specify the set of file descriptors to be checked by select().
Parameter readfds: a collection of file descriptors used to detect whether the input is ready.
Parameter writefds: a collection of file descriptors used to detect whether the output is ready.
Parameter exceptfds: a collection of file descriptors used to detect whether an exception occurs.
Parameter nfds: must be set to 1 greater than the maximum file descriptor contained in the three file descriptor sets.
All operations on the set of file descriptors are completed through four macros: FD_ZERO(),FD_SET(),FD_CLR() and FD_ISSET().
void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set);
FD_ZERO(): initialize the set pointed to by fdset to null.
FD_SET(): add the file descriptor fd to the set pointed by fdset.
FD_CLR(): remove the file descriptor fd from the set pointed to by fdset.
If the file descriptor fd is a member of the set pointed to by fdset, FD_ISSET() returns true.
Parameter timeout: used to set the upper limit of select() blocking time. The struct timeout data structure is defined as follows:
struct timeval { int tv_sec; int tv_usec; }
Both fields of timeout are set to 0. At this time, select() will not block, but simply poll the specified file descriptor set to see if there is a ready file descriptor and return it immediately.
select() multiplexes as follows:
When reading and writing for the first time, if any file meets the reading and writing requirements, select() returns directly;
When selecting () the second time, no files meet the read-write requirements. The process of selecting () is blocked and sleeps.
1.2.2 poll() function
The prototype function of pol() is similar to that of pol()
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll() provides a list of file descriptors and indicates the events of interest on each file descriptor.
Parameter fds: lists the file descriptors to be checked by poll(). This parameter is the pollfd structure array, which is defined as:
struct pollfd { int fd; /* file descriptor */ short events; /* requested events bit mask */ short revents; /* returned events bit mask */ };
Parameter nfds: Specifies the number of elements in the array fds.
Parameter timeout: the parameter timeout determines the blocking behavior of poll(), as follows.
If timeout equals − 1, poll() will block until one of the file descriptors listed in the fds array reaches the ready state (defined in the corresponding events field) or a signal is captured.
If timeout equals 0, poll() won't block - just a check to see which file descriptor is ready.
If timeout is greater than 0, poll() blocks timeout for milliseconds at most until one of the file descriptors listed by fds reaches the ready state, or until a signal is captured.
Return value of poll(): as the return value of the function, poll() will return one of the following situations.
Return − 1: indicates that an error has occurred. One possible error is EINTR, which indicates that the call was interrupted by a signal processing routine. (poll() will never resume automatically if interrupted by a signal processing routine.)
Return 0: indicates that the call timed out before any file descriptor becomes ready.
Returns a positive integer: indicates that one or more file descriptors are ready. The return value indicates the number of pollfd structures with non-zero events fields in the array fds.
1.2.3 epoll() function
When the number of multiplexed files is large and I/O traffic is frequent, select() and poll() are generally not suitable. In this case,
The performance of select() and poll() is poor, so epoll should be used.
User programming interfaces related to epoll:
epoll_create() is used to create an epoll handle, and size specifies how many FDS to listen for.
#include <sys/epoll.h> int epoll_create(int size);
When the epoll handle is created, it will also occupy an fd value. Therefore, after using the epoll, you need to call close() to close it.
epoll_ctl() is used to tell the kernel the type of event to listen for.
#include <sys/epoll.h>s int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
Parameter epfd: epoll_ The return value of the create() function.
Parameter op: indicates the action, including:
EPOLL_CTL_ADD: Register new fd reach epfd Yes. EPOLL_CTL_MOD: Modify registered fd Listening events. EPOLL_CTL_DEL: from epfd Delete one from fd.
Parameter fd: fd to be monitored
Parameter event: tells the kernel the type of event to listen for. struct epoll_ The event structure is as follows:
struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }
events can be the "or" of the following macros:
EPOLLIN: indicates that the corresponding file descriptor can be read.
EPOLLOUT: indicates that the corresponding file descriptor can be written.
EPOLLPRI: indicates that the corresponding file descriptor has urgent data readability (this should mean that there is socket out of band data).
EPOLLERR: indicates an error occurred in the corresponding file descriptor.
EPOLLHUP: indicates that the corresponding file descriptor is hung up.
EPOLLET: set epoll to Edge Triggered mode, which is relative to Level Triggered. LT (Level Triggered) is the default working mode. In LT, the kernel tells the user whether an fd is ready, and then the user can perform I/O operations on the ready fd. However, if the user does not do anything, the event will not be lost, and ET (Edge Triggered) works in a high-speed mode. In this mode, when fd is not ready and becomes ready, the kernel tells the user through epoll, and then it will assume that the user knows that fd is ready and will not send more ready notifications for that fd.
EPOLLONESHOT: it means one-time monitoring. After listening to this event, if you need to continue to monitor this fd, you need to add this fd to the epoll queue again.
epoll_ The wait() function is used to wait for the generation of events:
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
Parameter events: output parameters, which are used to get the collection of events from the kernel.
Parameter maxevents: tells the kernel how many events can be received at most this time. Maxevents cannot be greater than creating epoll_ size at create().
Parameter timeout: timeout (in milliseconds, 0: return immediately; - 1: permanent wait)
Function return value: the number of events to be processed. If 0 is returned, it indicates that it has timed out.
1.3 polling programming in device driver
1.3.1 prototype of poll() function
File in device driver_ Prototype of poll() function in operations data structure:
unsigned int(*poll)(struct file *filp, struct poll_table *wait);
Parameter filp: file structure pointer
Parameter wait: polling table pointer
Return value: a mask indicating whether non blocking read and write access to the device can be performed.
1.3.2 poll_wait() function
poll_ The wait() function is used to send a message to poll_table registers the waiting queue. The prototype of the function is:
void poll_wait(struct file * filp, wait_queue_head_t * queue, poll_table *wait);
poll_ The wait function does not block and wait for something to happen. It only adds the current process to the poll_table specified by the wait parameter. The actual function is to make the waiting queue corresponding to the wake-up parameter queue wake up the process sleeping due to select().
The driver poll() function should return the obtainable status of device resources, that is, the bit or result of macros such as POLLIN, POLLOUT, POLLPRI, POLLERR and POLLNVAL. The meaning of each macro indicates a state of the device. For example, POLLIN (defined as 0x0001) means that the device can read without blocking, and POLLOUT (defined as 0x0004) means that the device can write without blocking.
1.3.3 call template for poll() function
Typical template of poll() function:
static unsigned int xxx_poll(struct file *filp, poll_table *wait) { unsigned int mask = 0; struct xxx_dev *dev = filp->private_data; /* Get device structure pointer */ ... poll_wait(filp, &dev->r_wait, wait); /* Join read wait queue */ poll_wait(filp, &dev->w_wait, wait); /* Join write waiting queue*/ if (...) /* readable */ mask |= POLLIN | POLLRDNORM; /* Marking data available (user readable) */ if (...) /* Writable */ mask |= POLLOUT | POLLWRNORM; /* Mark data can be written */ ... return mask; }
1.4 global FIFO driver supporting polling operation
1.4.1 kernel device driver globalfifo
Add the poll() function to the code of globalfifo.
The complete code is as follows:
#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> /* It is unreasonable to directly use the immediate number as the command, which is tentative */ #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 globalfifo_dev *globalfifo_devp; 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) { 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 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->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); 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, }; 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");
Makefile:
KVERS = $(shell uname -r) # Kernel modules obj-m += globalfifo_poll.o # Specify flags for the module compilation. #EXTRA_CFLAGS=-g -O0 build: kernel_modules kernel_modules: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules clean: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
1.4.2 user space authentication global FIFO device polling
Write an application in user space and call select() to monitor the read-write status of global FIFO.
The complete code is as follows:
#include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #define FIFO_CLEAR 0x1 #define BUFFER_LEN 20 int main(void) { int fd, num; char rd_ch[BUFFER_LEN]; fd_set rfds, wfds; /* Read / write file descriptor set */ /* Open the device file in a non blocking manner */ fd = open("/dev/globalfifo", O_RDONLY | O_NONBLOCK); if (fd != -1) { /* FIFO Clear 0 */ if (ioctl(fd, FIFO_CLEAR) < 0) printf("ioctl command failed!\n"); while (1) { sleep(2); FD_ZERO(&rfds); //Initialize the collection pointed to by rfds to null FD_ZERO(&wfds); //Initialize the collection pointed to by wfds to null FD_SET(fd, &rfds); //Add the file descriptor fd to the collection pointed to by rfds FD_SET(fd, &wfds); //Add the file descriptor fd to the collection pointed to by wfds select(fd + 1, &rfds, &wfds, NULL, NULL); /* Data available */ if (FD_ISSET(fd, &rfds)) printf("Poll monitor: can be read!\n"); /* Data writable */ if (FD_ISSET(fd, &wfds)) printf("Poll monitor: can be written!\n"); } } else { printf("Device open failure\n"); } return 0; }
1.4.3 compilation test
Compile the device driver and load ko, then create a character device node:
$ make $ insmod globalfifo_poll.ko $ mknod /dev/globalfifo c 230 0
Compile the user mode application and run:
$ gcc app_poll.c $ ./a.out Poll monitor: can be written! Poll monitor: can be written! Poll monitor: can be written!
At the beginning of operation, the device can only write, and then let the device space write some characters, the device becomes readable and writable, and then read the characters in the device space, and the device can only write.
$ ./a.out Poll monitor: can be written! Poll monitor: can be written! Poll monitor: can be written! $ echo "hello" > /dev/globalfifo Poll monitor: can be written! Poll monitor: can be read! Poll monitor: can be written! Poll monitor: can be read! $ cat /dev/globalfifo hello Poll monitor: can be written! Poll monitor: can be written!