在我們看全功能的 read 和 write 方法的實現之前, 我們觸及的最后一點是決定何時使 進程睡眠. 有時實現正確的 unix 語義要求一個操作不阻塞, 即便它不能完全地進行下去.
有時還有調用進程通知你他不想阻塞, 不管它的 I/O 是否繼續. 明確的非阻塞 I/O 由 filp->f_flags 中的 O_NONBLOCK 標志來指示. 這個標志定義於 <linux/fcntl.h>, 被
<linux/fs.h>自動包含. 這個標志得名自"打開-非阻塞", 因為它可在打開時指定(並且起
初只能在那里指定). 如果你瀏覽源碼, 你會發現一些對一個 O_NDELAY 標志的引用; 這 是一個替代 O_NONBLOCK 的名子, 為兼容 System V 代碼而被接受的. 這個標志缺省地被 清除, 因為一個等待數據的進程的正常行為僅僅是睡眠. 在一個阻塞操作的情況下, 這是 缺省地, 下列的行為應當實現來符合標准語法:
- · 如果一個進程調用 read 但是沒有數據可用(尚未), 這個進程必須阻塞. 這個進程 在有數據達到時被立刻喚醒, 並且那個數據被返回給調用者, 即便小於在給方法的 count 參數中請求的數量.
- · 如果一個進程調用 write 並且在緩沖中沒有空間, 這個進程必須阻塞, 並且它必 須在一個與用作 read 的不同的等待隊列中. 當一些數據被寫入硬件設備, 並且在
輸出緩沖中的空間變空閑, 這個進程被喚醒並且寫調用成功, 盡管數據可能只被部 分寫入如果在緩沖只沒有空間給被請求的 count 字節.
這 2 句都假定有輸入和輸出緩沖; 實際上, 幾乎每個設備驅動都有. 要求有輸入緩沖是 為了避免丟失到達的數據, 當無人在讀時. 相反, 數據在寫時不能丟失, 因為如果系統調
用不能接收數據字節, 它們保留在用戶空間緩沖. 即便如此, 輸出緩沖幾乎一直有用, 對 於從硬件擠出更多的性能.
在驅動中實現輸出緩沖所獲得的性能來自減少了上下文切換和用戶級/內核級切換的次數. 沒有一個輸出緩沖(假定一個慢速設備), 每次系統調用接收這樣一個或幾個字符, 並且當 一個進程在 write 中睡眠, 另一個進程運行(那是一次上下文切換). 當第一個進程被喚 醒, 它恢復(另一次上下文切換), 寫返回(內核/用戶轉換), 並且這個進程重新發出系統 調用來寫入更多的數據(用戶/內核轉換); 這個調用阻塞並且循環繼續. 增加一個輸出緩 沖可允許驅動在每個寫調用中接收大的數據塊, 性能上有相應的提高. 如果這個緩沖足夠 大, 寫調用在第一次嘗試就成功 -- 被緩沖的數據之后將被推到設備 -- 不必控制需要返 回用戶空間來第二次或者第三次寫調用. 選擇一個合適的值給輸出緩沖顯然是設備特定的.
我們不使用一個輸入緩沖在 scull 中, 因為數據當發出 read 時已經可用. 類似的, 不用 輸出緩沖, 因為數據被簡單地拷貝到和設備關聯的內存區. 本質上, 這個設備是一個緩沖, 因此額外緩沖的實現可能是多余的. 我們將在第 10 章見到緩沖的使用.
如果指定 O_NONBLOCK, read 和 write 的行為是不同的. 在這個情況下, 這個調用簡單 地返回 -EAGAIN(("try it agin")如果一個進程當沒有數據可用時調用 read , 或者如果 當緩沖中沒有空間時它調用 write .
如你可能期望的, 非阻塞操作立刻返回, 允許這個應用程序輪詢數據. 應用程序當使用 stdio 函數處理非阻塞文件中, 必須小心, 因為它們容易搞錯一個的非阻塞返回為 EOF. 它們始終必須檢查 errno.
自然地, O_NONBLOCK 也在 open 方法中有意義. 這個發生在當這個調用真正阻塞長時間 時; 例如, 當打開(為讀存取)一個 沒有寫者的(尚無)FIFO, 或者存取一個磁盤文件使用 一個懸掛鎖. 常常地, 打開一個設備或者成功或者失敗, 沒有必要等待外部的事件. 有時, 但是, 打開這個設備需要一個長的初始化, 並且你可能選擇在你的 open 方法中支持 O_NONBLOCK , 通過立刻返回 -EAGAIN,如果這個標志被設置. 在開始這個設備的初始化進 程之后. 這個驅動可能還實現一個阻塞 open 來支持存取策略, 通過類似於文件鎖的方式. 我們將見到這樣一個實現在"阻塞 open 作為對 EBUSY 的替代"一節, 在本章后面.
一些驅動可能還實現特別的語義給 O_NONBLOCK; 例如, 一個磁帶設備的 open 常常阻塞 直到插入一個磁帶. 如果這個磁帶驅動器使用 O_NONBLOCK 打開, 這個 open 立刻成功, 不管是否介質在或不在.
只有 read, write, 和 open 文件操作受到非阻塞標志影響.