阻塞I/O與非阻塞I/O是設備訪問的兩種不一樣模式,驅動程序能夠靈活的支持用戶空間對設備的這兩種訪問方式。node
select
系統調用查詢是否能夠對設備進行無阻塞的訪問最終會引起設備驅動中poll
函數執行struct __wait_queue_head { spinlock_t lock; //自旋鎖,用來對task_list鏈表起保護做用,實現了等待隊列的互斥訪問 struct list_head task_list; //用來存放等待的進程, }; typedef struct __wait_queue_head wait_queue_head_t;
/* 定義等待隊列 */ wait_queue_head_t wait; /* 初始化等待隊列 */ init_waitqueue_head(&wait); /* 定義並初始化等待隊列 */ #define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name) /* 添加等待隊列,將等待隊列元素wait添加到等待隊列隊頭q所指向的等待隊列鏈表中 */ 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); /* 等待事件,在等待隊列中睡眠直到condition爲真 */ /* 注意: @queue:做爲等待隊列頭的等待隊列被喚醒 @condition:必須爲真,不然就阻塞 @timeout:相較於condition,timeout有更高的優先級 */ wait_event(wq, condition); wait_evevt_timeout(wq, condition, timeout); wait_event_interruptible(wq, condition); wait_event_interruptible_timeout(wq, condition, timeout); /* 睡眠,其中sleep_on做用是將目前進程的狀態設置爲TASK_UNINTERRUPTIBLE,直到資源可用,q引導的等待隊列被喚醒; interruptible_sleep_on是將進程狀態設置爲TASK_INTERRUPTIBLE */ sleep_on(wait_queue_head_t *q); interruptible_sleep_on(wait_queue_head_t *q); /* 喚醒等待隊列,可喚醒處於TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE狀態的進程 */ #define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL) /* 只能喚醒處於TASK_INTERRUPTIBLE狀態的進程 */ #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
對線程的控制比如一個老闆招募了一個僱工幹活。linux
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/uaccess.h> #include <linux/slab.h> #include <linux/miscdevice.h> #include <linux/wait.h> #include "cdev_test_v4.h" #define BUF_SIZR 1024 #define MAJOR_NUM 168 #define MINOR_NUM 0 struct demo_cdev { char *buffer; //my private memory int value; struct miscdevice *mdev; wait_queue_head_t queue_head; bool is_empty; }; /* step1: malloc memory for char device */ struct demo_cdev *demo_dev; static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { int ret = -1; int read_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); /* determine whether user has finished reading data */ if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { read_bytes = BUF_SIZR - *pos; } else { read_bytes = size; } if (demo->is_empty) { /* make this process sleep */ wait_event_interruptible(demo->queue_head, !(demo->is_empty)); } ret = copy_to_user(buf, kbuf, read_bytes); if (ret != 0) { return -EFAULT; } *pos += read_bytes; return read_bytes; } static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) { int ret; int write_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { write_bytes = BUF_SIZR - *pos; } else { write_bytes = size; } ret = copy_from_user(kbuf, buf, write_bytes); if (ret != 0) { return -EFAULT; } demo->is_empty = false; /* wake up this process */ wake_up(&demo->queue_head); *pos += write_bytes; return write_bytes; } static int demo_open(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); file->private_data = (void *)demo_dev; return 0; } static int demo_release(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); return 0; } static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; struct demo_cdev *demo = file->private_data; switch(cmd) { case CDEV_TEST_V4_CLEAN: memset(demo->buffer, 0x00, BUF_SIZR); printk(KERN_INFO "cmd: clean\n"); break; case CDEV_TEST_V4_GETVAL: put_user(demo->value, (int *)arg); printk(KERN_INFO "cmd: getval\n"); break; case CDEV_TEST_V4_SETVAL: demo->value = (int)arg; printk(KERN_INFO "cmd: setval\n"); break; default: break; } return (long)ret; } static struct file_operations demo_operation= { .open = demo_open, .release = demo_release, .read = demo_read, .write = demo_write, .unlocked_ioctl = demo_ioctl, }; static struct miscdevice misc_struct = { .minor = MISC_DYNAMIC_MINOR, .name = "misc_dev", .fops = &demo_operation, }; static int __init demo_init(void) { int ret = -1; printk(KERN_INFO "Enter: %s\n", __func__); demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL); if (!demo_dev) { printk(KERN_ERR "failed to malloc demo_dev\n"); ret = -ENOMEM; goto ERROR_MALLOC_DEMODEV; } demo_dev->value = 1; demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL); if (!demo_dev->buffer) { printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR); ret = -ENOMEM; goto ERROR_MALLOC_BUFFER; } memset(demo_dev->buffer, 0x00, BUF_SIZR); demo_dev->is_empty = true; init_waitqueue_head(&demo_dev->queue_head); demo_dev->mdev = &misc_struct; ret = misc_register(demo_dev->mdev); if (ret < 0) { printk(KERN_ERR "failed to register misc\n"); goto ERROR_MISC; } printk(KERN_INFO "demo char device init done\n"); return 0; ERROR_MISC: kfree(demo_dev->buffer); demo_dev->buffer = NULL; ERROR_MALLOC_BUFFER: kfree(demo_dev); demo_dev = NULL; ERROR_MALLOC_DEMODEV: return ret; } static void __exit demo_exit(void) { printk(KERN_INFO "Enter: %s\n", __func__); misc_deregister(demo_dev->mdev); kfree(demo_dev->buffer); demo_dev->buffer = NULL; kfree(demo_dev); demo_dev = NULL; printk(KERN_INFO "demo char device exit done\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_AUTHOR("Qi Han"); MODULE_LICENSE("GPL");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod wait_queue.ko [sudo] hq 的密碼: hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev # 此處會阻塞直到有數據 hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev Peng fei tan - # 寫入數據喚醒阻塞的進程 hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "Peng fei tan" -> /dev/misc_dev hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$
BUF_SIZE
大小個字節的數據,那麼能夠正常使用下述讀寫方法。假如單次讀寫數據量超過了BUF_SIZE
大小個數據量的話將會致使數據沒法正常地寫入與讀出,主要是由於偏移量的問題,即*pos += read_bytes;
,當讀取超過1024個字節數據的時候,偏移量就會大於1024,那麼會致使數組越界,所以咱們可使用環形隊列FIFO來儲存數據,當偏移量pos大於1024的時候會從新從0開始偏移#define BUF_SIZR 1024 struct demo_cdev { char *buffer; //my private memory ... }; static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { int ret = -1; int read_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; /* determine whether user has finished reading data */ /* 判斷當前讀取的數據量是否大於buffer所容納的數據量 */ if (*pos >= BUF_SIZR) { return 0; } /* 判斷當前讀取的數據量是否大於buffer中剩餘的數據量 */ if (size > (BUF_SIZR - *pos)) { read_bytes = BUF_SIZR - *pos; } else { read_bytes = size; } ret = copy_to_user(buf, kbuf, read_bytes); if (ret != 0) { return -EFAULT; } *pos += read_bytes; return read_bytes; } static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) { int ret; int write_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; /* 判斷當前寫入的數據量是否大於buffer所容納的數據量 */ if (*pos >= BUF_SIZR) { return 0; } /* 判斷當前寫入的數據量是否大於buffer所剩餘的數據量 */ if (size > (BUF_SIZR - *pos)) { write_bytes = BUF_SIZR - *pos; } else { write_bytes = size; } ret = copy_from_user(kbuf, buf, write_bytes); if (ret != 0) { return -EFAULT; } *pos += write_bytes; return write_bytes; }