block and nonblock

同步阻塞IO

應用程序(application)爲了執行這個read操作,會調用相應的一個system call,將系統控制權交給kernel,然後就進行等待(這其實就是被阻塞了)。kernel開始執行這個system call,執行完畢後會嚮應用程序返回響應,應用程序得到響應後,就不再阻塞,並進行後面的工作。

例如,“在調用 read 系統調用時,應用程序會阻塞並對內核進行上下文切換。然後會觸發讀操作,當響應返回時(從我們正在從中讀取的設備中返回),數據就被移動到用戶空間的緩衝區中。然後應用程序就會解除阻塞(read 調用返回)。”

舉一個淺顯的例子,就好比你去一個銀行櫃檯存錢。首先,你會將存錢的單子填好,然後交給櫃員。這裏,你就好比是application,單子就是 調用的 system call,櫃員就是kernel。提交好單子後,你就坐在櫃檯前等,相當於開始進行等待。櫃員辦好以後會給你一個回執,表示辦好了,這就是 response。然後你就可以拿着回執幹其它的事了。注意,這個時候,如果你辦完之後馬上去查賬,存的錢已經打到你的賬戶上了。後面你會發現,這點很重要。

同步非阻塞IO

在linux下,應用程序可以通過設置文件描述符的屬性O_NONBLOCK,I/O操作可以立即返回,但是並不保證I/O操作成功。也就是說,當應用程序設置了O_NONBLOCK之後,執行write操作,調用相應的system call,這個system call會從內核中立即返回。但是在這個返回的時間點,數據可能還沒有被真正的寫入到指定的地方。也就是說,kernel只是很快的返回了這個 system call(這樣,應用程序不會被這個IO操作blocking),但是這個system call具體要執行的事情(寫數據)可能並沒有完成。而對於應用程序,雖然這個IO操作很快就返回了,但是它並不知道這個IO操作是否真的成功了,如果想 知道,需要應用程序主動地去問kernel。這次不是去銀行存錢,而是去銀行匯款。同樣的,你也需要填寫匯款單然後交給櫃員,櫃員進行一些簡單的手續處理就能夠給你回執。但是,你拿到回執並 不意味着錢已經打到了對方的賬上。事實上,一般匯款的週期大概是24個小時,如果你要以存錢的模式來匯款的話,意味着你需要在銀行等24個小時,這顯然是 不現實的。所以,同步非阻塞IO在實際生活中也是有它的意義的。

異步阻塞IO

和之前一樣,應用程序要執行read操作,因此調用一個system call,這個system call被傳遞給了kernel。但在應用程序這邊,它調用system call之後,並不等待kernel返回response,這一點是和前面兩種機制不一樣的地方。這也是爲什麼它被稱爲異步的原因。但是爲什麼稱其爲阻塞 呢?這是因爲雖然應用程序是一個異步的方式,但是select()函數會將應用程序阻塞住,一直等到這個system call有結果返回了,再通知應用程序。也就是說,“在這種模型中,配置的是非阻塞 I/O,然後使用阻塞 select 系統調用來確定一個 I/O 描述符何時有操作。”所以,從IO操作的實際效果來看,異步阻塞IO和第一種同步阻塞IO是一樣的,應用程序都是一直等到IO操作成功之後(數據已經被寫入或者讀取),纔開始進行下面的工作。異步阻塞IO的好處在於一個select函數可以爲多個描述符提供通知,提高了併發性。關於提高併發性這點,我們還以銀行爲例說明。比如說一個銀行櫃檯,現在有10個人想存錢。按照現在銀行的做法,一個個排隊。第一個人先填存款單, 然後提交,然後櫃員處理,然後給回執,成功後再輪到下一個人。大家應該都在銀行排過對,這樣的流程是很痛苦的。如果按照異步阻塞的機制,10個人都填好存 款單,然後都提交給櫃檯,提交完之後所有的10個人就在銀行大廳等待。這時候會專門有個人,他會了解存款單處理的情況,一旦有存款單處理完畢,他會將回執 交給相應的正在大廳等待的人,這個拿到回執的人就可以去幹其他的事情了。而前面提到的這個專人,就對應於select函數。

異步非阻塞IO

如圖所示,應用程序提交read請求的system call,然後,kernel開始處理相應的IO操作,而同時,應用程序並不等kernel返回響應,就會開始執行其他的處理操作(應用程序沒有被IO操 作所阻塞)。當kernel執行完畢,返回read的響應,就會產生一個信號或執行一個基於線程的回調函數來完成這次 I/O 處理過程。比如銀行存錢。現在某銀行新開通了一項存錢業務。用戶之需要將存款單交給櫃檯,然後無需等待就可以離開了。櫃檯辦好以後會給用戶發送一條短信,告知交易成功。這樣用戶不需要在櫃檯前進行長時間的等待,同時,也能夠得到確切的消息知道交易完成。從前面的介紹中可以看出,所謂的同步和異步,在這裏指的是application和kernel之間的交互方式。如果application不需 要等待 kernel的迴應,那麼它就是異步的。如果application提交完IO請求後,需要等待“回執”,那麼它就是同步的。而阻塞和非阻塞,指的是application是否等待IO操作的完成。如果application必須等到IO操作實際完成以後再執行下面的操作,那麼它是阻塞的。反之,如果不等待IO操作的完成就開始執行其它操作,那麼它是非阻塞的。

等待對列

在 Linux驅動程序中,可以使用等待隊列(wait queue)來實現阻塞進程的喚醒。wait queue很早就作爲一種基本的功能單位出現在Linux內核裏了,它以隊列位基礎數據結構,與進程調度機制緊密結合,能夠用於實現內核中異步事件通知機 制。等待隊列可以用來同步對系統資源的訪問。(信號量在內核中也依賴等待隊列來實現).

Linux-2.6提供如下關於等待隊列的操作:(1) 定義"等待隊列頭"

wait_queue_head_t my_queue;

(2) 初始化"等待隊列頭"

init_waitqueue_head(&my_queue);定義和初始化的快捷方式:DECLARE_WAIT_QUEUE_HEAD(my_queue);  

(3) 定義等待隊列

DECLARE_WAITQUEUE(name, tsk);定義並初始化一個名爲name的等待隊列(wait_queue_t);

(4) 添加/移除等待隊列

void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);add_wait_queue()用於將等待隊列wait添加到等待隊列頭q指向的等待隊列鏈表中,而remove_wait_queue()用於將等待隊列wait從附屬的等待隊列頭q指向的等待隊列鏈表中移除。

(5) 等待事件

wait_event(queue, condition);wait_event_interruptible(queue, condition);wait_event_timeout(queue, condition, timeout);wait_event_interruptible_timeout(queue, condition, timeout);等待第一個參數queue作爲等待隊列頭的等待隊列被喚醒,而且第二個參數condition必須滿足,否則阻塞。wait_event()和 wait_event_interruptible()的區別在於後者可以被信號打斷,而前者不能。加上timeout後的宏意味着阻塞等待的超時時間, 以jiffy爲單位,在第三個參數的timeout到達時,不論condition是否滿足,均返回。

(6) 喚醒隊列

void wake_up(wait_queue_head_t *queue);void wake_up_interruptible(wait_queue_head_t *queue);上述操作會喚醒以queue作爲等待隊列頭的所有等待隊列對應的進程。wake_up()               <--->    wait_event()

wait_event_timeout()

wake_up_interruptible() <--->    wait_event_interruptible()  

wait_event_interruptible_timeout()

wake_up()可以喚醒處於TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的進程wake_up_interruptble()只能喚醒處於TASK_INTERRUPTIBLE的進程。

(7) 在等待隊列上睡眠

sleep_on(wait_queue_head_t *q);interruptible_sleep_on(wait_queue_head_t *q);

     

sleep_on()函數的作用就是將當前進程的狀態置成TASK_UNINTERRUPTIBLE,定義一個等待隊列,並把它添加到等待隊列頭q,直到資源可獲得,q引導的等待隊列被喚醒。interruptible_sleep_on()與sleep_on()函數類似,其作用是將目前進程的狀態置成 TASK_INTERRUPTIBLE,並定義一個等待隊列,之後把它附屬到等待隊列頭q,直到資源可獲得,q引導的等待隊列被喚醒或者進程收到信 號。   sleep_on()               <--->   wake_up() interruptible_sleep_on() <--->   wake_up_interruptible()


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章