阻塞與非阻塞I O

2021-06-19 06:39:02 字數 3841 閱讀 9247

還記得上篇 我們講到的是linux中併發控制訪問的手段有哪些????原子、訊號量、自旋鎖、互斥體。這是為了保護臨界區的資源,是多個程序對共享資源的併發訪問的一種處理手段。但是,在驅動程式中,我們常常為了支援使用者空間對裝置的靈活訪問,引入了阻塞與非阻塞i/o兩種不同模式。 

阻塞操作是指在執行裝置操作時若不能獲得資源則掛起程序,直到滿足可操作的條件後再進行操作。

因為阻塞的程序會進入休眠狀態,因此,必須確保有乙個地方能夠喚醒休眠的程序。喚醒程序的地方最大可能發生在中斷裡面,因為硬體資源獲得的同時往往伴隨著乙個中斷。

注意:驅動程式需要提供阻塞(等待佇列,中斷)和非阻塞方式(輪詢,非同步通知)訪問裝置。

休眠(被阻塞)的程序處於乙個特殊的不可執行狀態。這點非常重要,否則,沒有這種特殊狀態的話,排程程式就可能選出乙個本不願意被執行的程序,更糟糕的是,休眠就必須以輪詢的方式實現了。程序休眠有各種原因,但肯定都是為了等待一些事件。事件可能是一段時間、從檔案i/o讀更多資料,或者是某個硬體事件。乙個程序還有可能在嘗試獲得乙個已經占用的核心訊號量時被迫進入休眠。休眠的乙個常見原因就是檔案i/o -- 如程序對乙個檔案執行了read()操作,而這需要從磁碟裡讀取。還有,程序在獲取鍵盤輸入的時候也需要等待。無論哪種情況,核心的操作都相同:程序把它自己標記成休眠狀態,把自己從可執行佇列移出,放入等待佇列,然後呼叫schedule()選擇和執行乙個其他程序。喚醒的程序剛好相反:程序被設定為可執行狀態,然後再從等待佇列中移到可執行佇列。

休眠有兩種相關的程序狀態:task_interruptible and task_uninterruptible。它們的惟一區別是處於task_uninterruptible狀態的程序會忽略訊號,而處於task_interruptible狀態的程序如果收到訊號會被喚醒並處理訊號(然後再次進入等待睡眠狀態)。兩種狀態的程序位於同乙個等待佇列上,等待某些事件,不能夠執行。

休眠通過等待佇列進行處理。等待佇列是由等待某些事件發生的程序組成的簡單鍊錶。核心用wake_queue_head_t來代表等待佇列。等待佇列可以通過declare_waitqueue()靜態建立,也可以有init_waitqueue_head()動態建立。程序把自己放入等待佇列中並設定成不可執行狀態。等與等待佇列相關的事件發生的時候,佇列上的程序會被喚醒。為了避免產生競爭條件,休眠和喚醒的實現不能有紕漏。

等待佇列

在linux驅動程式中,可以使用等待佇列來實現阻塞程序的喚醒。

程序通過執行下面幾步將自己加入到乙個等待佇列中:

當然,首先是定義等待佇列頭,並初始化:

wait_queue_head_t wait;

init_waitqueue_head(&wait);

1. 呼叫declare_waitqueue()建立乙個等待佇列的項

|/* 'q' is the wait queue we wish to sleep on */ |

|declare_waitqueue(wait, current); |

2. 呼叫add_wait_queue()把自己加入到佇列中。該佇列在程序等待的條件滿足時喚醒它。當然我們必須在其他地方撰寫相關**,在事件發生時,對等待佇列執行wake_up()操作

|add_wait_queue(q, &wait); |

while (!condition) { /* condition is the event that we are waiting for */

3. 將程序的狀態變更為task_interruptible or task_uninterruptible

| /* or task_uninterruptible */ |

| __set_current_state(task_interruptible); |

4. 如果狀態被設定為task_interruptible,則訊號可以喚醒程序(訊號和事件都可以喚醒該程序)。這就是所謂的偽喚醒(喚醒不是因為事件的發生,而是由訊號喚醒的),因此檢查並處理訊號。

注: 訊號和等待事件都可以喚醒處於task_interruptible狀態的程序,訊號喚醒該程序為偽喚醒;該程序被喚醒後,如果(!condition)結果為真,則說明該程序不是由等待事件喚醒的,而是由訊號喚醒的。所以該程序處理訊號後將再次讓出cpu控制權

| if (signal_pending(current)) |

| /* handle signal */ |

5. tests whether the condition is true. if it is, there is no need to sleep. if it is not true, the task calls schedule().

本程序在此處交出cpu控制權,如果該程序再次被喚醒,將從while迴圈結尾處繼續執行,因而將回到while迴圈的開始處while (!condition),進測等待事件是否真正發生.

| schedule(); |

6. now that the condition is true, the task can set itself to task_running and remove itself from the wait queue via remove_wait_queue().

|set_current_state(task_running); |

|remove_wait_queue(q, &wait); |

另外,在程式中必須有喚醒等待佇列的機制:

wake_up_interruptible(&q);

輪詢的概念與作用

使用非阻塞i/o的應用程式通常會使用select()和poll()系統呼叫查詢是否可對裝置進行無阻塞的訪問。select()和poll()系統呼叫最終會引發裝置驅動中的poll()函式被執行。

select()和poll()系統呼叫的本質一樣,前者在bsd unix中引入,後者在system v中引入。

應用程式中的輪詢程式設計

int select(int numfds,fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout);

檔案描述符集合操作:fd_zero(fd_set *set) fd_set(int fd, fd_set *set)

fd_clr(int fd, fd_set *set) fd_isset(int fd, fd_set *set)

裝置驅動中的輪詢程式設計

unsigned int (*poll)(struct file *filp, struct poll_table *wait);

void poll_wait(struct file *filp, wait_queue_head_t *queue, struct poll_table *wait);

poll()函式的典型模板:

static unsigned int ***_poll(struct file *filp, poll_table *wait)

unsigned int mask = o;

struct ***_dev *dev - filp->private_data;//獲取裝置接構體指標

poll_wait (filp, &dev->r_wait, wait);//加讀等待佇列頭

poll_wait (filp, &dev->w_ait, waitl);//加寫等待佇列頭

if(...) //可讀

mask |=pollin|pollrdnorm; //標誌資料可獲得

if(...) //可寫

mask |=pollout|pollrdnorm; //標誌資料可寫入

return mask;

自從看到乙個命令在我的手下,蹦的一下------------我就笑了

阻塞IO與非阻塞IO

阻塞io,當前程序因不滿足一些條件,而被掛起,即阻塞,cpu改去服務其它程序,read乙個普通檔案,就馬上執行,read乙個滑鼠,可是滑鼠沒有動,於是就阻塞了,阻塞的好處,利於os效能的發揮,cpu發揮高,雖然個體的費了點時間,但是總的效率得到了提高,阻塞在多路io的時候,缺陷就出來了,比如2路io...

非阻塞IO與阻塞IO

非阻塞式呼叫的問題 kibuv提供了乙個執行緒池 阻塞於非阻塞對於被呼叫者,即系統層面,系統為程式提供了阻塞呼叫和非阻塞呼叫,同步和非同步是對於呼叫者,就是自己的程式,發七呼叫,沒有其他操作,只是等待結果這個過程就是同步,發起呼叫後會等待結果,繼續完成其他的工作,等有回掉再執行,這個過程就是非同步的...

阻塞I O,非阻塞I O

拿 socket舉例。當read資料時,如果這時沒有資料可讀,阻塞i o會一直等待有資料讀,資料從kernel copy 到socket的buffer後返回 非阻塞i o會立即返回,但如果有資料可讀,非阻塞i o也是等資料從kernel copy 到socket的buffer後返回。以上是阻塞與非阻...