參考資料:
《正點原子Linux驅動教程》
等待佇列可參考:https://www.cnblogs.com/lethe1203/p/18092974
阻塞IO:
阻塞IO簡單理解:使用者程序透過read函式讀取核心相應裝置的file_operations.read函式,舉個例子:
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/fs.h> 4 #include <linux/uaccess.h> 5 6 #define DEVICE_NAME "blockio_demo" 7 #define BUF_SIZE 1024 8 9 static char buffer[BUF_SIZE] = "Hello, this is a block IO demo!\n"; 10 static int major_num; 11 12 static ssize_t blockio_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) 13 { 14 ssize_t bytes_read = 0; 15 16 // Simulate blocking IO by adding a delay 17 msleep(2000); // Block for 2000 milliseconds (2 seconds) 18 19 if (*ppos >= BUF_SIZE) { 20 return 0; // EOF 21 } 22 23 bytes_read = min(count, (size_t)(BUF_SIZE - *ppos)); 24 25 if (copy_to_user(user_buf, buffer + *ppos, bytes_read) != 0) { 26 return -EFAULT; // Error copying data to user space 27 } 28 29 *ppos += bytes_read; 30 31 return bytes_read; 32 } 33 34 static struct file_operations fops = { 35 .read = blockio_read, 36 }; 37 38 static int __init blockio_init(void) 39 { 40 major_num = register_chrdev(0, DEVICE_NAME, &fops); 41 42 if (major_num < 0) { 43 printk(KERN_ALERT "Failed to register a major number\n"); 44 return major_num; 45 } 46 47 printk("Block IO demo module loaded with major number: %d\n", major_num); 48 49 return 0; 50 } 51 52 static void __exit blockio_exit(void) 53 { 54 unregister_chrdev(major_num, DEVICE_NAME); 55 printk("Block IO demo module unloaded\n"); 56 } 57 58 module_init(blockio_init); 59 module_exit(blockio_exit); 60 61 MODULE_LICENSE("GPL"); 62 MODULE_AUTHOR("lethe1203"); 63 MODULE_DESCRIPTION("block IO");
在第17行中,使用了msleep函式進行休眠,應用程式如果read該裝置節點,單CPU的情況下去看CPU佔用情況,發現CPU利用率其實很低的,這就是因為msleep進入了休眠。2s休眠結束程序繼續工作
非阻塞IO:
非阻塞IO工作原理如下:
應用程式使用非阻塞訪問方式從裝置讀取資料,當裝置不可用或者資料未準備好時會立即向核心返回一個錯誤碼,表示資料讀取失敗。應用程式會再次重新讀取資料,這樣一直往復迴圈,直到資料讀取成功
在file_operations中有一個poll函式,該函式就是用來實現非阻塞訪問方式的
同時,應用程式非阻塞方式訪問可呼叫select、poll、epoll等函式
正點原子imx6ull測試demo:
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/of_irq.h> #include <linux/irq.h> #include <linux/wait.h> #include <linux/poll.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #define IMX6UIRQ_CNT 1 /* 裝置號個數 */ #define IMX6UIRQ_NAME "noblockio" /* 名字 */ #define KEY0VALUE 0X01 /* KEY0按鍵值 */ #define INVAKEY 0XFF /* 無效的按鍵值 */ #define KEY_NUM 1 /* 按鍵數量 */ /* 中斷IO描述結構體 */ struct irq_keydesc { int gpio; /* gpio */ int irqnum; /* 中斷號 */ unsigned char value; /* 按鍵對應的鍵值 */ char name[10]; /* 名字 */ irqreturn_t (*handler)(int, void *); /* 中斷服務函式 */ }; /* imx6uirq裝置結構體 */ struct imx6uirq_dev{ dev_t devid; /* 裝置號 */ struct cdev cdev; /* cdev */ struct class *class; /* 類 */ struct device *device; /* 裝置 */ int major; /* 主裝置號 */ int minor; /* 次裝置號 */ struct device_node *nd; /* 裝置節點 */ atomic_t keyvalue; /* 有效的按鍵鍵值 */ atomic_t releasekey; /* 標記是否完成一次完成的按鍵,包括按下和釋放 */ struct timer_list timer;/* 定義一個定時器*/ struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按鍵init述陣列 */ unsigned char curkeynum; /* 當前init按鍵號 */ wait_queue_head_t r_wait; /* 讀等待佇列頭 */ }; struct imx6uirq_dev imx6uirq; /* irq裝置 */ /* @description : 中斷服務函式,開啟定時器 * 定時器用於按鍵消抖。 * @param - irq : 中斷號 * @param - dev_id : 裝置結構。 * @return : 中斷執行結果 */ static irqreturn_t key0_handler(int irq, void *dev_id) { struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id; dev->curkeynum = 0; dev->timer.data = (volatile long)dev_id; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定時 */ return IRQ_RETVAL(IRQ_HANDLED); } /* @description : 定時器服務函式,用於按鍵消抖,定時器到了以後 * 再次讀取按鍵值,如果按鍵還是處於按下狀態就表示按鍵有效。 * @param - arg : 裝置結構變數 * @return : 無 */ void timer_function(unsigned long arg) { unsigned char value; unsigned char num; struct irq_keydesc *keydesc; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg; num = dev->curkeynum; keydesc = &dev->irqkeydesc[num]; value = gpio_get_value(keydesc->gpio); /* 讀取IO值 */ if(value == 0){ /* 按下按鍵 */ atomic_set(&dev->keyvalue, keydesc->value); } else{ /* 按鍵鬆開 */ atomic_set(&dev->keyvalue, 0x80 | keydesc->value); atomic_set(&dev->releasekey, 1); /* 標記鬆開按鍵,即完成一次完整的按鍵過程 */ } /* 喚醒程序 */ if(atomic_read(&dev->releasekey)) { /* 完成一次按鍵過程 */ /* wake_up(&dev->r_wait); */ wake_up_interruptible(&dev->r_wait); } } /* * @description : 按鍵IO初始化 * @param : 無 * @return : 無 */ static int keyio_init(void) { unsigned char i = 0; char name[10]; int ret = 0; imx6uirq.nd = of_find_node_by_path("/key"); if (imx6uirq.nd== NULL){ printk("key node not find!\r\n"); return -EINVAL; } /* 提取GPIO */ for (i = 0; i < KEY_NUM; i++) { imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i); if (imx6uirq.irqkeydesc[i].gpio < 0) { printk("can't get key%d\r\n", i); } } /* 初始化key所使用的IO,並且設定成中斷模式 */ for (i = 0; i < KEY_NUM; i++) { memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name)); /* 緩衝區清零 */ sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 組合名字 */ gpio_request(imx6uirq.irqkeydesc[i].gpio, name); gpio_direction_input(imx6uirq.irqkeydesc[i].gpio); imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i); } /* 申請中斷 */ imx6uirq.irqkeydesc[0].handler = key0_handler; imx6uirq.irqkeydesc[0].value = KEY0VALUE; for (i = 0; i < KEY_NUM; i++) { ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq); if(ret < 0){ printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum); return -EFAULT; } } /* 建立定時器 */ init_timer(&imx6uirq.timer); imx6uirq.timer.function = timer_function; /* 初始化等待佇列頭 */ init_waitqueue_head(&imx6uirq.r_wait); return 0; } /* * @description : 開啟裝置 * @param - inode : 傳遞給驅動的inode * @param - filp : 裝置檔案,file結構體有個叫做private_data的成員變數 * 一般在open的時候將private_data指向裝置結構體。 * @return : 0 成功;其他 失敗 */ static int imx6uirq_open(struct inode *inode, struct file *filp) { filp->private_data = &imx6uirq; /* 設定私有資料 */ return 0; } /* * @description : 從裝置讀取資料 * @param - filp : 要開啟的裝置檔案(檔案描述符) * @param - buf : 返回給使用者空間的資料緩衝區 * @param - cnt : 要讀取的資料長度 * @param - offt : 相對於檔案首地址的偏移 * @return : 讀取的位元組數,如果為負值,表示讀取失敗 */ static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret = 0; unsigned char keyvalue = 0; unsigned char releasekey = 0; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; if (filp->f_flags & O_NONBLOCK) { /* 非阻塞訪問 */ if(atomic_read(&dev->releasekey) == 0) /* 沒有按鍵按下,返回-EAGAIN */ return -EAGAIN; } else { /* 阻塞訪問 */ /* 加入等待佇列,等待被喚醒,也就是有按鍵按下 */ ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); if (ret) { goto wait_error; } } keyvalue = atomic_read(&dev->keyvalue); releasekey = atomic_read(&dev->releasekey); if (releasekey) { /* 有按鍵按下 */ if (keyvalue & 0x80) { keyvalue &= ~0x80; ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue)); } else { goto data_error; } atomic_set(&dev->releasekey, 0);/* 按下標誌清零 */ } else { goto data_error; } return 0; wait_error: return ret; data_error: return -EINVAL; } /* * @description : poll函式,用於處理非阻塞訪問 * @param - filp : 要開啟的裝置檔案(檔案描述符) * @param - wait : 等待列表(poll_table) * @return : 裝置或者資源狀態, */ unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait) { unsigned int mask = 0; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; poll_wait(filp, &dev->r_wait, wait); /* 將等待佇列頭新增到poll_table中 */ if(atomic_read(&dev->releasekey)) { /* 按鍵按下 */ mask = POLLIN | POLLRDNORM; /* 返回PLLIN */ } return mask; } /* 裝置操作函式 */ static struct file_operations imx6uirq_fops = { .owner = THIS_MODULE, .open = imx6uirq_open, .read = imx6uirq_read, .poll = imx6uirq_poll, }; /* * @description : 驅動入口函式 * @param : 無 * @return : 無 */ static int __init imx6uirq_init(void) { /* 1、構建裝置號 */ if (imx6uirq.major) { imx6uirq.devid = MKDEV(imx6uirq.major, 0); register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME); } else { alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME); imx6uirq.major = MAJOR(imx6uirq.devid); imx6uirq.minor = MINOR(imx6uirq.devid); } /* 2、註冊字元裝置 */ cdev_init(&imx6uirq.cdev, &imx6uirq_fops); cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT); /* 3、建立類 */ imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME); if (IS_ERR(imx6uirq.class)) { return PTR_ERR(imx6uirq.class); } /* 4、建立裝置 */ imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME); if (IS_ERR(imx6uirq.device)) { return PTR_ERR(imx6uirq.device); } /* 5、始化按鍵 */ atomic_set(&imx6uirq.keyvalue, INVAKEY); atomic_set(&imx6uirq.releasekey, 0); keyio_init(); return 0; } /* * @description : 驅動出口函式 * @param : 無 * @return : 無 */ static void __exit imx6uirq_exit(void) { unsigned i = 0; /* 刪除定時器 */ del_timer_sync(&imx6uirq.timer); /* 刪除定時器 */ /* 釋放中斷 */ for (i = 0; i < KEY_NUM; i++) { free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq); gpio_free(imx6uirq.irqkeydesc[i].gpio); } cdev_del(&imx6uirq.cdev); unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT); device_destroy(imx6uirq.class, imx6uirq.devid); class_destroy(imx6uirq.class); } module_init(imx6uirq_init); module_exit(imx6uirq_exit); MODULE_LICENSE("GPL");
應用測試demo:
#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" #include "poll.h" #include "sys/select.h" #include "sys/time.h" #include "linux/ioctl.h" /* * @description : main主程式 * @param - argc : argv陣列元素個數 * @param - argv : 具體引數 * @return : 0 成功;其他 失敗 */ int main(int argc, char *argv[]) { int fd; int ret = 0; char *filename; struct pollfd fds; fd_set readfds; struct timeval timeout; unsigned char data; if (argc != 2) { printf("Error Usage!\r\n"); return -1; } filename = argv[1]; fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞訪問 */ if (fd < 0) { printf("Can't open file %s\r\n", filename); return -1; } while (1) { FD_ZERO(&readfds); FD_SET(fd, &readfds); /* 構造超時時間 */ timeout.tv_sec = 0; timeout.tv_usec = 500000; /* 500ms */ ret = select(fd + 1, &readfds, NULL, NULL, &timeout); switch (ret) { case 0: /* 超時 */ /* 使用者自定義超時處理 */ break; case -1: /* 錯誤 */ /* 使用者自定義錯誤處理 */ break; default: /* 可以讀取資料 */ if(FD_ISSET(fd, &readfds)) { ret = read(fd, &data, sizeof(data)); if (ret < 0) { /* 讀取錯誤 */ } else { if (data) printf("key value=%d\r\n", data); } } break; } } close(fd); return ret; }