阻塞與非阻塞是設(shè)備訪問的兩種方式,。驅(qū)動(dòng)程序需要提供阻塞(等待隊(duì)列,,中斷)和非阻塞方式(輪詢,異步通知)訪問設(shè)備,。在寫阻塞與非阻塞的驅(qū)動(dòng)程序時(shí),,經(jīng)常用到等待隊(duì)列。
一,、阻塞與非阻塞 阻塞調(diào)用是沒有獲得資源則掛起進(jìn)程,,被掛起的進(jìn)程進(jìn)入休眠狀態(tài),調(diào)用的函數(shù)只有在得到結(jié)果之后才返回,,進(jìn)程繼續(xù),。 非阻塞是不能進(jìn)行設(shè)備操作時(shí)不掛起,或返回,,或反復(fù)查詢,,直到可以進(jìn)行操作為止,被調(diào)用的函數(shù)不會(huì)阻塞當(dāng)前進(jìn)程,,而會(huì)立刻返回,。
驅(qū)動(dòng)程序常需要這種能力:當(dāng)應(yīng)用程序進(jìn)程read(),write()等系統(tǒng)調(diào)用時(shí),若設(shè)備的資源不能獲取,,而用戶又希望以阻塞的方式訪問設(shè)備,,驅(qū)動(dòng)程序應(yīng)該在設(shè)備驅(qū)動(dòng)程序的xxx_read(),xxx_write()等操作中將進(jìn)程阻止到資源可以獲取,,以后,,應(yīng)用程序的read(),write()等調(diào)用返回,,整個(gè)過程仍然進(jìn)行了正確的設(shè)備訪問,用戶并沒有感知到,。若用戶以非阻塞的方式訪問設(shè)備文件,,則當(dāng)設(shè)備資源不可獲取時(shí),設(shè)備驅(qū)動(dòng)的xxx_read(),xxx_write()等操作應(yīng)立即返回,,read(),,write()等喜用調(diào)用也隨即被訪問。 阻塞不是低效率,,如果設(shè)備驅(qū)動(dòng)不阻塞,,用戶想獲取設(shè)備資源只能不斷查詢,小號(hào)CPU資源,,阻塞訪問時(shí),,不能獲取資源的進(jìn)程將進(jìn)入休眠,將CPU資源讓給其他資源,。 阻塞的進(jìn)程會(huì)進(jìn)入休眠狀態(tài),,因此,必須確保有一個(gè)地方能喚醒休眠的進(jìn)程,。喚醒進(jìn)程的地方最大可能發(fā)生在中斷里面,,因?yàn)橛布Y源的獲得往往伴隨著一個(gè)中斷。 對(duì)象是否處于阻塞模式和函數(shù)是不是阻塞調(diào)用有很強(qiáng)的相關(guān)性,,但并不是一一對(duì)應(yīng)的,。阻塞對(duì)象上可以有非阻塞的調(diào)用方式,我們可以通過一定的API去輪詢狀 態(tài),,在適當(dāng)?shù)臅r(shí)候調(diào)用阻塞函數(shù),,就可以避免阻塞。而對(duì)于非阻塞對(duì)象,,調(diào)用的函數(shù)也可以進(jìn)入阻塞調(diào)用,。函數(shù)select()就是這樣一個(gè)例子。 二,、等待隊(duì)列 在linux驅(qū)動(dòng)程序中,,可使用等待隊(duì)列(wait queue)來(lái)實(shí)現(xiàn)阻塞進(jìn)程的喚醒,以隊(duì)列為基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),,與進(jìn)程調(diào)度機(jī)制緊密結(jié)合,,用于實(shí)現(xiàn)內(nèi)核的異步事件通知機(jī)制,也可用于同步對(duì)系統(tǒng)資源的訪問,。(信號(hào)量在內(nèi)核中也依賴等待隊(duì)列來(lái)實(shí)現(xiàn)) 在軟件開發(fā)中任務(wù)經(jīng)常由于某種條件沒有得到滿足而不得不進(jìn)入睡眠狀態(tài),,然后等待條件得到滿足的時(shí)候再繼續(xù)運(yùn)行,進(jìn)入運(yùn)行狀態(tài)。這種需求需要等待隊(duì)列機(jī)制的支持,。Linux中提供了等待隊(duì)列的機(jī)制,,該機(jī)制在內(nèi)核中應(yīng)用很廣泛。 等待隊(duì)列在linux內(nèi)核中有著舉足輕重的作用,,很多l(xiāng)inux驅(qū)動(dòng)都或多或少涉及到了等待隊(duì)列,。因此,對(duì)于linux內(nèi)核及驅(qū)動(dòng)開發(fā)者來(lái)說(shuō),,掌握等待隊(duì) 列是必須課之 Linux內(nèi)核的等待隊(duì)列是以雙循環(huán)鏈表為基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),,與進(jìn)程調(diào)度機(jī)制緊密結(jié)合,能夠用于實(shí)現(xiàn)核心的異步事件通知機(jī)制,。它有兩種數(shù)據(jù)結(jié)構(gòu):等待隊(duì)列頭 (wait_queue_head_t)和等待隊(duì)列項(xiàng)(wait_queue_t),。等待隊(duì)列頭和等待隊(duì)列項(xiàng)中都包含一個(gè)list_head類型的域作為”連接件”。它通過一個(gè)雙鏈表和把等待task的頭,,和等待的進(jìn)程列表鏈接起來(lái),。下面具體介紹。 1.定義 頭文件:/include/linux/wait.h
struct __wait_queue_head { spinlock_t lock; /* 保護(hù)等待隊(duì)列的原子鎖 (自旋鎖),在對(duì)task_list與操作的過程中,,使用該鎖實(shí)現(xiàn)對(duì)等待隊(duì)列的互斥訪問*/ struct list_head task_list; /* 等待隊(duì)列,雙向循環(huán)鏈表,,存放等待的進(jìn)程 */ }; /*__wait_queue,該結(jié)構(gòu)是對(duì)一個(gè)等待任務(wù)的抽象,。每個(gè)等待任務(wù)都會(huì)抽象成一個(gè)wait_queue,,并且掛載到wait_queue_head上。該結(jié)構(gòu)定義如下:*/ struct __wait_queue { unsigned int flags;
2.操作: (1) 定義并初始化"等待隊(duì)列頭" wait_queue_head_t my_queue; init_waitqueue_head(&my_queue); //會(huì)將自旋鎖初始化為未鎖,等待隊(duì)列初始化為空的雙向循環(huán)鏈表,。 //宏名用于定義并初始化,,相當(dāng)于"快捷方式" DECLARE_WAIT_QUEUE_HEAD (my_queue); (2) 定義"等待隊(duì)列" /*定義并初始化一個(gè)名為name的等待隊(duì)列 ,注意此處是定義一個(gè)wait_queue_t類型的變量name,并將其private與設(shè)置為tsk*/ DECLARE_WAITQUEUE(name,tsk); struct __wait_queue { unsigned int flags; #define WQ_FLAG_EXCLUSIVE 0x01 void *private; /* 通常指向當(dāng)前任務(wù)控制塊 */ wait_queue_func_t func; struct list_head task_list; /* 掛入wait_queue_head的掛載點(diǎn) */ }; typedef struct __wait_queue wait_queue_t; 其中flags域指明該等待的進(jìn)程是互斥進(jìn)程還是非互斥進(jìn)程,。其中0是非互斥進(jìn)程,,WQ_FLAG_EXCLUSIVE(0×01)是互斥進(jìn)程。等待隊(duì)列 (wait_queue_t)和等待對(duì)列頭(wait_queue_head_t)的區(qū)別是等待隊(duì)列是等待隊(duì)列頭的成員,。也就是說(shuō)等待隊(duì)列頭的task_list域鏈接的成員就是等待隊(duì)列類型的(wait_queue_t),。
(3) (從等待隊(duì)列頭中)添加/刪除等待隊(duì)列 //設(shè)置等待的進(jìn)程為非互斥進(jìn)程,并將其添加進(jìn)等待隊(duì)列頭(q)的隊(duì)頭中,。 void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) { unsigned long flags; wait->flags &= ~WQ_FLAG_EXCLUSIVE; spin_lock_irqsave(&q->lock, flags); __add_wait_queue(q, wait); spin_unlock_irqrestore(&q->lock, flags); } EXPORT_SYMBOL(add_wait_queue); //下面函數(shù)也和add_wait_queue()函數(shù)功能基本一樣,,只不過它是將等待的進(jìn)程(wait)設(shè)置為互斥進(jìn)程。 void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait) { unsigned long flags; wait->flags |= WQ_FLAG_EXCLUSIVE; spin_lock_irqsave(&q->lock, flags); __add_wait_queue_tail(q, wait); spin_unlock_irqrestore(&q->lock, flags); } EXPORT_SYMBOL(add_wait_queue_exclusive); //在等待的資源或事件滿足時(shí),,進(jìn)程被喚醒,,使用該函數(shù)被從等待頭中刪除,。 (4) 等待事件 #define wait_event(wq, condition) do { if (condition) break; __wait_event(wq, condition); } while (0) wait_event(queue,condition);等待以queue為等待隊(duì)列頭等待隊(duì)列被喚醒,condition必須滿足,,否則阻塞 wait_event_interruptible(queue,condition);可被信號(hào)打斷 wait_event_timeout(queue,condition,timeout);阻塞等待的超時(shí)時(shí)間,,時(shí)間到了,不論condition是否滿足,,都要返回 wait_event_interruptible_timeout(queue,condition,timeout) wait_event()宏: 在等待會(huì)列中睡眠直到condition為真,。在等待的期間,,進(jìn)程會(huì)被置為TASK_UNINTERRUPTIBLE進(jìn)入睡眠,,直到condition變量變?yōu)檎妗C看芜M(jìn)程被喚醒的時(shí)候都會(huì)檢查condition的值. wait_event_interruptible()函數(shù): 和wait_event()的區(qū)別是調(diào)用該宏在等待的過程中當(dāng)前進(jìn)程會(huì)被設(shè)置為TASK_INTERRUPTIBLE狀態(tài).在每次被喚醒的時(shí)候,首先檢查 condition是否為真,如果為真則返回,否則檢查如果進(jìn)程是被信號(hào)喚醒,會(huì)返回-ERESTARTSYS錯(cuò)誤碼.如果是condition為真,則 返回0. wait_event_timeout()宏: 也與wait_event()類似.不過如果所給的睡眠時(shí)間為負(fù)數(shù)則立即返回.如果在睡眠期間被喚醒,且condition為真則返回剩余的睡眠時(shí)間,否則繼續(xù)睡眠直到到達(dá)或超過給定的睡眠時(shí)間,然后返回0. wait_event_interruptible_timeout()宏 與wait_event_timeout()類似,不過如果在睡眠期間被信號(hào)打斷則返回ERESTARTSYS錯(cuò)誤碼. wait_event_interruptible_exclusive()宏 同樣和wait_event_interruptible()一樣,不過該睡眠的進(jìn)程是一個(gè)互斥進(jìn)程. (5)喚醒隊(duì)列 /* __wake_up - wake up threads blocked on a waitqueue. @q: the waitqueue @mode: which threads @nr_exclusive: how many wake-one or wake-many threads to wake up @key: is directly passed to the wakeup function */ void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key) { unsigned long flags; spin_lock_irqsave(&q->lock, flags); __wake_up_common(q, mode, nr_exclusive, 0, key); spin_unlock_irqrestore(&q->lock, flags); } EXPORT_SYMBOL(__wake_up); //喚醒等待隊(duì)列.可喚醒處于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE狀態(tài)的進(jìn)程,和wait_event/wait_event_timeout成對(duì)使用 #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL) //和wake_up()唯一的區(qū)別是它只能喚醒TASK_INTERRUPTIBLE狀態(tài)的進(jìn)程.,與wait_event_interruptible wait_event_interruptible_timeout wait_event_interruptible_exclusive成對(duì)使用 #define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL) #define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL) #define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL) //這些也基本都和wake_up/wake_up_interruptible一樣 wake_up()與wake_event()或者wait_event_timeout成對(duì)使用,, (6) 在等待隊(duì)列上睡眠: sleep_on()函數(shù) void __sched sleep_on(wait_queue_head_t *q) { sleep_on_common(q, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); } static long __sched sleep_on_common(wait_queue_head_t *q, int state, long timeout) { unsigned long flags; wait_queue_t wait; init_waitqueue_entry(&wait, current); __set_current_state(state); spin_lock_irqsave(&q->lock, flags); __add_wait_queue(q, &wait); spin_unlock(&q->lock); timeout = schedule_timeout(timeout); spin_lock_irq(&q->lock); __remove_wait_queue(q, &wait); spin_unlock_irqrestore(&q->lock, flags); return timeout; } 該函數(shù)的作用是定義一個(gè)等待隊(duì)列(wait),并將當(dāng)前進(jìn)程添加到等待隊(duì)列中(wait),,然后將當(dāng)前進(jìn)程的狀態(tài)置為 TASK_UNINTERRUPTIBLE,,并將等待隊(duì)列(wait)添加到等待隊(duì)列頭(q)中。之后就被掛起直到資源可以獲取,,才被從等待隊(duì)列頭(q) 中喚醒,,從等待隊(duì)列頭中移出。在被掛起等待資源期間,,該進(jìn)程不能被信號(hào)喚醒,。 sleep_on_timeout()函數(shù) long __sched sleep_on_timeout(wait_queue_head_t *q, long timeout) { return sleep_on_common(q, TASK_UNINTERRUPTIBLE, timeout); } EXPORT_SYMBOL(sleep_on_timeout); 與sleep_on()函數(shù)的區(qū)別在于調(diào)用該函數(shù)時(shí),如果在指定的時(shí)間內(nèi)(timeout)沒有獲得等待的資源就會(huì)返回,。實(shí)際上是調(diào)用 schedule_timeout()函數(shù)實(shí)現(xiàn)的,。值得注意的是如果所給的睡眠時(shí)間(timeout)小于0,則不會(huì)睡眠,。該函數(shù)返回的是真正的睡眠時(shí)間,。 interruptible_sleep_on()函數(shù) void __sched interruptible_sleep_on(wait_queue_head_t *q)
{
sleep_on_common(q, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
EXPORT_SYMBOL(interruptible_sleep_on);
該函數(shù)和sleep_on()函數(shù)唯一的區(qū)別是將當(dāng)前進(jìn)程的狀態(tài)置為TASK_INTERRUPTINLE,這意味在睡眠如果該進(jìn)程收到信號(hào)則會(huì)被喚醒,。 interruptible_sleep_on_timeout()函數(shù): long __sched interruptible_sleep_on_timeout(wait_queue_head_t *q, long timeout) { return sleep_on_common(q, TASK_INTERRUPTIBLE, timeout); } EXPORT_SYMBOL(interruptible_sleep_on_timeout); 類似于sleep_on_timeout()函數(shù),。進(jìn)程在睡眠中可能在等待的時(shí)間沒有到達(dá)就被信號(hào)打斷而被喚醒,也可能是等待的時(shí)間到達(dá)而被喚醒,。 以上四個(gè)函數(shù)都是讓進(jìn)程在等待隊(duì)列上睡眠,,不過是小有差異而已。在實(shí)際用的過程中,,根據(jù)需要選擇合適的函數(shù)使用就是了,。例如在對(duì)軟驅(qū)數(shù)據(jù)的讀寫中,如果設(shè) 備沒有就緒則調(diào)用sleep_on()函數(shù)睡眠直到數(shù)據(jù)可讀(可寫),,在打開串口的時(shí)候,,如果串口端口處于關(guān)閉狀態(tài)則調(diào)用 interruptible_sleep_on()函數(shù)嘗試等待其打開,。在聲卡驅(qū)動(dòng)中,讀取聲音數(shù)據(jù)時(shí),,如果沒有數(shù)據(jù)可讀,,就會(huì)等待足夠常的時(shí)間直到可讀取。
|
|