6.2. 阻塞 I/O

2018-02-24 15:49 更新

6.2.?阻塞 I/O

回顧第 3 章, 我們看到如何實(shí)現(xiàn) read 和 write 方法. 在此, 但是, 我們跳過了一個(gè)重要的問題:一個(gè)驅(qū)動(dòng)當(dāng)它無法立刻滿足請(qǐng)求應(yīng)當(dāng)如何響應(yīng)? 一個(gè)對(duì) read 的調(diào)用可能當(dāng)沒有數(shù)據(jù)時(shí)到來, 而以后會(huì)期待更多的數(shù)據(jù). 或者一個(gè)進(jìn)程可能試圖寫, 但是你的設(shè)備沒有準(zhǔn)備好接受數(shù)據(jù), 因?yàn)槟愕妮敵鼍彌_滿了. 調(diào)用進(jìn)程往往不關(guān)心這種問題; 程序員只希望調(diào)用 read 或 write 并且使調(diào)用返回, 在必要的工作已完成后. 這樣, 在這樣的情形中, 你的驅(qū)動(dòng)應(yīng)當(dāng)(缺省地)阻塞進(jìn)程, 使它進(jìn)入睡眠直到請(qǐng)求可繼續(xù).

本節(jié)展示如何使一個(gè)進(jìn)程睡眠并且之后再次喚醒它. 如常, 但是, 我們必須首先解釋幾個(gè)概念.

6.2.1.?睡眠的介紹

對(duì)于一個(gè)進(jìn)程"睡眠"意味著什么? 當(dāng)一個(gè)進(jìn)程被置為睡眠, 它被標(biāo)識(shí)為處于一個(gè)特殊的狀態(tài)并且從調(diào)度器的運(yùn)行隊(duì)列中去除. 直到發(fā)生某些事情改變了那個(gè)狀態(tài), 這個(gè)進(jìn)程將不被在任何 CPU 上調(diào)度, 并且, 因此, 將不會(huì)運(yùn)行. 一個(gè)睡著的進(jìn)程已被擱置到系統(tǒng)的一邊, 等待以后發(fā)生事件.

對(duì)于一個(gè) Linux 驅(qū)動(dòng)使一個(gè)進(jìn)程睡眠是一個(gè)容易做的事情. 但是, 有幾個(gè)規(guī)則必須記住以安全的方式編碼睡眠.

這些規(guī)則的第一個(gè)是: 當(dāng)你運(yùn)行在原子上下文時(shí)不能睡眠. 我們?cè)诘?5 章介紹過原子操作; 一個(gè)原子上下文只是一個(gè)狀態(tài), 這里多個(gè)步驟必須在沒有任何類型的并發(fā)存取的情況下進(jìn)行. 這意味著, 對(duì)于睡眠, 是你的驅(qū)動(dòng)在持有一個(gè)自旋鎖, seqlock, 或者 RCU 鎖時(shí)不能睡眠. 如果你已關(guān)閉中斷你也不能睡眠. 在持有一個(gè)旗標(biāo)時(shí)睡眠是合法的, 但是你應(yīng)當(dāng)仔細(xì)查看這樣做的任何代碼. 如果代碼在持有一個(gè)旗標(biāo)時(shí)睡眠, 任何其他的等待這個(gè)旗標(biāo)的線程也睡眠. 因此發(fā)生在持有旗標(biāo)時(shí)的任何睡眠應(yīng)當(dāng)短暫, 并且你應(yīng)當(dāng)說服自己, 由于持有這個(gè)旗標(biāo), 你不能阻塞這個(gè)將最終喚醒你的進(jìn)程.

另一件要記住的事情是, 當(dāng)你醒來, 你從不知道你的進(jìn)程離開 CPU 多長(zhǎng)時(shí)間或者同時(shí)已經(jīng)發(fā)生了什么改變. 你也常常不知道是否另一個(gè)進(jìn)程已經(jīng)睡眠等待同一個(gè)事件; 那個(gè)進(jìn)程可能在你之前醒來并且獲取了你在等待的資源. 結(jié)果是你不能關(guān)于你醒后的系統(tǒng)狀態(tài)做任何的假設(shè), 并且你必須檢查來確保你在等待的條件是, 確實(shí), 真的.

一個(gè)另外的相關(guān)的點(diǎn), 當(dāng)然, 是你的進(jìn)程不能睡眠除非確信其他人, 在某處的, 將喚醒它. 做喚醒工作的代碼必須也能夠找到你的進(jìn)程來做它的工作. 確保一個(gè)喚醒發(fā)生, 是深入考慮你的代碼和對(duì)于每次睡眠, 確切知道什么系列的事件將結(jié)束那次睡眠. 使你的進(jìn)程可能被找到, 真正地, 通過一個(gè)稱為等待隊(duì)列的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的. 一個(gè)等待隊(duì)列就是它聽起來的樣子:一個(gè)進(jìn)程列表, 都等待一個(gè)特定的事件.

在 Linux 中, 一個(gè)等待隊(duì)列由一個(gè)"等待隊(duì)列頭"來管理, 一個(gè) wait_queue_head_t 類型的結(jié)構(gòu), 定義在<linux/wait.h>中. 一個(gè)等待隊(duì)列頭可被定義和初始化, 使用:


DECLARE_WAIT_QUEUE_HEAD(name); 

或者動(dòng)態(tài)地, 如下:


wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);

我們將很快返回到等待隊(duì)列結(jié)構(gòu), 但是我們知道了足夠多的來首先看看睡眠和喚醒.

6.2.2.?簡(jiǎn)單睡眠

當(dāng)一個(gè)進(jìn)程睡眠, 它這樣做以期望某些條件在以后會(huì)成真. 如我們之前注意到的, 任何睡眠的進(jìn)程必須在它再次醒來時(shí)檢查來確保它在等待的條件真正為真. Linux 內(nèi)核中睡眠的最簡(jiǎn)單方式是一個(gè)宏定義, 稱為 wait_event(有幾個(gè)變體); 它結(jié)合了處理睡眠的細(xì)節(jié)和進(jìn)程在等待的條件的檢查. wait_event 的形式是:


wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)

在所有上面的形式中, queue 是要用的等待隊(duì)列頭. 注意它是"通過值"傳遞的. 條件是一個(gè)被這個(gè)宏在睡眠前后所求值的任意的布爾表達(dá)式; 直到條件求值為真值, 進(jìn)程繼續(xù)睡眠. 注意條件可能被任意次地求值, 因此它不應(yīng)當(dāng)有任何邊界效應(yīng).

如果你使用 wait_event, 你的進(jìn)程被置為不可中斷地睡眠, 如同我們之前已經(jīng)提到的, 它常常不是你所要的. 首選的選擇是 wait_event_interruptible, 它可能被信號(hào)中斷. 這個(gè)版本返回一個(gè)你應(yīng)當(dāng)檢查的整數(shù)值; 一個(gè)非零值意味著你的睡眠被某些信號(hào)打斷, 并且你的驅(qū)動(dòng)可能應(yīng)當(dāng)返回 -ERESTARTSYS. 最后的版本(wait_event_timeout 和 wait_event_interruptible_timeout)等待一段有限的時(shí)間; 在這個(gè)時(shí)間期間(以嘀噠數(shù)表達(dá)的, 我們將在第 7 章討論)超時(shí)后, 這個(gè)宏返回一個(gè) 0 值而不管條件是如何求值的.

圖片的另一半, 當(dāng)然, 是喚醒. 一些其他的執(zhí)行線程(一個(gè)不同的進(jìn)程, 或者一個(gè)中斷處理, 也許)必須為你進(jìn)行喚醒, 因?yàn)槟愕倪M(jìn)程, 當(dāng)然, 是在睡眠. 基本的喚醒睡眠進(jìn)程的函數(shù)稱為 wake_up. 它有幾個(gè)形式(但是我們現(xiàn)在只看其中 2 個(gè)):


void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);

wake_up 喚醒所有的在給定隊(duì)列上等待的進(jìn)程(盡管這個(gè)情形比那個(gè)要復(fù)雜一些, 如同我們之后將見到的). 其他的形式(wake_up_interruptible)限制它自己到處理一個(gè)可中斷的睡眠. 通常, 這 2 個(gè)是不用區(qū)分的(如果你使用可中斷的睡眠); 實(shí)際上, 慣例是使用 wake_up 如果你在使用 wait_event , wake_up_interruptible 如果你在使用 wait_event_interruptible.

我們現(xiàn)在知道足夠多來看一個(gè)簡(jiǎn)單的睡眠和喚醒的例子. 在這個(gè)例子代碼中, 你可找到一個(gè)稱為 sleepy 的模塊. 它實(shí)現(xiàn)一個(gè)有簡(jiǎn)單行為的設(shè)備:任何試圖從這個(gè)設(shè)備讀取的進(jìn)程都被置為睡眠. 無論何時(shí)一個(gè)進(jìn)程寫這個(gè)設(shè)備, 所有的睡眠進(jìn)程被喚醒. 這個(gè)行為由下面的 read 和 write 方法實(shí)現(xiàn):


static DECLARE_WAIT_QUEUE_HEAD(wq);
static int flag = 0;

ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
        printk(KERN_DEBUG "process %i (%s) going to sleep\n",
               current->pid, current->comm);
        wait_event_interruptible(wq, flag != 0);
        flag = 0;
        printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);
        return 0; /* EOF */
}
ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
        printk(KERN_DEBUG "process %i (%s) awakening the readers...\n",
               current->pid, current->comm);
        flag = 1;
        wake_up_interruptible(&wq);
        return count; /* succeed, to avoid retrial */

}

注意這個(gè)例子里 flag 變量的使用. 因?yàn)?wait_event_interruptible 檢查一個(gè)必須變?yōu)檎娴臈l件, 我們使用 flag 來創(chuàng)建那個(gè)條件.

有趣的是考慮當(dāng) sleepy_write 被調(diào)用時(shí)如果有 2 個(gè)進(jìn)程在等待會(huì)發(fā)生什么. 因?yàn)?sleepy_read 重置 flag 為 0 一旦它醒來, 你可能認(rèn)為醒來的第 2 個(gè)進(jìn)程會(huì)立刻回到睡眠. 在一個(gè)單處理器系統(tǒng), 這幾乎一直是發(fā)生的事情. 但是重要的是要理解為什么你不能依賴這個(gè)行為. wake_up_interruptible 調(diào)用將使 2 個(gè)睡眠進(jìn)程醒來. 完全可能它們都注意到 flag 是非零, 在另一個(gè)有機(jī)會(huì)重置它之前. 對(duì)于這個(gè)小模塊, 這個(gè)競(jìng)爭(zhēng)條件是不重要的. 在一個(gè)真實(shí)的驅(qū)動(dòng)中, 這種競(jìng)爭(zhēng)可能導(dǎo)致少見的難于查找的崩潰. 如果正確的操作要求只能有一個(gè)進(jìn)程看到這個(gè)非零值, 它將必須以原子的方式被測(cè)試. 我們將見到一個(gè)真正的驅(qū)動(dòng)如何處理這樣的情況. 但首先我們必須開始另一個(gè)主題.

6.2.3.?阻塞和非阻塞操作

在我們看全功能的 read 和 write 方法的實(shí)現(xiàn)之前, 我們觸及的最后一點(diǎn)是決定何時(shí)使進(jìn)程睡眠. 有時(shí)實(shí)現(xiàn)正確的 unix 語義要求一個(gè)操作不阻塞, 即便它不能完全地進(jìn)行下去.

有時(shí)還有調(diào)用進(jìn)程通知你他不想阻塞, 不管它的 I/O 是否繼續(xù). 明確的非阻塞 I/O 由 filp->f_flags 中的 O_NONBLOCK 標(biāo)志來指示. 這個(gè)標(biāo)志定義于 <linux/fcntl.h>, 被 <linux/fs.h>自動(dòng)包含. 這個(gè)標(biāo)志得名自"打開-非阻塞", 因?yàn)樗稍诖蜷_時(shí)指定(并且起初只能在那里指定). 如果你瀏覽源碼, 你會(huì)發(fā)現(xiàn)一些對(duì)一個(gè) O_NDELAY 標(biāo)志的引用; 這是一個(gè)替代 O_NONBLOCK 的名子, 為兼容 System V 代碼而被接受的. 這個(gè)標(biāo)志缺省地被清除, 因?yàn)橐粋€(gè)等待數(shù)據(jù)的進(jìn)程的正常行為僅僅是睡眠. 在一個(gè)阻塞操作的情況下, 這是缺省地, 下列的行為應(yīng)當(dāng)實(shí)現(xiàn)來符合標(biāo)準(zhǔn)語法:

  • 如果一個(gè)進(jìn)程調(diào)用 read 但是沒有數(shù)據(jù)可用(尚未), 這個(gè)進(jìn)程必須阻塞. 這個(gè)進(jìn)程在有數(shù)據(jù)達(dá)到時(shí)被立刻喚醒, 并且那個(gè)數(shù)據(jù)被返回給調(diào)用者, 即便小于在給方法的 count 參數(shù)中請(qǐng)求的數(shù)量.

  • 如果一個(gè)進(jìn)程調(diào)用 write 并且在緩沖中沒有空間, 這個(gè)進(jìn)程必須阻塞, 并且它必須在一個(gè)與用作 read 的不同的等待隊(duì)列中. 當(dāng)一些數(shù)據(jù)被寫入硬件設(shè)備, 并且在輸出緩沖中的空間變空閑, 這個(gè)進(jìn)程被喚醒并且寫調(diào)用成功, 盡管數(shù)據(jù)可能只被部分寫入如果在緩沖只沒有空間給被請(qǐng)求的 count 字節(jié).

這 2 句都假定有輸入和輸出緩沖; 實(shí)際上, 幾乎每個(gè)設(shè)備驅(qū)動(dòng)都有. 要求有輸入緩沖是為了避免丟失到達(dá)的數(shù)據(jù), 當(dāng)無人在讀時(shí). 相反, 數(shù)據(jù)在寫時(shí)不能丟失, 因?yàn)槿绻到y(tǒng)調(diào)用不能接收數(shù)據(jù)字節(jié), 它們保留在用戶空間緩沖. 即便如此, 輸出緩沖幾乎一直有用, 對(duì)于從硬件擠出更多的性能.

在驅(qū)動(dòng)中實(shí)現(xiàn)輸出緩沖所獲得的性能來自減少了上下文切換和用戶級(jí)/內(nèi)核級(jí)切換的次數(shù). 沒有一個(gè)輸出緩沖(假定一個(gè)慢速設(shè)備), 每次系統(tǒng)調(diào)用接收這樣一個(gè)或幾個(gè)字符, 并且當(dāng)一個(gè)進(jìn)程在 write 中睡眠, 另一個(gè)進(jìn)程運(yùn)行(那是一次上下文切換). 當(dāng)?shù)谝粋€(gè)進(jìn)程被喚醒, 它恢復(fù)(另一次上下文切換), 寫返回(內(nèi)核/用戶轉(zhuǎn)換), 并且這個(gè)進(jìn)程重新發(fā)出系統(tǒng)調(diào)用來寫入更多的數(shù)據(jù)(用戶/內(nèi)核轉(zhuǎn)換); 這個(gè)調(diào)用阻塞并且循環(huán)繼續(xù). 增加一個(gè)輸出緩沖可允許驅(qū)動(dòng)在每個(gè)寫調(diào)用中接收大的數(shù)據(jù)塊, 性能上有相應(yīng)的提高. 如果這個(gè)緩沖足夠大, 寫調(diào)用在第一次嘗試就成功 -- 被緩沖的數(shù)據(jù)之后將被推到設(shè)備 -- 不必控制需要返回用戶空間來第二次或者第三次寫調(diào)用. 選擇一個(gè)合適的值給輸出緩沖顯然是設(shè)備特定的.

我們不使用一個(gè)輸入緩沖在 scull中, 因?yàn)閿?shù)據(jù)當(dāng)發(fā)出 read 時(shí)已經(jīng)可用. 類似的, 不用輸出緩沖, 因?yàn)閿?shù)據(jù)被簡(jiǎn)單地拷貝到和設(shè)備關(guān)聯(lián)的內(nèi)存區(qū). 本質(zhì)上, 這個(gè)設(shè)備是一個(gè)緩沖, 因此額外緩沖的實(shí)現(xiàn)可能是多余的. 我們將在第 10 章見到緩沖的使用.

如果指定 O_NONBLOCK, read 和 write 的行為是不同的. 在這個(gè)情況下, 這個(gè)調(diào)用簡(jiǎn)單地返回 -EAGAIN(("try it agin")如果一個(gè)進(jìn)程當(dāng)沒有數(shù)據(jù)可用時(shí)調(diào)用 read , 或者如果當(dāng)緩沖中沒有空間時(shí)它調(diào)用 write .

如你可能期望的, 非阻塞操作立刻返回, 允許這個(gè)應(yīng)用程序輪詢數(shù)據(jù). 應(yīng)用程序當(dāng)使用 stdio 函數(shù)處理非阻塞文件中, 必須小心, 因?yàn)樗鼈內(nèi)菀赘沐e(cuò)一個(gè)的非阻塞返回為 EOF. 它們始終必須檢查 errno.

自然地, O_NONBLOCK 也在 open 方法中有意義. 這個(gè)發(fā)生在當(dāng)這個(gè)調(diào)用真正阻塞長(zhǎng)時(shí)間時(shí); 例如, 當(dāng)打開(為讀存取)一個(gè) 沒有寫者的(尚無)FIFO, 或者存取一個(gè)磁盤文件使用一個(gè)懸掛鎖. 常常地, 打開一個(gè)設(shè)備或者成功或者失敗, 沒有必要等待外部的事件. 有時(shí), 但是, 打開這個(gè)設(shè)備需要一個(gè)長(zhǎng)的初始化, 并且你可能選擇在你的 open 方法中支持 O_NONBLOCK , 通過立刻返回 -EAGAIN,如果這個(gè)標(biāo)志被設(shè)置. 在開始這個(gè)設(shè)備的初始化進(jìn)程之后. 這個(gè)驅(qū)動(dòng)可能還實(shí)現(xiàn)一個(gè)阻塞 open 來支持存取策略, 通過類似于文件鎖的方式. 我們將見到這樣一個(gè)實(shí)現(xiàn)在"阻塞 open 作為對(duì) EBUSY 的替代"一節(jié), 在本章后面.

一些驅(qū)動(dòng)可能還實(shí)現(xiàn)特別的語義給 O_NONBLOCK; 例如, 一個(gè)磁帶設(shè)備的 open 常常阻塞直到插入一個(gè)磁帶. 如果這個(gè)磁帶驅(qū)動(dòng)器使用 O_NONBLOCK 打開, 這個(gè) open 立刻成功, 不管是否介質(zhì)在或不在.

只有 read, write, 和 open 文件操作受到非阻塞標(biāo)志影響.

6.2.4.?一個(gè)阻塞 I/O 的例子

最后, 我們看一個(gè)實(shí)現(xiàn)了阻塞 I/O 的真實(shí)驅(qū)動(dòng)方法的例子. 這個(gè)例子來自 scullpipe 驅(qū)動(dòng); 它是 scull 的一個(gè)特殊形式, 實(shí)現(xiàn)了一個(gè)象管道的設(shè)備.

在驅(qū)動(dòng)中, 一個(gè)阻塞在讀調(diào)用上的進(jìn)程被喚醒, 當(dāng)數(shù)據(jù)到達(dá)時(shí); 常常地硬件發(fā)出一個(gè)中斷來指示這樣一個(gè)事件, 并且驅(qū)動(dòng)喚醒等待的進(jìn)程作為處理這個(gè)中斷的一部分. scullpipe 驅(qū)動(dòng)不同, 以至于它可運(yùn)行而不需要任何特殊的硬件或者一個(gè)中斷處理. 我們選擇來使用另一個(gè)進(jìn)程來產(chǎn)生數(shù)據(jù)并喚醒讀進(jìn)程; 類似地, 讀進(jìn)程被用來喚醒正在等待緩沖空間可用的寫者進(jìn)程.

這個(gè)設(shè)備驅(qū)動(dòng)使用一個(gè)設(shè)備結(jié)構(gòu), 它包含 2 個(gè)等待隊(duì)列和一個(gè)緩沖. 緩沖大小是以常用的方法可配置的(在編譯時(shí)間, 加載時(shí)間, 或者運(yùn)行時(shí)間).


struct scull_pipe
{
        wait_queue_head_t inq, outq; /* read and write queues */
        char *buffer, *end; /* begin of buf, end of buf */
        int buffersize; /* used in pointer arithmetic */
        char *rp, *wp; /* where to read, where to write */
        int nreaders, nwriters; /* number of openings for r/w */
        struct fasync_struct *async_queue; /* asynchronous readers */
        struct semaphore sem;  /* mutual exclusion semaphore */
        struct cdev cdev;  /* Char device structure */
};

讀實(shí)現(xiàn)既管理阻塞也管理非阻塞輸入, 看來如此:


static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
        struct scull_pipe *dev = filp->private_data;
        if (down_interruptible(&dev->sem))
                return -ERESTARTSYS;

        while (dev->rp == dev->wp)
        { /* nothing to read */
                up(&dev->sem); /* release the lock */
                if (filp->f_flags & O_NONBLOCK)

                        return -EAGAIN;
                PDEBUG("\"%s\" reading: going to sleep\n", current->comm);
                if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
                        return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ /* otherwise loop, but first reacquire the lock */
                if (down_interruptible(&dev->sem))
                        return -ERESTARTSYS;
        }
        /* ok, data is there, return something */

        if (dev->wp > dev->rp)
                count = min(count, (size_t)(dev->wp - dev->rp));
        else /* the write pointer has wrapped, return data up to dev->end */
                count = min(count, (size_t)(dev->end - dev->rp));
        if (copy_to_user(buf, dev->rp, count))
        {
                up (&dev->sem);
                return -EFAULT;
        }
        dev->rp += count;
        if (dev->rp == dev->end)

                dev->rp = dev->buffer; /* wrapped */
        up (&dev->sem);

        /* finally, awake any writers and return */
        wake_up_interruptible(&dev->outq);
        PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count);
        return count;
}

如同你可見的, 我們?cè)诖a中留有一些 PDEBUG 語句. 當(dāng)你編譯這個(gè)驅(qū)動(dòng), 你可使能消息機(jī)制來易于跟隨不同進(jìn)程間的交互.

讓我們仔細(xì)看看 scull_p_read 如何處理對(duì)數(shù)據(jù)的等待. 這個(gè) while 循環(huán)在持有設(shè)備旗標(biāo)下測(cè)試這個(gè)緩沖. 如果有數(shù)據(jù)在那里, 我們知道我們可立刻返回給用戶, 不必睡眠, 因此整個(gè)循環(huán)被跳過. 相反, 如果這個(gè)緩沖是空的, 我們必須睡眠. 但是在我們可做這個(gè)之前, 我們必須丟掉設(shè)備旗標(biāo); 如果我們要持有它而睡眠, 就不會(huì)有寫者有機(jī)會(huì)喚醒我們. 一旦這個(gè)確保被丟掉, 我們做一個(gè)快速檢查來看是否用戶已請(qǐng)求非阻塞 I/O, 并且如果是這樣就返回. 否則, 是時(shí)間調(diào)用 wait_event_interruptible.

一旦我們過了這個(gè)調(diào)用, 某些東東已經(jīng)喚醒了我們, 但是我們不知道是什么. 一個(gè)可能是進(jìn)程接收到了一個(gè)信號(hào). 包含 wait_event_interruptible 調(diào)用的這個(gè) if 語句檢查這種情況. 這個(gè)語句保證了正確的和被期望的對(duì)信號(hào)的反應(yīng), 它可能負(fù)責(zé)喚醒這個(gè)進(jìn)程(因?yàn)槲覀兲幱谝粋€(gè)可中斷的睡眠). 如果一個(gè)信號(hào)已經(jīng)到達(dá)并且它沒有被這個(gè)進(jìn)程阻塞, 正確的做法是讓內(nèi)核的上層處理這個(gè)事件. 到此, 這個(gè)驅(qū)動(dòng)返回 -ERESTARTSYS 到調(diào)用者; 這個(gè)值被虛擬文件系統(tǒng)(VFS)在內(nèi)部使用, 它或者重啟系統(tǒng)調(diào)用或者返回 -EINTR 給用戶空間. 我們使用相同類型的檢查來處理信號(hào), 給每個(gè)讀和寫實(shí)現(xiàn).

但是, 即便沒有一個(gè)信號(hào), 我們還是不確切知道有數(shù)據(jù)在那里為獲取. 其他人也可能已經(jīng)在等待數(shù)據(jù), 并且它們可能贏得競(jìng)爭(zhēng)并且首先得到數(shù)據(jù). 因此我們必須再次獲取設(shè)備旗標(biāo); 只有這時(shí)我們才可以測(cè)試讀緩沖(在 while 循環(huán)中)并且真正知道我們可以返回緩沖中的數(shù)據(jù)給用戶. 全部這個(gè)代碼的最終結(jié)果是, 當(dāng)我們從 while 循環(huán)中退出時(shí), 我們知道旗標(biāo)被獲得并且緩沖中有數(shù)據(jù)我們可以用.

僅僅為了完整, 我們要注意, scull_p_read 可以在另一個(gè)地方睡眠, 在我們獲得設(shè)備旗標(biāo)之后: 對(duì) copy_to_user 的調(diào)用. 如果 scull 當(dāng)在內(nèi)核和用戶空間之間拷貝數(shù)據(jù)時(shí)睡眠, 它在持有設(shè)備旗標(biāo)中睡眠. 在這種情況下持有旗標(biāo)是合理的因?yàn)樗荒芩梨i系統(tǒng)(我們知道內(nèi)核將進(jìn)行拷貝到用戶空間并且在不加鎖進(jìn)程中的同一個(gè)旗標(biāo)下喚醒我們), 并且因?yàn)橹匾氖窃O(shè)備內(nèi)存數(shù)組在驅(qū)動(dòng)睡眠時(shí)不改變.

6.2.5.?高級(jí)睡眠

許多驅(qū)動(dòng)能夠滿足它們的睡眠要求, 使用至今我們已涉及到的函數(shù). 但是, 有時(shí)需要深入理解 Linux 等待隊(duì)列機(jī)制如何工作. 復(fù)雜的加鎖或者性能需要可強(qiáng)制一個(gè)驅(qū)動(dòng)來使用低層函數(shù)來影響一個(gè)睡眠. 在本節(jié), 我們?cè)诘蛯涌炊斫庠谝粋€(gè)進(jìn)程睡眠時(shí)發(fā)生了什么.

6.2.5.1.?一個(gè)進(jìn)程如何睡眠

如果我們深入 <linux/wait.h>, 你見到在 wait_queue_head_t 類型后面的數(shù)據(jù)結(jié)構(gòu)是非常簡(jiǎn)單的; 它包含一個(gè)自旋鎖和一個(gè)鏈表. 這個(gè)鏈表是一個(gè)等待隊(duì)列入口, 它被聲明做 wait_queue_t. 這個(gè)結(jié)構(gòu)包含關(guān)于睡眠進(jìn)程的信息和它想怎樣被喚醒.

使一個(gè)進(jìn)程睡眠的第一步常常是分配和初始化一個(gè) wait_queue_t 結(jié)構(gòu), 隨后將其添加到正確的等待隊(duì)列. 當(dāng)所有東西都就位了, 負(fù)責(zé)喚醒工作的人就可以找到正確的進(jìn)程.

下一步是設(shè)置進(jìn)程的狀態(tài)來標(biāo)志它為睡眠. 在 <linux/sched.h> 中定義有幾個(gè)任務(wù)狀態(tài). TASK_RUNNING 意思是進(jìn)程能夠運(yùn)行, 盡管不必在任何特定的時(shí)刻在處理器上運(yùn)行. 有 2 個(gè)狀態(tài)指示一個(gè)進(jìn)程是在睡眠: TASK_INTERRUPTIBLE 和 TASK_UNTINTERRUPTIBLE; 當(dāng)然, 它們對(duì)應(yīng) 2 類的睡眠. 其他的狀態(tài)正常地和驅(qū)動(dòng)編寫者無關(guān).

在 2.6 內(nèi)核, 對(duì)于驅(qū)動(dòng)代碼通常不需要直接操作進(jìn)程狀態(tài). 但是, 如果你需要這樣做, 使用的代碼是:


void set_current_state(int new_state); 

在老的代碼中, 你常常見到如此的東西:


current->state = TASK_INTERRUPTIBLE; 

但是象這樣直接改變 current 是不鼓勵(lì)的; 當(dāng)數(shù)據(jù)結(jié)構(gòu)改變時(shí)這樣的代碼會(huì)輕易地失效. 但是, 上面的代碼確實(shí)展示了自己改變一個(gè)進(jìn)程的當(dāng)前狀態(tài)不能使其睡眠. 通過改變 current 狀態(tài), 你已改變了調(diào)度器對(duì)待進(jìn)程的方式, 但是你還未讓出處理器.

放棄處理器是最后一步, 但是要首先做一件事: 你必須先檢查你在睡眠的條件. 做這個(gè)檢查失敗會(huì)引入一個(gè)競(jìng)爭(zhēng)條件; 如果在你忙于上面的這個(gè)過程并且有其他的線程剛剛試圖喚醒你, 如果這個(gè)條件變?yōu)檎鏁?huì)發(fā)生什么? 你可能錯(cuò)過喚醒并且睡眠超過你預(yù)想的時(shí)間. 因此, 在睡眠的代碼下面, 典型地你會(huì)見到下面的代碼:


if (!condition)
    schedule();

通過在設(shè)置了進(jìn)程狀態(tài)后檢查我們的條件, 我們涵蓋了所有的可能的事件進(jìn)展. 如果我們?cè)诘却臈l件已經(jīng)在設(shè)置進(jìn)程狀態(tài)之前到來, 我們?cè)谶@個(gè)檢查中注意到并且不真正地睡眠. 如果之后發(fā)生了喚醒, 進(jìn)程被置為可運(yùn)行的不管是否我們已真正進(jìn)入睡眠.

調(diào)用 schedule , 當(dāng)然, 是引用調(diào)度器和讓出 CPU 的方式. 無論何時(shí)你調(diào)用這個(gè)函數(shù), 你是在告訴內(nèi)核來考慮應(yīng)當(dāng)運(yùn)行哪個(gè)進(jìn)程并且轉(zhuǎn)換控制到那個(gè)進(jìn)程, 如果必要. 因此你從不知道在 schedule 返回到你的代碼會(huì)是多長(zhǎng)時(shí)間.

在 if 測(cè)試和可能的調(diào)用 schedule (并從其返回)之后, 有些清理工作要做. 因?yàn)檫@個(gè)代碼不再想睡眠, 它必須保證任務(wù)狀態(tài)被重置為 TASK_RUNNING. 如果代碼只是從 schedule 返回, 這一步是不必要的; 那個(gè)函數(shù)不會(huì)返回直到進(jìn)程處于可運(yùn)行態(tài). 如果由于不再需要睡眠而對(duì) schedule 的調(diào)用被跳過, 進(jìn)程狀態(tài)將不正確. 還有必要從等待隊(duì)列中去除這個(gè)進(jìn)程, 否則它可能被多次喚醒.

6.2.5.2.?手動(dòng)睡眠

在 Linux 內(nèi)核的之前的版本, 正式的睡眠要求程序員手動(dòng)處理所有上面的步驟. 它是一個(gè)繁瑣的過程, 包含相當(dāng)多的易出錯(cuò)的樣板式的代碼. 程序員如果愿意還是可能用那種方式手動(dòng)睡眠; <linux/sched.h> 包含了所有需要的定義, 以及圍繞例子的內(nèi)核源碼. 但是, 有一個(gè)更容易的方式.

第一步是創(chuàng)建和初始化一個(gè)等待隊(duì)列. 這常常由這個(gè)宏定義完成:


DEFINE_WAIT(my_wait); 

其中, name 是等待隊(duì)列入口項(xiàng)的名子. 你可用 2 步來做:


wait_queue_t my_wait;
init_wait(&my_wait);

但是常常更容易的做法是放一個(gè) DEFINE_WAIT 行在循環(huán)的頂部, 來實(shí)現(xiàn)你的睡眠.

下一步是添加你的等待隊(duì)列入口到隊(duì)列, 并且設(shè)置進(jìn)程狀態(tài). 2 個(gè)任務(wù)都由這個(gè)函數(shù)處理:


void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state); 

這里, queue 和 wait 分別地是等待隊(duì)列頭和進(jìn)程入口. state 是進(jìn)程的新狀態(tài); 它應(yīng)當(dāng)或者是 TASK_INTERRUPTIBLE(給可中斷的睡眠, 這常常是你所要的)或者 TASK_UNINTERRUPTIBLE(給不可中斷睡眠).

在調(diào)用 prepare_to_wait 之后, 進(jìn)程可調(diào)用 schedule -- 在它已檢查確認(rèn)它仍然需要等待之后. 一旦 schedule 返回, 就到了清理時(shí)間. 這個(gè)任務(wù), 也, 被一個(gè)特殊的函數(shù)處理:


void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait); 

之后, 你的代碼可測(cè)試它的狀態(tài)并且看是否它需要再次等待.

我們?cè)缭撔枰粋€(gè)例子了. 之前我們看了 給 scullpipe 的 read 方法, 它使用 wait_event. 同一個(gè)驅(qū)動(dòng)中的 write 方法使用 prepare_to_wait 和 finish_wait 來實(shí)現(xiàn)它的等待. 正常地, 你不會(huì)在一個(gè)驅(qū)動(dòng)中象這樣混用各種方法, 但是我們這樣作是為了能夠展示 2 種處理睡眠的方式.

為完整起見, 首先, 我們看 write 方法本身:


/* How much space is free? */
static int spacefree(struct scull_pipe *dev)
{

        if (dev->rp == dev->wp)
                return dev->buffersize - 1;
        return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1;
}

static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,
                             loff_t *f_pos)
{

        struct scull_pipe *dev = filp->private_data;
        int result;
        if (down_interruptible(&dev->sem))
                return -ERESTARTSYS;

        /* Make sure there's space to write */
        result = scull_getwritespace(dev, filp);
        if (result)
                return result; /* scull_getwritespace called up(&dev->sem) */
        /* ok, space is there, accept something */
        count = min(count, (size_t)spacefree(dev));
        if (dev->wp >= dev->rp)
                count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */
        else /* the write pointer has wrapped, fill up to rp-1 */
                count = min(count, (size_t)(dev->rp - dev->wp - 1));
        PDEBUG("Going to accept %li bytes to %p from %p\n", (long)count, dev->wp, buf);
        if (copy_from_user(dev->wp, buf, count))
        {
                up (&dev->sem);
                return -EFAULT;
        }
        dev->wp += count;
        if (dev->wp == dev->end)
                dev->wp = dev->buffer; /* wrapped */
        up(&dev->sem);

        /* finally, awake any reader */
        wake_up_interruptible(&dev->inq); /* blocked in read() and select() */

        /* and signal asynchronous readers, explained late in chapter 5 */
        if (dev->async_queue)
                kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
        PDEBUG("\"%s\" did write %li bytes\n",current->comm, (long)count);
        return count;
}

這個(gè)代碼看來和 read 方法類似, 除了我們已經(jīng)將睡眠代碼放到了一個(gè)單獨(dú)的函數(shù), 稱為 scull_getwritespace. 它的工作是確保在緩沖中有空間給新的數(shù)據(jù), 睡眠直到有空間可用. 一旦空間在, scull_p_write 可簡(jiǎn)單地拷貝用戶的數(shù)據(jù)到那里, 調(diào)整指針, 并且喚醒可能已經(jīng)在等待讀取數(shù)據(jù)的進(jìn)程.

處理實(shí)際的睡眠的代碼是:


/* Wait for space for writing; caller must hold device semaphore. On
 * error the semaphore will be released before returning. */
static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
{

        while (spacefree(dev) == 0)
        { /* full */
                DEFINE_WAIT(wait);

                up(&dev->sem);
                if (filp->f_flags & O_NONBLOCK)
                        return -EAGAIN;

                PDEBUG("\"%s\" writing: going to sleep\n",current->comm);
                prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE);
                if (spacefree(dev) == 0)
                        schedule();
                finish_wait(&dev->outq, &wait);
                if (signal_pending(current))

                        return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
                if (down_interruptible(&dev->sem))
                        return -ERESTARTSYS;
        }
        return 0;

}

再次注意 while 循環(huán). 如果有空間可用而不必睡眠, 這個(gè)函數(shù)簡(jiǎn)單地返回. 否則, 它必須丟掉設(shè)備旗標(biāo)并且等待. 這個(gè)代碼使用 DEFINE_WAIT 來設(shè)置一個(gè)等待隊(duì)列入口并且 prepare_to_wait 來準(zhǔn)備好實(shí)際的睡眠. 接著是對(duì)緩沖的必要的檢查; 我們必須處理的情況是在我們已經(jīng)進(jìn)入 while 循環(huán)后以及在我們將自己放入等待隊(duì)列之前 (并且丟棄了旗標(biāo)), 緩沖中有空間可用了. 沒有這個(gè)檢查, 如果讀進(jìn)程能夠在那時(shí)完全清空緩沖, 我們可能錯(cuò)過我們能得到的唯一的喚醒并且永遠(yuǎn)睡眠. 在說服我們自己必須睡眠之后, 我們調(diào)用 schedule.

值得再看看這個(gè)情況: 當(dāng)睡眠發(fā)生在 if 語句測(cè)試和調(diào)用 schedule 之間, 會(huì)發(fā)生什么? 在這個(gè)情況里, 都好. 這個(gè)喚醒重置了進(jìn)程狀態(tài)為 TASK_RUNNING 并且 schedule 返回 -- 盡管不必馬上. 只要這個(gè)測(cè)試發(fā)生在進(jìn)程放置自己到等待隊(duì)列和改變它的狀態(tài)之后, 事情都會(huì)順利.

為了結(jié)束, 我們調(diào)用 finish_wait. 對(duì) signal_pending 的調(diào)用告訴我們是否我們被一個(gè)信號(hào)喚醒; 如果是, 我們需要返回到用戶并且使它們稍后再試. 否則, 我們請(qǐng)求旗標(biāo), 并且再次照常測(cè)試空閑空間.

6.2.5.3.?互斥等待

我們已經(jīng)見到當(dāng)一個(gè)進(jìn)程調(diào)用 wake_up 在等待隊(duì)列上, 所有的在這個(gè)隊(duì)列上等待的進(jìn)程被置為可運(yùn)行的. 在許多情況下, 這是正確的做法. 但是, 在別的情況下, 可能提前知道只有一個(gè)被喚醒的進(jìn)程將成功獲得需要的資源, 并且其余的將簡(jiǎn)單地再次睡眠. 每個(gè)這樣的進(jìn)程, 但是, 必須獲得處理器, 競(jìng)爭(zhēng)資源(和任何的管理用的鎖), 并且明確地回到睡眠. 如果在等待隊(duì)列中的進(jìn)程數(shù)目大, 這個(gè)"驚群"行為可能嚴(yán)重降低系統(tǒng)的性能.

為應(yīng)對(duì)實(shí)際世界中的驚群?jiǎn)栴}, 內(nèi)核開發(fā)者增加了一個(gè)"互斥等待"選項(xiàng)到內(nèi)核中. 一個(gè)互斥等待的行為非常象一個(gè)正常的睡眠, 有 2 個(gè)重要的不同:

  • 當(dāng)一個(gè)等待隊(duì)列入口有 WQ_FLAG_EXCLUSEVE 標(biāo)志置位, 它被添加到等待隊(duì)列的尾部. 沒有這個(gè)標(biāo)志的入口項(xiàng), 相反, 添加到開始.

  • 當(dāng) wake_up 被在一個(gè)等待隊(duì)列上調(diào)用, 它在喚醒第一個(gè)有 WQ_FLAG_EXCLUSIVE 標(biāo)志的進(jìn)程后停止.

最后的結(jié)果是進(jìn)行互斥等待的進(jìn)程被一次喚醒一個(gè), 以順序的方式, 并且沒有引起驚群?jiǎn)栴}. 但內(nèi)核仍然每次喚醒所有的非互斥等待者.

在驅(qū)動(dòng)中采用互斥等待是要考慮的, 如果滿足 2 個(gè)條件: 你希望對(duì)資源的有效競(jìng)爭(zhēng), 并且喚醒一個(gè)進(jìn)程就足夠來完全消耗資源當(dāng)資源可用時(shí). 互斥等待對(duì) Apacheweb 服務(wù)器工作地很好, 例如; 當(dāng)一個(gè)新連接進(jìn)入, 確實(shí)地系統(tǒng)中的一個(gè) Apache 進(jìn)程應(yīng)當(dāng)被喚醒來處理它. 我們?cè)?scullpipe 驅(qū)動(dòng)中不使用互斥等待, 但是; 很少見到競(jìng)爭(zhēng)數(shù)據(jù)的讀者(或者競(jìng)爭(zhēng)緩沖空間的寫者), 并且我們無法知道一個(gè)讀者, 一旦被喚醒, 將消耗所有的可用數(shù)據(jù).

使一個(gè)進(jìn)程進(jìn)入可中斷的等待, 是調(diào)用 prepare_to_wait_exclusive 的簡(jiǎn)單事情:


void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state); 

這個(gè)調(diào)用, 當(dāng)用來代替 prepare_to_wait, 設(shè)置"互斥"標(biāo)志在等待隊(duì)列入口項(xiàng)并且添加這個(gè)進(jìn)程到等待隊(duì)列的尾部. 注意沒有辦法使用 wait_event 和它的變體來進(jìn)行互斥等待.

6.2.5.4.?喚醒的細(xì)節(jié)

我們已展現(xiàn)的喚醒進(jìn)程的樣子比內(nèi)核中真正發(fā)生的要簡(jiǎn)單. 當(dāng)進(jìn)程被喚醒時(shí)產(chǎn)生的真正動(dòng)作是被位于等待隊(duì)列入口項(xiàng)的一個(gè)函數(shù)控制的. 缺省的喚醒函數(shù)[22]設(shè)置進(jìn)程為可運(yùn)行的狀態(tài), 并且可能地進(jìn)行一個(gè)上下文切換到有更高優(yōu)先級(jí)進(jìn)程. 設(shè)備驅(qū)動(dòng)應(yīng)當(dāng)從不需要提供一個(gè)不同的喚醒函數(shù); 如果你例外, 關(guān)于如何做的信息見 <linux/wait.h>

我們尚未看到所有的 wake_up 變體. 大部分驅(qū)動(dòng)編寫者從不需要其他的, 但是, 為完整起見, 這里是整個(gè)集合:

wake_up(wait_queue_head_t queue);wake_up_interruptible(wait_queue_head_t queue);
wake_up 喚醒隊(duì)列中的每個(gè)不是在互斥等待中的進(jìn)程, 并且就只一個(gè)互斥等待者, 如果它存在. wake_up_interruptible 同樣, 除了它跳過處于不可中斷睡眠的進(jìn)程. 這些函數(shù), 在返回之前, 使一個(gè)或多個(gè)進(jìn)程被喚醒來被調(diào)度(盡管如果它們被從一個(gè)原子上下文調(diào)用, 這就不會(huì)發(fā)生).

wake_up_nr(wait_queue_head_t queue, int nr);wake_up_interruptible_nr(wait_queue_head_t queue, int nr);
這些函數(shù)類似 wake_up, 除了它們能夠喚醒多達(dá) nr 個(gè)互斥等待者, 而不只是一個(gè). 注意傳遞 0 被解釋為請(qǐng)求所有的互斥等待者都被喚醒, 而不是一個(gè)沒有.

wake_up_all(wait_queue_head_t queue);wake_up_interruptible_all(wait_queue_head_t queue);
這種 wake_up 喚醒所有的進(jìn)程, 不管它們是否進(jìn)行互斥等待(盡管可中斷的類型仍然跳過在做不可中斷等待的進(jìn)程)

wake_up_interruptible_sync(wait_queue_head_t *queue);
正常地, 一個(gè)被喚醒的進(jìn)程可能搶占當(dāng)前進(jìn)程, 并且在 wake_up 返回之前被調(diào)度到處理器. 換句話說, 調(diào)用 wake_up 可能不是原子的. 如果調(diào)用 wake_up 的進(jìn)程運(yùn)行在原子上下文(它可能持有一個(gè)自旋鎖, 例如, 或者是一個(gè)中斷處理), 這個(gè)重調(diào)度不會(huì)發(fā)生. 正常地, 那個(gè)保護(hù)是足夠的. 但是, 如果你需要明確要求不要被調(diào)度出處理器在那時(shí), 你可以使用 wake_up_interruptible 的"同步"變體. 這個(gè)函數(shù)最常用在當(dāng)調(diào)用者要無論如何重新調(diào)度, 并且它會(huì)更有效的來首先簡(jiǎn)單地完成剩下的任何小的工作.

如果上面的全部?jī)?nèi)容在第一次閱讀時(shí)沒有完全清楚, 不必?fù)?dān)心. 很少請(qǐng)求會(huì)需要調(diào)用 wake_up_interruptible 之外的.

6.2.5.5.?以前的歷史: sleep_on

如果你花些時(shí)間深入內(nèi)核源碼, 你可能遇到我們到目前忽略討論的 2 個(gè)函數(shù):


void sleep_on(wait_queue_head_t *queue);
void interruptible_sleep_on(wait_queue_head_t *queue);

如你可能期望的, 這些函數(shù)無條件地使當(dāng)前進(jìn)程睡眠在給定隊(duì)列尚. 這些函數(shù)強(qiáng)烈不推薦, 但是, 并且你應(yīng)當(dāng)從不使用它們. 如果你想想它則問題是明顯的: sleep_on 沒提供方法來避免競(jìng)爭(zhēng)條件. 常常有一個(gè)窗口在當(dāng)你的代碼決定它必須睡眠時(shí)和當(dāng) sleep_on 真正影響到睡眠時(shí). 在那個(gè)窗口期間到達(dá)的喚醒被錯(cuò)過. 因此, 調(diào)用 sleep_on 的代碼從不是完全安全的.

當(dāng)前計(jì)劃對(duì) sleep_on 和 它的變體的調(diào)用(有多個(gè)我們尚未展示的超時(shí)的類型)在不太遠(yuǎn)的將來從內(nèi)核中去掉.

6.2.6.?測(cè)試 scullpipe 驅(qū)動(dòng)

我們已經(jīng)見到了 scullpipe 驅(qū)動(dòng)如何實(shí)現(xiàn)阻塞 I/O. 如果你想試一試, 這個(gè)驅(qū)動(dòng)的源碼可在剩下的本書例子中找到. 阻塞 I/O 的動(dòng)作可通過打開 2 個(gè)窗口見到. 第一個(gè)可運(yùn)行一個(gè)命令諸如 cat /dev/scullpipe. 如果你接著, 在另一個(gè)窗口拷貝文件到 /dev/scullpipe, 你可見到文件的內(nèi)容出現(xiàn)在第一個(gè)窗口.

測(cè)試非阻塞的動(dòng)作是技巧性的, 因?yàn)榭捎糜?shell 的傳統(tǒng)的程序不做非阻塞操作. misc-progs 源碼目錄包含下面簡(jiǎn)單的程序, 稱為 nbtest, 來測(cè)試非阻塞操作. 所有它做的是拷貝它的輸入到它的輸出, 使用非阻塞 I/O 和在重試間延時(shí). 延時(shí)時(shí)間在命令行被傳遞被缺省是 1 秒.


int main(int argc, char **argv)
{

        int delay = 1, n, m = 0;
        if (argc > 1)
                delay=atoi(argv[1]);
        fcntl(0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK); /* stdin */
        fcntl(1, F_SETFL, fcntl(1,F_GETFL) | O_NONBLOCK); /* stdout */

        while (1) {
                n = read(0, buffer, 4096);
                if (n >= 0)
                        m = write(1, buffer, n);
                if ((n < 0 || m < 0) && (errno != EAGAIN))
                        break;
                sleep(delay);
        }
        perror(n < 0 ? "stdin" : "stdout");
        exit(1);
}

如果你在一個(gè)進(jìn)程跟蹤工具, 如 strace 下運(yùn)行這個(gè)程序, 你可見到每個(gè)操作的成功或者失敗, 依賴是否當(dāng)進(jìn)行操作時(shí)有數(shù)據(jù)可用.

[22] 它有一個(gè)想象的名子 default_wake_function.

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)