linux裝置驅動(8)阻塞型IO

2021-09-26 07:43:41 字數 3676 閱讀 4808

當驅動程式無法立即滿足請求,該如何響應?如當我們想要寫入的時候,裝置對應的緩衝區已滿,或者是當我們想要讀的時候當前緩衝區是空的。為了提高cpu的效率,我們的驅動程式應該阻塞等待該程序,將其置於休眠狀態直到請求可繼續。

休眠(sleep)對於程序來講意味著什麼?當乙個程序被置入休眠時,他會被標記為一種特殊狀態並從排程器的執行佇列中移走,直到某些情況下修改了這個狀態,程序才會在任意cpu上排程,也就是執行該程序。休眠中的程序會被擱置到一邊,等待將來某個事件的發生。

在linux裝置驅動中,將乙個程序置於休眠狀態很簡單,但是怎麼才能安全的將程序置於休眠狀態呢?需要注意以下三點:

永遠不要在原子上下文中進入休眠

原子上下文:在執行多個步驟時,不能有任何的併發訪問,即將要休眠的程式不能擁有自旋鎖、seqlock或者rcu鎖時休眠。如果我們已經禁止了中斷也不能休眠。但是在擁有訊號量的時候休眠是合法的,但是必須仔細檢查擁有訊號量時休眠的**。如果**在擁有訊號量時休眠,任何其他等待該訊號量的執行緒也會休眠,因此任何擁有訊號量而休眠的**必須很短,並且還要確保擁有訊號量並不會阻塞最終會喚醒我們的那個程序。

當程序從休眠狀態被喚醒之後,應該重新檢查程序所等待的條件(因為可能有多個多個程序都在等待這同乙個條件)

我們要確保其他的**會在其他地方喚醒我們,否則不能休眠。完成喚醒任務的**必須能夠找到我們的程序,之後才能喚醒休眠的程序。為了確保喚醒發生,需要整體理解我們的**,並清楚地知道對每個休眠而言那些事件序列會結束休眠。

鑑於以上的三個點,我們需要維護乙個稱為等待佇列的資料結構,在這個結構中包含了等待某個特定事件的所有程序。

在linux中,乙個等待佇列通過乙個等待佇列頭來管理,等待佇列頭是乙個型別為wait_queue_head_t的結構體,定義在中,如下所示:

struct __wait_queue_head ;

typedef struct __wait_queue_head wait_queue_head_t;

我們可以通過兩種方法來初始化這個wait_queue_head_t結構體

/**

* 該巨集定義乙個新等待佇列的頭,它靜態的宣告了乙個叫name的等待佇列的頭變數並對該變數的lock和task_list

* 字段進行初始化

*/#define declare_wait_queue_head(name) \

wait_queue_head_t name = __wait_queue_head_initializer(name)

#define __wait_queue_head_initializer(name) }

/**

* 用來初始化動態分配的等待佇列的頭變數

*/static inline void init_waitqueue_head(wait_queue_head_t *q)

/** * 建立乙個新的鍊錶。是新煉表頭的佔位符,並且是乙個啞元素。

* 同時初始化prev和next欄位,讓它們指向list_name變數本身。

*/#define init_list_head(ptr) do while (0)

以下函式可以實現休眠並且可以不斷檢查條件,直到條件為真停止休眠。

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:jiffies的個數,乙個tick的時間長取決於核心的config_hz的大小。比如config_hz=200,則乙個jiffies對應5ms時間。

第乙個函式是不可以被中斷的,而第二個是可以被訊號中斷的,第三和第四個函式在第一和第二個函式的基礎上增加了timeout引數,如果給定時間到達時,這兩個巨集都會返回0值,而無論condition如何求值。

void wake_up(wait_queue_head_t *queue);

void wake_up_interruptible(wait_queue_head_t *queue);

wake_up會喚醒等待在給定queue上的所有程序,而wake_up_interruptible只會喚醒那些執行可中斷休眠的程序。在實踐中,約定做法是在使用wait_event時使用wake_up,而在使用wait_event_interruptible時使用wake_up_interruptible。

檔案io模型-阻塞(休眠)

阻塞:當程序在讀取外部裝置的資源(資料),資源沒有準備好,程序就會進入休眠狀態 linux應用中,大部分介面都是阻塞,例如scanf(); read(); write(); accept();

實現步驟

1) 將當前程序加入等待佇列頭中

add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

2) 將當前程序狀態設定為task_interruptible,可以響應訊號。

set_current_state(task_interruptible);

3) 讓出排程-休眠

schedule(void);

乙個更加智慧型的介面是(可以實現上面三個的功能)

wait_event_interrupt(wq, condition);

非阻塞:在讀寫的時候,如果沒有資料,立即返回,並且返回乙個出錯碼(用的比較少,因為比較消耗資源)

open(「/dev/key0」, o_rdwr | o_noblock);驅動中需要去區分,當前模式是阻塞還是非阻塞。

有時呼叫程序會告訴我們他不想阻塞,而不管其io是否可以繼續,顯式的非阻塞io由filp->f_flags中的o_noblock標誌決定的。他可以在開啟時指定。o_ndelay標誌是o_nonblock的另乙個名字,是為了保持和system v**的相容性而設計的。這個標誌在預設情況下是被清除的,因為等待資料的程序一般只是休眠,在執行阻塞型操作的情況下,應該實現下面的動作:

1)如果乙個程序呼叫了read但是還沒有資料可讀,此程序必須阻塞,資料到達時程序被喚醒,並把資料返回給呼叫者。即使資料資料少於count引數指定的資料也是這樣

2)如果乙個程序呼叫了write但緩衝區沒有空間,此程序必須阻塞,而且必須休眠在與讀取程序不同的等待佇列上,當向硬體裝置寫入一些資料,從而騰出了部分輸出緩衝區後,程序即將被喚醒,write呼叫成功,即使緩衝區中可能沒有所要求的的count位元組的空間而只是寫入了部分資料,也是如此。

3)在驅動程式中實現輸出緩衝區可以提高效能,這得益於減少了上下文的切換和使用者級/核心級轉換的次數。

如果指定了o_noblock 標誌,read和write的行為就會有所不同,如果在資料沒有就緒時呼叫read或是在緩衝區沒有空間時呼叫write,則該呼叫簡單地返回-eagain。

非阻塞型操作會立即返回,使得應用程式可以查詢資料,在處理非阻塞型檔案時,應用程式呼叫stdio函式必須非常小心,因為很容易把乙個非阻塞返回錯誤認為是eof,所以必須檢查errno

只有read,write和open檔案操作會受到非阻塞標誌的影響。

Linux 裝置驅動阻塞 非阻塞IO 等待佇列

阻塞 顧名思義,就是指在執行裝置操作時若不能獲得資源則掛起操作,直到滿足可操作的條件後再進行操作,被掛起的程序進入休眠狀態,被從排程器的執行佇列移 走,直到等待的條件滿足。非阻塞 就是反過來,程序在不能進行裝置操作時並不掛起,它或者放棄,或者不停的查詢,直到可以進行位置。小王,明白了沒這兩個基本的概...

Linux裝置驅動中的阻塞和非阻塞I O

阻塞操作 是指在執行裝置操作時,若不能獲得資源,則掛起程序直到滿足操作條件後再進行操作。被掛起的程序進入休眠,被從排程器移走,直到條件滿足。非阻塞操作 在不能進行裝置操作時,並不掛起,它或者放棄,或者不停地查詢,直到可以進行操作。非阻塞應用程式通常使用select系統呼叫查詢是否可以對裝置進行無阻塞...

Linux裝置驅動之阻塞I O與非同步通知

阻塞與非阻塞訪問是 i o 操作的兩種不同模式,前者在 i o 操作暫時不可進行時會讓程序睡眠,後者則不然。在裝置驅動中阻塞 i o一般基於等待佇列來實現,等待佇列可用於同步驅動中事件發生的先後順序。使用非阻塞 i o 的應用程式也可借助輪詢函式來查詢裝置是否能立即被訪問,使用者空間呼叫 selec...