Linux驅動技術(五) _設備阻塞/非阻塞讀寫

等待隊列是內核中實現進程調度的一個十分重要的數據結構,其任務是維護一個鏈表,鏈表中每個節點都是一個PCB(進程控制塊),內核會將PCB掛在等待隊列中的全部進程都調度爲睡眠狀態,直到某個喚醒的條件發生。應用層的阻塞IO與非阻塞IO的使用我已經在Linux I/O多路複用一文中討論過了,本文主要討論驅動中怎麼實現對設備IO的阻塞與非阻塞讀寫。顯然,實現這種與阻塞相關的機制要用到等待隊列機制。本文的內核源碼使用的是3.14.0版本html

設備阻塞IO的實現

當咱們讀寫設備文件的IO時,最終會回調驅動中相應的接口,而這些接口也會出如今讀寫設備進程的進程(內核)空間中,若是條件不知足,接口函數使進程進入睡眠狀態,即便讀寫設備的用戶進程進入了睡眠,也就是咱們常說的發生了阻塞。In a word,讀寫設備文件阻塞的本質是驅動在驅動中實現對設備文件的阻塞,其讀寫的流程可歸納以下:linux

1. 定義-初始化等待隊列頭

//定義等待隊列頭
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域,居然沒有用內核標準的初始化宏,無語。。。code

2. 將本進程添加到等待隊列

爲等待隊列添加事件,即進程進入睡眠狀態直到condition爲真才返回。_interruptible的版本版本表示睡眠可中斷,_timeout版本表示超時版本,超時就會返回,這種命名規範在內核API中隨處可見。htm

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);

這但是等待隊列的核心,咱們來看一下blog

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-->死循環
--207-->進程進入睡眠
--209-->進程被wake_up喚醒,再次檢查條件,若是條件爲真,跳出循環,執行finish_wait(),wait_event()返回;若是醒來發現條件仍然不知足, 則執行下一個循環進入睡眠, 周而復始...
--212-->若是進程睡眠的方式是interruptible的,那麼當中斷來的時候也會abort_exclusive_wait被喚醒
--222-->若是上面兩條都不知足,就會回調傳入的schedule(),即繼續睡眠

3. 無條件睡眠

wait_event是睡在一個條件上, 內核還提供了下面的API進行無條件的睡眠, 只要被wake_up了就會醒來

//在等待隊列上睡眠
sleep_on(wait_queue_head_t *wqueue_h);
sleep_on_interruptible(wait_queue_head_t *wqueue_h);

4. 喚醒

條件不知足, wait_event就不會返回, 當前調用該接口的進程就會進入睡眠, 爲了喚醒這個進程, 一般在另一個接口或中斷處理程序中知足條件並調用wake_up, 另一個進程調用這個接口的時候,就會喚醒全部睡在這個條件上(這個等待隊列頭)的進程, 這個這樣其實也實現了兩個進程之間的"通訊"

//喚醒等待的進程
void wake_up(wait_queue_t *wqueue);
void wake_up_interruptible(wait_queue_t *wqueue);

模板

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 ssize_t demo_write(struct file *, const char __user *, size_t, loff_t *)
{
    condition = 1;
    wake_up(&xj_waitq_h);
}
static file_operations fops = {
    .read = demo_read,
    .write= demo_write,
};
static __init demo_init(void)
{
    init_waitqueue_head(&xj_waitq_h);
}

IO多路複用的實現

對於普通的非阻塞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);
}
相關文章
相關標籤/搜索