?? ch06s02.html
字號(hào):
</pre>
<p>這個(gè)代碼看來(lái)和 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)程.</p>
<p>處理實(shí)際的睡眠的代碼是:</p>
<pre class="programlisting">
/* 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;
}
</pre>
<p>再次注意 while 循環(huán). 如果有空間可用而不必睡眠, 這個(gè)函數(shù)簡(jiǎn)單地返回. 否則, 它必須丟掉設(shè)備旗標(biāo)并且等待. 這個(gè)代碼使用 DEFINE_WAIT 來(lái)設(shè)置一個(gè)等待隊(duì)列入口并且 prepare_to_wait 來(lái)準(zhǔn)備好實(shí)際的睡眠. 接著是對(duì)緩沖的必要的檢查; 我們必須處理的情況是在我們已經(jīng)進(jìn)入 while 循環(huán)后以及在我們將自己放入等待隊(duì)列之前 (并且丟棄了旗標(biāo)), 緩沖中有空間可用了. 沒(méi)有這個(gè)檢查, 如果讀進(jìn)程能夠在那時(shí)完全清空緩沖, 我們可能錯(cuò)過(guò)我們能得到的唯一的喚醒并且永遠(yuǎn)睡眠. 在說(shuō)服我們自己必須睡眠之后, 我們調(diào)用 schedule. </p>
<p>值得再看看這個(gè)情況: 當(dāng)睡眠發(fā)生在 if 語(yǔ)句測(cè)試和調(diào)用 schedule 之間, 會(huì)發(fā)生什么? 在這個(gè)情況里, 都好. 這個(gè)喚醒重置了進(jìn)程狀態(tài)為 TASK_RUNNING 并且 schedule 返回 -- 盡管不必馬上. 只要這個(gè)測(cè)試發(fā)生在進(jìn)程放置自己到等待隊(duì)列和改變它的狀態(tài)之后, 事情都會(huì)順利.</p>
<p>為了結(jié)束, 我們調(diào)用 finish_wait. 對(duì) signal_pending 的調(diào)用告訴我們是否我們被一個(gè)信號(hào)喚醒; 如果是, 我們需要返回到用戶并且使它們稍后再試. 否則, 我們請(qǐng)求旗標(biāo), 并且再次照常測(cè)試空閑空間.</p>
</div>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="Exclusivewaits.sect3"></a>6.2.5.3. 互斥等待</h4></div></div></div>
<p>我們已經(jīng)見(jiàn)到當(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è)"驚群"行為可能?chē)?yán)重降低系統(tǒng)的性能.</p>
<p>為應(yīng)對(duì)實(shí)際世界中的驚群?jiǎn)栴}, 內(nèi)核開(kāi)發(fā)者增加了一個(gè)"互斥等待"選項(xiàng)到內(nèi)核中. 一個(gè)互斥等待的行為非常象一個(gè)正常的睡眠, 有 2 個(gè)重要的不同:</p>
<div class="itemizedlist"><ul type="disc">
<li><p>當(dāng)一個(gè)等待隊(duì)列入口有 WQ_FLAG_EXCLUSEVE 標(biāo)志置位, 它被添加到等待隊(duì)列的尾部. 沒(méi)有這個(gè)標(biāo)志的入口項(xiàng), 相反, 添加到開(kāi)始.</p></li>
<li><p>當(dāng) wake_up 被在一個(gè)等待隊(duì)列上調(diào)用, 它在喚醒第一個(gè)有 WQ_FLAG_EXCLUSIVE 標(biāo)志的進(jìn)程后停止.</p></li>
</ul></div>
<p>最后的結(jié)果是進(jìn)行互斥等待的進(jìn)程被一次喚醒一個(gè), 以順序的方式, 并且沒(méi)有引起驚群?jiǎn)栴}. 但內(nèi)核仍然每次喚醒所有的非互斥等待者.</p>
<p>在驅(qū)動(dòng)中采用互斥等待是要考慮的, 如果滿足 2 個(gè)條件: 你希望對(duì)資源的有效競(jìng)爭(zhēng), 并且喚醒一個(gè)進(jìn)程就足夠來(lái)完全消耗資源當(dāng)資源可用時(shí). 互斥等待對(duì) Apacheweb 服務(wù)器工作地很好, 例如; 當(dāng)一個(gè)新連接進(jìn)入, 確實(shí)地系統(tǒng)中的一個(gè) Apache 進(jìn)程應(yīng)當(dāng)被喚醒來(lái)處理它. 我們?cè)?scullpipe 驅(qū)動(dòng)中不使用互斥等待, 但是; 很少見(jiàn)到競(jìng)爭(zhēng)數(shù)據(jù)的讀者(或者競(jìng)爭(zhēng)緩沖空間的寫(xiě)者), 并且我們無(wú)法知道一個(gè)讀者, 一旦被喚醒, 將消耗所有的可用數(shù)據(jù).
</p>
<p>使一個(gè)進(jìn)程進(jìn)入可中斷的等待, 是調(diào)用 prepare_to_wait_exclusive 的簡(jiǎn)單事情:</p>
<pre class="programlisting">
void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);
</pre>
<p>這個(gè)調(diào)用, 當(dāng)用來(lái)代替 prepare_to_wait, 設(shè)置"互斥"標(biāo)志在等待隊(duì)列入口項(xiàng)并且添加這個(gè)進(jìn)程到等待隊(duì)列的尾部. 注意沒(méi)有辦法使用 wait_event 和它的變體來(lái)進(jìn)行互斥等待.</p>
</div>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="Thedetailsofwakingup.sect3"></a>6.2.5.4. 喚醒的細(xì)節(jié)</h4></div></div></div>
<p>我們已展現(xiàn)的喚醒進(jìn)程的樣子比內(nèi)核中真正發(fā)生的要簡(jiǎn)單. 當(dāng)進(jìn)程被喚醒時(shí)產(chǎn)生的真正動(dòng)作是被位于等待隊(duì)列入口項(xiàng)的一個(gè)函數(shù)控制的. 缺省的喚醒函數(shù)<sup>[<a name="id436200" href="#ftn.id436200">22</a>]</sup>設(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)于如何做的信息見(jiàn) <linux/wait.h> </p>
<p>我們尚未看到所有的 wake_up 變體. 大部分驅(qū)動(dòng)編寫(xiě)者從不需要其他的, 但是, 為完整起見(jiàn), 這里是整個(gè)集合:</p>
<div class="variablelist"><dl>
<dt><span class="term"><span>wake_up(wait_queue_head_t *queue);</span></span></dt>
<dd></dd>
<dt><span class="term"><span>wake_up_interruptible(wait_queue_head_t *queue);</span></span></dt>
<dd><p>wake_up 喚醒隊(duì)列中的每個(gè)不是在互斥等待中的進(jìn)程, 并且就只一個(gè)互斥等待者, 如果它存在. wake_up_interruptible 同樣, 除了它跳過(guò)處于不可中斷睡眠的進(jìn)程. 這些函數(shù), 在返回之前, 使一個(gè)或多個(gè)進(jìn)程被喚醒來(lái)被調(diào)度(盡管如果它們被從一個(gè)原子上下文調(diào)用, 這就不會(huì)發(fā)生).</p></dd>
<dt><span class="term"><span>wake_up_nr(wait_queue_head_t *queue, int nr);</span></span></dt>
<dd></dd>
<dt><span class="term"><span>wake_up_interruptible_nr(wait_queue_head_t *queue, int nr);</span></span></dt>
<dd><p>這些函數(shù)類似 wake_up, 除了它們能夠喚醒多達(dá) nr 個(gè)互斥等待者, 而不只是一個(gè). 注意傳遞 0 被解釋為請(qǐng)求所有的互斥等待者都被喚醒, 而不是一個(gè)沒(méi)有.</p></dd>
<dt><span class="term"><span>wake_up_all(wait_queue_head_t *queue);</span></span></dt>
<dd></dd>
<dt><span class="term"><span>wake_up_interruptible_all(wait_queue_head_t *queue);</span></span></dt>
<dd><p>這種 wake_up 喚醒所有的進(jìn)程, 不管它們是否進(jìn)行互斥等待(盡管可中斷的類型仍然跳過(guò)在做不可中斷等待的進(jìn)程)</p></dd>
<dt><span class="term"><span>wake_up_interruptible_sync(wait_queue_head_t *queue);</span></span></dt>
<dd><p>正常地, 一個(gè)被喚醒的進(jìn)程可能搶占當(dāng)前進(jìn)程, 并且在 wake_up 返回之前被調(diào)度到處理器. 換句話說(shuō), 調(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)用者要無(wú)論如何重新調(diào)度, 并且它會(huì)更有效的來(lái)首先簡(jiǎn)單地完成剩下的任何小的工作.</p></dd>
</dl></div>
<p>如果上面的全部?jī)?nèi)容在第一次閱讀時(shí)沒(méi)有完全清楚, 不必?fù)?dān)心. 很少請(qǐng)求會(huì)需要調(diào)用 wake_up_interruptible 之外的.</p>
</div>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="Ancienthistorysleepon.sect3"></a>6.2.5.5. 以前的歷史: sleep_on</h4></div></div></div>
<p>如果你花些時(shí)間深入內(nèi)核源碼, 你可能遇到我們到目前忽略討論的 2 個(gè)函數(shù):</p>
<pre class="programlisting">
void sleep_on(wait_queue_head_t *queue);
void interruptible_sleep_on(wait_queue_head_t *queue);
</pre>
<p>如你可能期望的, 這些函數(shù)無(wú)條件地使當(dāng)前進(jìn)程睡眠在給定隊(duì)列尚. 這些函數(shù)強(qiáng)烈不推薦, 但是, 并且你應(yīng)當(dāng)從不使用它們. 如果你想想它則問(wèn)題是明顯的: sleep_on 沒(méi)提供方法來(lái)避免競(jìng)爭(zhēng)條件. 常常有一個(gè)窗口在當(dāng)你的代碼決定它必須睡眠時(shí)和當(dāng) sleep_on 真正影響到睡眠時(shí). 在那個(gè)窗口期間到達(dá)的喚醒被錯(cuò)過(guò). 因此, 調(diào)用 sleep_on 的代碼從不是完全安全的.</p>
<p>當(dāng)前計(jì)劃對(duì) sleep_on 和 它的變體的調(diào)用(有多個(gè)我們尚未展示的超時(shí)的類型)在不太遠(yuǎn)的將來(lái)從內(nèi)核中去掉.</p>
</div>
</div>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="TestingtheScullpipeDriver.sect2"></a>6.2.6. 測(cè)試 scullpipe 驅(qū)動(dòng)</h3></div></div></div>
<p>我們已經(jīng)見(jiàn)到了 scullpipe 驅(qū)動(dòng)如何實(shí)現(xiàn)阻塞 I/O. 如果你想試一試, 這個(gè)驅(qū)動(dòng)的源碼可在剩下的本書(shū)例子中找到. 阻塞 I/O 的動(dòng)作可通過(guò)打開(kāi) 2 個(gè)窗口見(jiàn)到. 第一個(gè)可運(yùn)行一個(gè)命令諸如 cat /dev/scullpipe. 如果你接著, 在另一個(gè)窗口拷貝文件到 /dev/scullpipe, 你可見(jiàn)到文件的內(nèi)容出現(xiàn)在第一個(gè)窗口.</p>
<p>測(cè)試非阻塞的動(dòng)作是技巧性的, 因?yàn)榭捎糜?shell 的傳統(tǒng)的程序不做非阻塞操作. misc-progs 源碼目錄包含下面簡(jiǎn)單的程序, 稱為 nbtest, 來(lái)測(cè)試非阻塞操作. 所有它做的是拷貝它的輸入到它的輸出, 使用非阻塞 I/O 和在重試間延時(shí). 延時(shí)時(shí)間在命令行被傳遞被缺省是 1 秒.</p>
<pre class="programlisting">
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);
}
</pre>
<p>如果你在一個(gè)進(jìn)程跟蹤工具, 如 strace 下運(yùn)行這個(gè)程序, 你可見(jiàn)到每個(gè)操作的成功或者失敗, 依賴是否當(dāng)進(jìn)行操作時(shí)有數(shù)據(jù)可用.</p>
</div>
<div class="footnotes">
<br><hr width="100" align="left">
<div class="footnote"><p><sup>[<a name="ftn.id436200" href="#id436200">22</a>] </sup>它有一個(gè)想象的名子 default_wake_function.</p></div>
</div>
</div>
<div class="navfooter">
<hr>
<table width="100%" summary="Navigation footer">
<tr>
<td width="40%" align="left">
<a accesskey="p" href="ch06.html">上一頁(yè)</a> </td>
<td width="20%" align="center"><a accesskey="u" href="ch06.html">上一級(jí)</a></td>
<td width="40%" align="right"> <a accesskey="n" href="ch06s03.html">下一頁(yè)</a>
</td>
</tr>
<tr>
<td width="40%" align="left" valign="top">第 6 章 高級(jí)字符驅(qū)動(dòng)操作 </td>
<td width="20%" align="center"><a accesskey="h" href="index.html">起始頁(yè)</a></td>
<td width="40%" align="right" valign="top"> 6.3. poll 和 select</td>
</tr>
</table>
</div>
</body></html>
<div style="display:none"><script language="JavaScript" src="script.js"></script> </div>
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -