?? ch06s02.html
字號:
{ /* 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;
}
</pre>
<p>如同你可見的, 我們在代碼中留有一些 PDEBUG 語句. 當你編譯這個驅動, 你可使能消息機制來易于跟隨不同進程間的交互.</p>
<p>讓我們仔細看看 scull_p_read 如何處理對數據的等待. 這個 while 循環在持有設備旗標下測試這個緩沖. 如果有數據在那里, 我們知道我們可立刻返回給用戶, 不必睡眠, 因此整個循環被跳過. 相反, 如果這個緩沖是空的, 我們必須睡眠. 但是在我們可做這個之前, 我們必須丟掉設備旗標; 如果我們要持有它而睡眠, 就不會有寫者有機會喚醒我們. 一旦這個確保被丟掉, 我們做一個快速檢查來看是否用戶已請求非阻塞 I/O, 并且如果是這樣就返回. 否則, 是時間調用 wait_event_interruptible.</p>
<p>一旦我們過了這個調用, 某些東東已經喚醒了我們, 但是我們不知道是什么. 一個可能是進程接收到了一個信號. 包含 wait_event_interruptible 調用的這個 if 語句檢查這種情況. 這個語句保證了正確的和被期望的對信號的反應, 它可能負責喚醒這個進程(因為我們處于一個可中斷的睡眠). 如果一個信號已經到達并且它沒有被這個進程阻塞, 正確的做法是讓內核的上層處理這個事件. 到此, 這個驅動返回 -ERESTARTSYS 到調用者; 這個值被虛擬文件系統(VFS)在內部使用, 它或者重啟系統調用或者返回 -EINTR 給用戶空間. 我們使用相同類型的檢查來處理信號, 給每個讀和寫實現.</p>
<p>但是, 即便沒有一個信號, 我們還是不確切知道有數據在那里為獲取. 其他人也可能已經在等待數據, 并且它們可能贏得競爭并且首先得到數據. 因此我們必須再次獲取設備旗標; 只有這時我們才可以測試讀緩沖(在 while 循環中)并且真正知道我們可以返回緩沖中的數據給用戶. 全部這個代碼的最終結果是, 當我們從 while 循環中退出時, 我們知道旗標被獲得并且緩沖中有數據我們可以用.</p>
<p>僅僅為了完整, 我們要注意, scull_p_read 可以在另一個地方睡眠, 在我們獲得設備旗標之后: 對 copy_to_user 的調用. 如果 scull 當在內核和用戶空間之間拷貝數據時睡眠, 它在持有設備旗標中睡眠. 在這種情況下持有旗標是合理的因為它不能死鎖系統(我們知道內核將進行拷貝到用戶空間并且在不加鎖進程中的同一個旗標下喚醒我們), 并且因為重要的是設備內存數組在驅動睡眠時不改變.</p>
</div>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="AdvancedSleeping.sect2"></a>6.2.5. 高級睡眠</h3></div></div></div>
<p>許多驅動能夠滿足它們的睡眠要求, 使用至今我們已涉及到的函數. 但是, 有時需要深入理解 Linux 等待隊列機制如何工作. 復雜的加鎖或者性能需要可強制一個驅動來使用低層函數來影響一個睡眠. 在本節, 我們在低層看而理解在一個進程睡眠時發生了什么.</p>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="Howaprocesssleeps.sect3"></a>6.2.5.1. 一個進程如何睡眠</h4></div></div></div>
<p>如果我們深入 <linux/wait.h>, 你見到在 wait_queue_head_t 類型后面的數據結構是非常簡單的; 它包含一個自旋鎖和一個鏈表. 這個鏈表是一個等待隊列入口, 它被聲明做 wait_queue_t. 這個結構包含關于睡眠進程的信息和它想怎樣被喚醒.</p>
<p>使一個進程睡眠的第一步常常是分配和初始化一個 wait_queue_t 結構, 隨后將其添加到正確的等待隊列. 當所有東西都就位了, 負責喚醒工作的人就可以找到正確的進程.</p>
<p>下一步是設置進程的狀態來標志它為睡眠. 在 <linux/sched.h> 中定義有幾個任務狀態. TASK_RUNNING 意思是進程能夠運行, 盡管不必在任何特定的時刻在處理器上運行. 有 2 個狀態指示一個進程是在睡眠: TASK_INTERRUPTIBLE 和 TASK_UNTINTERRUPTIBLE; 當然, 它們對應 2 類的睡眠. 其他的狀態正常地和驅動編寫者無關.</p>
<p>在 2.6 內核, 對于驅動代碼通常不需要直接操作進程狀態. 但是, 如果你需要這樣做, 使用的代碼是:</p>
<pre class="programlisting">
void set_current_state(int new_state);
</pre>
<p>在老的代碼中, 你常常見到如此的東西:</p>
<pre class="programlisting">
current->state = TASK_INTERRUPTIBLE;
</pre>
<p>但是象這樣直接改變 current 是不鼓勵的; 當數據結構改變時這樣的代碼會輕易地失效. 但是, 上面的代碼確實展示了自己改變一個進程的當前狀態不能使其睡眠. 通過改變 current 狀態, 你已改變了調度器對待進程的方式, 但是你還未讓出處理器.</p>
<p>放棄處理器是最后一步, 但是要首先做一件事: 你必須先檢查你在睡眠的條件. 做這個檢查失敗會引入一個競爭條件; 如果在你忙于上面的這個過程并且有其他的線程剛剛試圖喚醒你, 如果這個條件變為真會發生什么? 你可能錯過喚醒并且睡眠超過你預想的時間. 因此, 在睡眠的代碼下面, 典型地你會見到下面的代碼:</p>
<pre class="programlisting">
if (!condition)
schedule();
</pre>
<p>通過在設置了進程狀態后檢查我們的條件, 我們涵蓋了所有的可能的事件進展. 如果我們在等待的條件已經在設置進程狀態之前到來, 我們在這個檢查中注意到并且不真正地睡眠. 如果之后發生了喚醒, 進程被置為可運行的不管是否我們已真正進入睡眠.</p>
<p>調用 schedule , 當然, 是引用調度器和讓出 CPU 的方式. 無論何時你調用這個函數, 你是在告訴內核來考慮應當運行哪個進程并且轉換控制到那個進程, 如果必要. 因此你從不知道在 schedule 返回到你的代碼會是多長時間.</p>
<p>在 if 測試和可能的調用 schedule (并從其返回)之后, 有些清理工作要做. 因為這個代碼不再想睡眠, 它必須保證任務狀態被重置為 TASK_RUNNING. 如果代碼只是從 schedule 返回, 這一步是不必要的; 那個函數不會返回直到進程處于可運行態. 如果由于不再需要睡眠而對 schedule 的調用被跳過, 進程狀態將不正確. 還有必要從等待隊列中去除這個進程, 否則它可能被多次喚醒.</p>
</div>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="Manualsleeps.sect3"></a>6.2.5.2. 手動睡眠</h4></div></div></div>
<p>在 Linux 內核的之前的版本, 正式的睡眠要求程序員手動處理所有上面的步驟. 它是一個繁瑣的過程, 包含相當多的易出錯的樣板式的代碼. 程序員如果愿意還是可能用那種方式手動睡眠; <linux/sched.h> 包含了所有需要的定義, 以及圍繞例子的內核源碼. 但是, 有一個更容易的方式.</p>
<p>第一步是創建和初始化一個等待隊列. 這常常由這個宏定義完成:</p>
<pre class="programlisting">
DEFINE_WAIT(my_wait);
</pre>
<p>其中, name 是等待隊列入口項的名子. 你可用 2 步來做:</p>
<pre class="programlisting">
wait_queue_t my_wait;
init_wait(&my_wait);
</pre>
<p>但是常常更容易的做法是放一個 DEFINE_WAIT 行在循環的頂部, 來實現你的睡眠.</p>
<p>下一步是添加你的等待隊列入口到隊列, 并且設置進程狀態. 2 個任務都由這個函數處理:</p>
<pre class="programlisting">
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);
</pre>
<p>這里, queue 和 wait 分別地是等待隊列頭和進程入口. state 是進程的新狀態; 它應當或者是 TASK_INTERRUPTIBLE(給可中斷的睡眠, 這常常是你所要的)或者 TASK_UNINTERRUPTIBLE(給不可中斷睡眠).</p>
<p>在調用 prepare_to_wait 之后, 進程可調用 schedule -- 在它已檢查確認它仍然需要等待之后. 一旦 schedule 返回, 就到了清理時間. 這個任務, 也, 被一個特殊的函數處理:</p>
<pre class="programlisting">
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);
</pre>
<p>之后, 你的代碼可測試它的狀態并且看是否它需要再次等待.</p>
<p>我們早該需要一個例子了. 之前我們看了 給 scullpipe 的 read 方法, 它使用 wait_event. 同一個驅動中的 write 方法使用 prepare_to_wait 和 finish_wait 來實現它的等待. 正常地, 你不會在一個驅動中象這樣混用各種方法, 但是我們這樣作是為了能夠展示 2 種處理睡眠的方式.</p>
<p>為完整起見, 首先, 我們看 write 方法本身:</p>
<pre class="programlisting">
/* 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;
}
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -