在Linux驅動程式編寫過程中,裝置阻塞/非阻塞讀寫是一種非常重要的技術。它可以實現高效的資料傳輸和事件處理,提高系統的效能和響應速度。在本文中,我們將深入探討Linux驅動技術(五) _裝置阻塞/非阻塞讀寫的實作原理與相關技術。
#等待佇列是核心中實現進程調度的一個十分重要的資料結構,其任務是維護一個鍊錶,鍊錶中每一個節點都是一個PCB(進程控制區塊),核心會將PCB掛在等待佇列中的所有行程都調度為睡眠狀態,直到某個喚醒的條件發生。應用層的阻塞IO與非阻塞IO的使用我已經在Linux I/O多路復用一文中討論過了,本文主要討論驅動中怎麼實現對設備IO的阻塞與非阻塞讀寫。顯然,實作這種與阻塞相關的機制要用到等待佇列機制。本文的核心原始碼使用的是3.14.0版本
#當我們讀寫設備檔案的IO時,最終會回調驅動中對應的接口,而這些接口也會出現在讀寫設備進程的進程(內核)空間中,如果條件不滿足,介面函數使進程進入睡眠狀態,即使讀寫設備的使用者進程進入了睡眠,也就是我們常說的發生了阻塞。 In a word,讀寫裝置檔案阻塞的本質是驅動在驅動程式中實現對裝置檔案的阻塞,其讀寫的流程可概括如下:
//定义等待队列头 wait_queue_head_t waitq_h; //初始化,等待队列头 init_waitqueue_head(wait_queue_head_t *q); //或 //定义并初始化等待队列头 DECLARE_WAIT_QUEUE_HEAD(waitq_name);
在上面的幾個選擇中,最後一種會直接定義並初始化一個等待頭,但是如果在模組內使用全域變數傳參,用著並不方便,具體用哪種看需求。
我們可以追一下原始碼,看看上面這幾行都做了什麼:
//include/linux/wait.h 35 struct __wait_queue_head { 36 spinlock_t lock; 37 struct list_head task_list; 38 }; 39 typedef struct __wait_queue_head wait_queue_head_t;
「
#wait_queue_head_t
–36–>這個隊列用的自旋鎖定
–27–>將整個隊列」字串」在一起的紐帶」
#然後我們看一下初始化的巨集:
55 #define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \ 56 .lock = __SPIN_LOCK_UNLOCKED(name.lock), \ 57 .task_list = { &(name).task_list, &(name).task_list } } 58 59 #define DECLARE_WAIT_QUEUE_HEAD(name) \ 60 wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
「
#DECLARE_WAIT_QUEUE_HEAD()
–60–>根據傳入的字串name,建立一個名為name的等待佇列頭
–57–>初始化上述task_list域,竟然沒有用核心標準的初始化宏,無語。 。 。」
為等待佇列新增事件,即進程進入睡眠狀態直到condition為真才回傳。 **_interruptible的版本版本表示睡眠可中斷,_timeout**版本表示逾時版本,超時就會返回,這種命名規範在內核API中隨處可見。
void wait_event(wait_queue_head_t *waitq_h,int condition); void wait_event_interruptible(wait_queue_head_t *waitq_h,int condition); void wait_event_timeout(wait_queue_head_t *waitq_h,int condition); void wait_event_interruptible_timeout(wait_queue_head_t *waitq_h,int condition);
這可是等待佇列的核心,我們來看看
“
wait_event
└── wait_event
└── _wait_event
├── abort_exclusive_wait
├── finish_wait
├── prepare_to_wait_event
└── ___wait_is_interruptible”
244 #define wait_event(wq, condition) \ 245 do { \ 246 if (condition) \ 247 break; \ 248 __wait_event(wq, condition); \ 249 } while (0)
“
wait_event
–246–>如果condition为真,立即返回
–248–>否则调用__wait_event”
194 #define ___wait_event(wq, condition, state, exclusive, ret, cmd) \ 195 ({ \ 206 for (;;) { \ 207 long __int = prepare_to_wait_event(&wq, &__wait, state);\ 208 \ 209 if (condition) \ 210 break; \ 212 if (___wait_is_interruptible(state) && __int) { \ 213 __ret = __int; \ 214 if (exclusive) { \ 215 abort_exclusive_wait(&wq, &__wait, \ 216 state, NULL); \ 217 goto __out; \ 218 } \ 219 break; \ 220 } \ 222 cmd; \ 223 } \ 224 finish_wait(&wq, &__wait); \ 225 __out: __ret; \ 226 })
“
___wait_event
–206–>死循环的轮询
–209–>如果条件为真,跳出循环,执行finish_wait();进程被唤醒
–212–>如果进程睡眠的方式是interruptible的,那么当中断来的时候也会abort_exclusive_wait被唤醒
–222–>如果上面两条都不满足,就会回调传入的schedule(),即继续睡眠”
struct wait_queue_head_t xj_waitq_h; static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset) { if(!condition) //条件可以在中断处理函数中置位 wait_event_interruptible(&xj_waitq_h,condition); } static file_operations fops = { .read = demo_read, }; static __init demo_init(void) { init_waitqueue_head(&xj_waitq_h); }
对于普通的非阻塞IO,我们只需要在驱动中注册的read/write接口时不使用阻塞机制即可,这里我要讨论的是IO多路复用,即当驱动中的read/write并没有实现阻塞机制的时候,我们如何利用内核机制来在驱动中实现对IO多路复用的支持。下面这个就是我们要用的API
int poll(struct file *filep, poll_table *wait); void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
当应用层调用select/poll/epoll机制的时候,内核其实会遍历回调相关文件的驱动中的poll接口,通过每一个驱动的poll接口的返回值,来判断该文件IO是否有相应的事件发生,我们知道,这三种IO多路复用的机制的核心区别在于内核中管理监视文件的方式,分别是位,数组,链表,但对于每一个驱动,回调的接口都是poll。
struct wait_queue_head_t waitq_h; static unsigned int demo_poll(struct file *filp, struct poll_table_struct *pts) { unsigned int mask = 0; poll_wait(filp, &wwaitq_h, pts); if(counter){ mask = (POLLIN | POLLRDNORM); } return mask; } static struct file_operations fops = { .owner = THIS_MODULE, .poll = demo_poll, }; static __init demo_init(void) { init_waitqueue_head(&xj_waitq_h); }
刚才我们讨论了如何使用等待队列实现阻塞IO,非阻塞IO,其实关于等待队列,内核还提供了很多其他API用以完成相关的操作,这里我们来认识一下
//在等待队列上睡眠 sleep_on(wait_queue_head_t *wqueue_h); sleep_on_interruptible(wait_queue_head_t *wqueue_h); //唤醒等待的进程 void wake_up(wait_queue_t *wqueue); void wake_up_interruptible(wait_queue_t *wqueue);
总之,设备阻塞/非阻塞读写是Linux驱动程序编写过程中不可或缺的一部分。它可以实现高效的数据传输和事件处理,提高系统的性能和响应速度。希望本文能够帮助读者更好地理解Linux驱动技术(五) _设备阻塞/非阻塞读写的实现原理和相关技术。
以上是詳解Linux驅動技術(五) _設備阻塞/非阻塞讀寫的詳細內容。更多資訊請關注PHP中文網其他相關文章!