內(nèi)核的睡眠機(jī)制
進(jìn)程通過睡眠機(jī)制釋放CPU,以便CPU能夠處理其他進(jìn)程。進(jìn)程進(jìn)入睡眠狀態(tài)的原因可能是等待自己需要的資源釋放或者數(shù)據(jù)可用。
內(nèi)核調(diào)度器管理要運(yùn)行的任務(wù)列表,這被稱為運(yùn)行隊(duì)列。睡眠進(jìn)程會被從運(yùn)行隊(duì)列中移除,不再調(diào)度,除非睡眠進(jìn)程被喚醒。進(jìn)程一旦進(jìn)入睡眠,就會被放進(jìn)等待隊(duì)列,也就會釋放cpu的控制權(quán),一定要確保有條件或者其他進(jìn)程能夠喚醒睡眠隊(duì)列。在內(nèi)核中,內(nèi)核提供了一組函數(shù)和數(shù)據(jù)結(jié)構(gòu)降低了實(shí)現(xiàn)睡眠機(jī)制的難度。

阻塞I/O:第一階段進(jìn)行數(shù)據(jù)的查詢,若外設(shè)數(shù)據(jù)沒有就緒,進(jìn)程就會進(jìn)入睡眠,放入等待隊(duì)列,一旦外設(shè)數(shù)據(jù)準(zhǔn)備就緒,就會在中斷中喚醒對應(yīng)的進(jìn)程恢復(fù)執(zhí)行。
非阻塞I/O:第一階段進(jìn)程外設(shè)數(shù)據(jù)的查詢,若外設(shè)數(shù)據(jù)沒有就緒,進(jìn)程不會進(jìn)入睡眠,會返回錯誤,表示數(shù)據(jù)沒有準(zhǔn)備好,進(jìn)程便不再進(jìn)行讀寫操作。若進(jìn)程仍要繼續(xù)讀取數(shù)據(jù),就會使用select或者poll、epoll函數(shù)進(jìn)行輪詢,若一旦發(fā)現(xiàn)外設(shè)數(shù)據(jù)準(zhǔn)備就緒,進(jìn)程就會完成read操作,進(jìn)行第二階段的讀寫操作。
阻塞IO和非阻塞IO都屬于同步IO,與之相對的還有異步IO,兩者的區(qū)別如下:
同步IO:在執(zhí)行IO過程中,會導(dǎo)致進(jìn)程被阻塞
異步IO:在執(zhí)行IO過程中,不會導(dǎo)致進(jìn)程被阻塞,進(jìn)程發(fā)出異步IO請求后,無論內(nèi)核中的數(shù)據(jù)是否準(zhǔn)備好,API都會立即返回,應(yīng)用程序不會阻塞,而是繼續(xù)執(zhí)行其他的邏輯代碼。當(dāng)數(shù)據(jù)在內(nèi)核中準(zhǔn)備好后,內(nèi)核會將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間中進(jìn)程指定的buffer中,拷貝完成后通過信號或者事件通知進(jìn)程。應(yīng)用程序在信號處理程序或者事件處理程序中獲取IO操作的結(jié)果,對IO操作結(jié)果的數(shù)據(jù)進(jìn)行處理。
兩者主要的區(qū)別是:同步IO的IO訪問,進(jìn)程調(diào)用的API會查詢外設(shè)數(shù)據(jù)是否就緒,并根據(jù)這個(gè)結(jié)果來決定是否進(jìn)行外設(shè)數(shù)據(jù)的讀取。但是異步IO并不依賴所調(diào)用的API的返回結(jié)果,由內(nèi)核自動完成對外設(shè)數(shù)據(jù)狀態(tài)的檢查、將外設(shè)數(shù)據(jù)拷貝到進(jìn)程buffer以及對進(jìn)程發(fā)出通知。
??等待隊(duì)列
等待隊(duì)列實(shí)際上用于處理被阻塞的I/O,以等待特定條件成立,并感知數(shù)據(jù)和資源的可用性。相關(guān)的數(shù)據(jù)結(jié)構(gòu)定義在include/linux/wait.h中:
這等待隊(duì)列的結(jié)構(gòu)體中,我們也能看到一個(gè)熟悉的老朋友 struct list_head,說明內(nèi)核也是使用鏈表來管理睡眠隊(duì)列。想要進(jìn)入睡眠的進(jìn)程都會被放進(jìn)鏈表中排隊(duì)并進(jìn)入睡眠狀態(tài),知道其想要的數(shù)據(jù)準(zhǔn)備就緒,等待隊(duì)列可以看做簡單的進(jìn)程鏈表和鎖。
處理等待隊(duì)列的API
(1)靜態(tài)聲明
DECLARE_WAIT_QUEUE_HEAD(name)
(2)動態(tài)聲明
wait_queue_head_t my_wait_queue;
init_waitqueue_head(&my_wait_queue);
等待隊(duì)列的動態(tài)聲明和靜態(tài)聲明與鏈表的聲明方式是類似的,都是內(nèi)存空間開辟的區(qū)別。
(3)阻塞
(4)解除阻塞
wait_event_interruptible、wake_up_interruptible和wake_up_interruptible_all都是可中斷的函數(shù),可以被信號打斷,因此應(yīng)當(dāng)檢查他們的返回值,非零值意味著睡眠被某種信號中斷,驅(qū)動應(yīng)當(dāng)返回ERESTARTSYS.
與之相對的是,內(nèi)核中還有wait_event、wake_up和wake_up_all等待隊(duì)列處理函數(shù),他們是不會被信號中斷的,以獨(dú)占等待的方式處理隊(duì)列中的進(jìn)程,這些函數(shù)只能應(yīng)用在關(guān)鍵任務(wù)中。
睡眠的進(jìn)程只有調(diào)用了阻塞解除處理函數(shù)才會被喚醒,否則將一直睡眠。
下面是一個(gè)等待隊(duì)列的例子(其中涉及到的運(yùn)行隊(duì)列在下一篇博文中學(xué)習(xí))