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.
Blocking and non blocking I/O
1.1 INTRODUCTION
Blocking operation: when performing device operation, if resources cannot be obtained, suspend the process until the operable conditions are met. The suspended process goes to sleep and is removed from the scheduling queue until the waiting conditions are met.
Non blocking operation: if the process does not suspend when it cannot operate the device, it will poll or give up until it can operate.
The difference between blocking and non blocking user mode access:
1.2 implementation code
Take the code of reading one character of serial port in blocking and non blocking mode as an example.
The blocking mode has no o when opening the file_ Nonblock flag.
char buf; fd = open("/dev/ttyS1", O_RDWR); ... res = read(fd, &buf, 1); /* The serial port returns only when there is an input */ if (res == 1) printf("%c\n", buf);
Non blocking read a serial character:
char buf; fd = open("/dev/ttyS1", O_RDWR | O_NONBLOCK); ... while(read(fd, &buf, 1) != 1) /* There is no return on the serial port. Cycle to try to read the serial port */ continue; printf("%c\n", buf);
In addition to specifying the blocking or non blocking mode when opening a file, after opening a file, you can change the reading and writing mode through ioctl() and fcntl(), such as changing from blocking to non blocking or from non blocking to blocking.
1.3 waiting queue (key)
In the Linux driver, the Wait Queue can be used to wake up the blocking process.
As a basic unit in the Linux kernel, waiting queue takes queue as the data structure and is closely combined with the scheduling mechanism. It can be used to synchronize the access to system resources.
1.3.1 relevant operations
1) Define the waiting queue header:
typedef struct __wait_queue_head wait_queue_head_t; wait_queue_head_t my_queue;
2) Initialize wait queue header:
init_waitqueue_head(&my_queue);
Macro DECLARE_WAIT_QUEUE_HEAD() can define and initialize the waiting queue header:
#define DECLARE_WAIT_QUEUE_HEAD(name) \ wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
3) Define wait queue elements
Define and initialize a wait queue element named name
#define DECLARE_WAITQUEUE(name, tsk) \ wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
4) Add / remove wait queue
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait); void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
5) Wait event
Parameter wq: the queue as the head of the waiting queue is awakened
Parameter condition: condition conditions must be met
Parameter timeout: wait timeout, in jifffy. When the timeout arrives, it will be returned regardless of whether the condition is satisfied or not.
#define wait_event(wq, condition) #define wait_event_timeout(wq, condition, timeout) #define wait_event_interruptible(wq, condition) / / can be interrupted by signal #define wait_event_interruptible_timeout(wq, condition, timeout)
6) Wake up queue
Wake up all threads in the queue with queue as the head of the waiting queue.
#define wake_up(queue) #define wake_up_interruptible(queue)
wake_up() should be the same as wait_event() or wait_event_timeout() is used in pairs;
wake_up_interruptible() should be the same as wait_event_interruptible() or wait_event_interruptible_timeout() is used in pairs.
wake_up() wakes up the TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE process; wake_up_interruptible can only wake up in task_ An intermittent process.
7) Sleep on waiting queue
sleep_on(wait_queue_head_t *q); interruptible_sleep_on(wait_queue_head_t *q);
sleep_ The on() function is used to set the status of the current process to task_ And define a waiting queue element, and then hang it to the two-way linked list pointed to by q in the head of the waiting queue until the resources are available and the process pointed to by q queue is awakened.
interruptible_sleep_on() and sleep_ Similar to the on() function, it is used to set the status of the current process to task_ And define a waiting queue element, and then attach it to the queue pointed by q until the resource is available (the waiting queue directed by q is awakened) or the process receives a signal.
sleep_ The on() function should be the same as wake_up() used in pairs, interruptible_sleep_on() should be the same as wake_up_interruptible() is used in pairs.
1.3.2 use of formwork
Use the template of the waiting queue to judge whether the device is writable. If it is not writable and I/O is blocked, the process will sleep and suspend the waiting queue.
static ssize_t xxx_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) { ... DECLARE_WAITQUEUE(wait, current); /* Define wait queue elements */ add_wait_queue(&xxx_wait, &wait); /* Add element to waiting queue */ /* Wait for device buffer to be writable */ do { avail = device_writable(...); if (avail < 0) { if (file->f_flags & O_NONBLOCK) { /* Non blocking */ ret = -EAGAIN; goto out; } __set_current_state(TASK_INTERRUPTIBLE); /* Change process state */ schedule(); /* Schedule other processes to execute */ if (signal_pending(current)) { /* If it's a wake-up signal */ ret = -ERESTARTSYS; goto out; } } } while (avail < 0); /* Write device buffer */ device_write(...); out: remove_wait_queue(&xxx_wait, &wait); /* Move element out of XXX_ Queue guided by wait */ set_current_state(TASK_RUNNING); /* Set the process status to TASK_RUNNING */ return ret; }
This code is very important for understanding process state switching. The code has the following points:
1) In case of non blocking access (O_NONBLOCK by the device), when the device is busy, it directly returns - EAGAIN.
2) If access is blocked, call__ set_ current_ State (task_intermittent) switches the process state and schedules the execution of other processes through schedule().
3) When the task wakes up_ Intermittent (light sleep), so the wake-up may be a signal, so go through signal first_ Pending determines whether it is a signal wake-up. If so, it immediately returns - erestatsys.
DECLARE_WAITQUEUE and add_ wait_ The effect of the two actions of queue is shown in the following figure:
wait_queue_head_t points to the newly defined wait on the linked list_ The queue element is inserted, and the new element is bound with a task_struct data structure (the current of xxx_write is also the reason why DECLARE_WAITQUEUE uses "current" as the parameter).
1.4 global FIFO device driver supporting blocking operation
The global memory of globalmem is regarded as a FIFO. Only when there is data in the FIFO (a process writes the data to the FIFO and no reading process reads empty), the reading process reads the data, and after reading the data, it is removed from the global memory of globalmem; Only when the FIFO is not full (some space is not written or the read process reads data from the FIFO after it is full), the write process can write data to the FIFO.
In global FIFO, reading FIFO will wake up the process of writing FIFO (if the previous FIFO is just full), and writing FIFO will also wake up the process of reading FIFO (if the previous FIFO is empty).
Full 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> /* 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; } /** * 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, }; 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.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
Compile, insert ko, and test:
$ make $ insmod globalfifo.ko $ mknod /dev/globalfifo c 230 0 //Create device node $ cat /dev/globalfifo & //The read process runs in the background $ echo "I want to be" > /dev/globalfifo //The write process writes data to FIFO $ I want to be //The cat read process prints immediately