第8章 阻塞與非阻塞I O

2021-08-20 08:01:55 字數 3764 閱讀 2484

阻塞操作:在執行裝置操作時,若不能獲得資源,則掛起程序直到滿足可操作的條件後再進行操作。被掛起的程序進入睡眠狀態,被從排程器的執行佇列移走,直到等待條件被滿足。

非阻塞操作:在不能進行裝置操作時,並不掛起,要麼放棄要麼不停地查詢,直至可以進行操作為止。

喚醒程序的地方最大可能發生在中斷裡面,因為在硬體資源獲得的同時往往伴隨著乙個中斷。

實現阻塞程序的喚醒

1.定義「等待佇列頭部」

wait_queue_head_t my_queue;//wait_queue_head_t是__wait_queue_head結構體的乙個typedef(別名)。

2.初始化「等待佇列頭部」

init_waitqueue_head(&my_queue);

declare_wait_queue_head(name);定義並初始化等待佇列頭部的「快捷方式」(1+2)

3.定義等待佇列元素

declare_waitqueue(name,tsk)

4新增/移除等待佇列

void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);//將佇列元素wait新增到等待佇列頭部 q指向的雙向鍊錶

void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);//將佇列元素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必須滿足,否則繼續阻塞。第乙個函式和第二個函式的區別在於後者可被訊號打斷,而前者不能。timeout為超時時間,以jiffy為單位。

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_upinterruptible()只能喚醒後者程序。

7.在等待佇列上睡眠

sleep_on(wait_queue_head_t *q);//將程序狀態設定成task_uninterruptible,並定義乙個等待佇列元素掛到等待佇列頭部q指向的雙向鍊錶。和wake_up()成對使用

interruptible_sleep_on(wait_queue_head_t *q);//將程序狀態設定成task_interruptible並定義乙個等待佇列元素掛到等待佇列頭部q指向的雙向鍊錶。和wake_up_interruptible()成對使用

select() (bsd unix) poll() (system v)

#include

int select (int maxfd + 1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval * timeout);

引數一:最大的檔案描述符加1。

引數二:用於檢查可讀性,

引數三:用於檢查可寫性,

引數四:用於檢查

帶外資料,

引數五:struct timeval

乙個指向timeval結構的指標,用於決定select等待i/o的最長時間。如果為空將一直等待。

返回值:

>0:就緒描述字的正數目

-1:出錯

0 :超時

#include

int poll(struct pollfd fds, nfds_t nfds, int timeout);

引數說明:

fds:是乙個struct pollfd結構型別的

陣列,用於存放需要檢測其狀態的socket描述符;每當呼叫這個函式之後,系統不會清空這個陣列,操作起來比較方便;特別是對於socket連線比較多的情況下,在一定程度上可以提高處理的效率;這一點與select()函式不同,呼叫select()函式之後,select()函式會清空它所檢測的socket描述符集合,導致每次呼叫select()之前都必須把socket描述符重新加入到待檢測的集合中;因此,select()函式適合於只檢測乙個socket描述符的情況,而poll()函式適合於大量socket描述符的情況;

n fds:nfds_t型別的引數,用於標記

陣列fds中的

結構體元素的總數量;

timeout:是poll

函式呼叫阻塞的時間,單位:毫秒;

返回值:

>0:陣列fds中準備好讀、寫或出錯狀態的那些socket描述符的總數量;

==0:陣列fds中沒有任何socket描述符準備好讀、寫,或出錯;此時poll超時,超時時間是timeout毫秒;換句話說,如果所檢測的socket描述符上沒有任何事件發生的話,那麼poll()函式會阻塞timeout所指定的毫秒時間長度之後返回,如果timeout==0,那麼poll() 函式立即返回而不阻塞,如果timeout==inftim,那麼poll() 函式會一直阻塞下去,直到所檢測的socket描述符上的感興趣的事件發生是才返回,如果感興趣的事件永遠不發生,那麼poll()就會永遠阻塞下去;

-1: poll函式呼叫失敗,同時會自動設定全域性變數errno;

poll和select實現功能差不多,但poll效率高,以後要多用poll

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

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

第二個引數為輪詢表指標。(1)對可能引起裝置檔案狀態變化的等待佇列呼叫poll_wait() 將對應的等待佇列頭部新增到poll_table中(2)返回表示是否能對裝置進行無阻塞讀寫訪問的掩碼

void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);//作用是讓喚醒引數queue對應的等待佇列可以喚醒因select()而睡眠的程序。

poll操作返回值

通常返回下列定義「或」的結果

pollin裝置可無阻塞讀

pollout設別可無阻塞寫

pollrdnorm資料可讀

pollwrnorm資料可寫

裝置可讀通常返回: pollin | pollrdnorm

裝置可寫通常返回: pollout | pollwrnorm

總結:在裝置驅動中

阻塞i/o一般基於等待佇列來實現。

非阻塞i/o的應用程式可通過輪詢函式來查詢裝置是否能立即被訪問,使用者空間呼叫select() poll()或者epoll()介面,裝置驅動提供poll()函式。

其中裝置驅動的poll()本身不會阻塞,但是與poll()、select()和epoll()相關的系統呼叫則可能會阻塞地等待至少乙個檔案描述符集合可訪問或超時。

阻塞IO與非阻塞IO

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

非阻塞IO與阻塞IO

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

阻塞與非阻塞I O

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