• 沒有找到結果。

第四章 驅動程式層網路事件通知機制之實作

4.4 事件處理之時機

4.4.1 檢查事件的發生

為了要能使使用者空間行程能夠快速的得知事件的發生,並且對其採取對應動作,

核心必須盡快的對已發生的事件反應,並且將中央處理器的控制權交回使用者空間的程 式,在我們的處理中,將在每次由中斷處理或例外處理返回以及新生行程返回且執行該 行程時,核心將會檢查欲返回的行程是否有未處理的事件,若有,則對該事件作處理,

將至中央處理器的控制權交回至使用者空間,並且執行對應回叫函數。

關於檢查事件的程式碼,可以參照 Fig.4-6,該段檢查的程式碼會被插入在核心返回

使用者空間程式碼中中斷處理和例外處理之後,以及系統呼叫執行之後。

Fig.4- 6 偵測是否有未處理事件的程式碼

圖 Fig.4-7 是未修改前由 Linux 2.6.10 核心返回的使用者空間的處理流程,當要由核 心返回使用者空間有三個狀況,由例外中返回,由中斷返回,以及由系統呼叫返回,在 圖中代表這三個情況發生的區塊分別為 ret_from_exception、ret_from_intr 以及

syscall_exit,當由 ret_from_exception 以及 ret_from_intr 返回時,若是要回到使用者空間 行程執行,則要先經過 work pending 判斷方塊的檢查,若為真表示有信號尚未處理,此 時,若不到重新排程時,便會呼叫 do_signal()函式處理尚未處理的信號。

Fig.4- 7 未修改前由核心返回的流程圖

圖 Fig.4-8 為修改過後由核心返回的流程圖,我們加入的處理為橘色的部分,主要 分為兩個部分,第一個部分是在 ret_from_intr 以及 ret_from_exception 中返回且進入 resume_userspace 的處理後,我們會在判斷是否有信號尚未處理之前 (work pending 之判 斷) 先判斷,是否有未處理的網路驅動程式事件,若有,則呼叫 handle_event_sig()函式,

該函式會執行使用者空間和核心空間之切換的相關設定,接下來跳至核心的 retore_all 函數處理,該函數會將硬體環境還原,並且跳至使用者空間執行使用者空間的程式碼;

另外一個檢查點則是在由系統呼叫返回且檢查是否有信號未處理或是系統呼叫被追蹤 前,發現有未處理的事件時也一樣會呼叫 handle_event_sig()函數,然後跳至 restore_all 內回復硬體環境。

由於每次由系統呼叫,中斷,或者是例外中返回時都會檢查是否有感興趣的事件發 生,因此,使用這個方法可以保證「在該行程握有中央處理器的控制權時,至少在兩次 記時器中斷 (timer interrupt) 其間的事件,可被傳達至使用者空間的行程」。

Fig.4- 8 修改過後的核心返回流程圖 4.4.2 排程器的修改

章節 4.4.1 中所提及的處理方法只有在該使用者取得中央處理器的佔有權時,才會 執行相關的檢查,若當時有某個行程感到興趣的事件發生,可是此時中央處理器並不由 該行程佔有時,則事件則無法傳達至該行程。

為了要能夠及時的讓使用者空間的行程得知其感興趣的事件已經發生,由於當事件 發生的時候中央處理器的控制權可能不已註冊監聽事件上,因此我們希望控制權能盡快 轉移至有註冊事件發生的行程上,因此我們對\核心的排程器做了一些修改。

圖 Fig.4-9 為修改過後的核心排程演算法,其中黃色和加粗的線條部分是我們加入 的判斷以及處理,該處理主要為以下兩個部分:

z 當目前行程為 TASK_INTERRUPTIBLE 狀態時,若發現有該行程感興趣的事件 已發生,則將其狀態設定為 TASK_RUNNING。

z 在依正常程序選擇下一個執行的行程前,若已註冊的行程有感興趣的事件發 生,則優先執行已註冊過的行程執行。

對於選擇註冊過的行程方面,由於所有註冊過的行程都會由一個靜態宣告的串列記

錄下來,因此我們檢查該串列中是否有發生興趣的行程,若是,則優先執行該行程,且 在檢查的同時,若是該事件只有一個未處理的事件,則將其移至串列的後端。

Fig.4- 9 修改過後的排程演算法

4.5 系統呼叫的重新執行

章節 4.4.2 中我們曾經提及,當發現當目前行程為 TASK_INTERRUPTIBLE 狀態時,

若發現有該行程感興趣的事件已發生,則將其狀態設定為 TASK_RUNNING,如此一來,

若是目前行程尚有多餘的時間切片的話,便可以繼續執行,以便對該發生的事件進行處 理。

初看之下,以上這段處理似乎十分自然,但是,我們知道,行程會由 TASK_RUNNING 狀態轉移到 TASK_INTERRUPITBLE 狀態,必然是因為該行程的要求無法即時的被完 成,而使用者空間的行程要向核心做出要求的話,必須通過系統呼叫的方式,因此,當 我們將行程的 TASK_INTERRUPTIBLE 狀態改變為 TASK_RUNNING 時,其實之前行 程向核心,也就是作業系統,經由系統呼叫所做的要求並未被完成 (例如,寫入檔案,

要求更多的記憶體...),而行程的狀態卻被換成 TASK_RUNNING 了。

現在,讓我們來看一下如果我們只將現在行程的狀態由 TASK_INTERRUPTIBLE 換成 TASK_RUNNING 的話會發生什麼樣的狀況?

當使用者空間的行程執行了一個系統呼叫後,IP 暫存器將會指向該系統呼叫的下 一個指令 (此在核心空間時將被儲存在核心堆疊內),接下來,系統便進入核心空間執行 以便完成系統的要求,當使用者的要求無法被馬上滿足時,核心程式會執行如以下的程 式碼,將該行程加入一個等待佇列當中,並且呼叫排程器排程:

Fig.4- 10 將行程加入某個等待佇列的程式碼片段

當我們將行程的狀態由 TASK_INTERRUPTIBLE 換成 TASK_RUNNING 時,該程

式碼會由 schedule()的下一行開始執行,此時,程式進入 while 迴圈的判斷,若是等待的 狀況(在圖 Fig.4-10 中以 condition 變數作為代表)沒有成立,換句話說,也就是行程的要 求無法被立即滿足時,程式將會重新進入 while 迴圈當中執行,又將行程的狀態設定為 TASK_INTERRUPTIBLE,然後又呼叫 schedule(),形成類似無窮迴圈。

瞭解了只改動行程的狀態會發生什麼事情後,我們來說明要如何避免前段所述的情 況發生;根據 Linux 系統在傳統訊號的處理上,當系統呼叫無法完成,且有訊號傳達時,

圖 Fig.4-10 中的 signal_pending()的回傳值為一個非零值,此時,系統呼叫必須回傳 EINTR、ERESTARTNOHAND、ERESTARTSYS 或 ERESTARTINTR。

在傳統的信號機制當中,根據行程各種信號的不同動作(忽略、捕捉或預設動作),

在信號處理的核心函數內系統會做出對應的不同處理,表 Table.4-2 表示了行程對信號的 動作和系統呼叫回傳值的對應,以及核心信號處理函數會採取的動作[10]。

行程對信

號動作 系統呼叫回傳值

EINTR ERESTARTSYS ERESTARTNOHAND ERESTARTNOINTR

預設 終止 重新執行 重新執行 重新執行

忽略 終止 重新執行 重新執行 重新執行

捕捉 終止 不一定 終止 重新執行

Table.4- 2 行程對信號動作和系統呼叫回傳值對系統呼叫執行影響之列表

其中,表 Table.4-2 中所指的「不一定」表示要根據 SA_RESTART 這個旗標的設定 與否來判斷(使用者空間行程可以利用 sigaction 系統呼叫設定此旗標),若該旗標被設 定,系統呼叫將會被重新執行。

在本系統當中,根據系統呼叫的回傳值的處理大致上仍然依照表 Table.4-2 所示的規 則來進行,此外,當系統發現目前行程有設備信號尚未被處理時,。

我們根據行程是否註冊處理函數來討論,如果使用者空間的行程沒有註冊處理函 數,則當系統呼叫回傳 EINTR 時會終止,而系統呼叫回傳 ERESTARTSYS、

ERESTARTNOHAND、ERESTARTNOINTR 時會重複執行系統呼叫;當使用者空間行程 註冊了處理函數時,當系統呼叫回傳 EINTR 以及 ERESTARTNOHAND 時,系統呼叫將 被終止,而當系統呼叫回傳 ERESTARTSYS 以及 ERESTARTNOINTR 則會重新執行中

斷的系統呼叫。

首先,我們將 signal_pending()這個函數的判斷加入判斷設備信號是否尚未處理的判 斷,如下所示:

Fig.4- 11 signal_pending()函數之修改

在設備信號的處理函數 handle_iw_event_sig()當中,我們必須判斷是否欲處理的信 號,如果有未處理的信號,而且又有註冊處理函數時,則以如下的程式碼來處理:

Fig.4- 12 重新執行系統呼叫。

如果行程並未對發生的設備事件註冊處理函數時,則會在設備信號的處理函數 handle_iw_event_sig()中執行以下的程式碼,除了回傳值為 EINTR 之外的情況都需要重 複執行系統呼叫:

Fig.4- 13 沒有註冊處理函數時的系統呼叫重新執行 4.6 使用者和核心的切換

根據章節 4.4.1 中所介紹的系統返回程式碼,當核心發現欲返回的使用者空間行程 有感興趣的事件發生時,會呼叫 handle_iw_event_sig()函式以處理,在這個函式中將會 做所有切換到使用者空間行程的設定及動作。

本系統主要實作的平台為英特爾 32 位元平台 (Intel Architecture 32-bits,IA32) ,在 使用者和核心空間的切換方面,我們採用 Linux 對於傳統信號處理中執行信號處理函式 之方法,該方法可分為兩大步驟:

1. 將目前儲存在核心模式堆疊的使用者空間硬體環境儲存到使用者模式堆疊。

2. 置換目前核心模式堆疊中的使用者空間硬體環境。

圖 Fig.4-14 為一般狀況下行程由核心空間返回使用者空間前核心模式堆疊和使用者 模式堆疊的狀態,當行程由使用者空間跳至核心空間時,核心會先將使用者空間的硬體 環境儲存在核心堆疊中,其中的 IP 暫存器值存著下一個要執行指令的記憶體位址,SP 暫存器儲存著堆疊端的位置。

Fig.4- 14 正常狀態下的核心模式堆疊和使用者模式堆疊

我們的作法是先將一段系統之後欲執行的組語碼寫在使用者模式堆疊的頂端,再將 原本儲存於核心模式堆疊的硬體環境 (hardware context) 儲存在使用者模式堆疊中,最 後將回存位址 (return address) 寫入使用者模式堆疊的頂端,並且使用核心堆疊內的行 程所註冊的信息處理函數的硬體環境 (包含 IP 暫存器) 取代原來核心模式的硬體環境。

Fig.4- 15 執行回叫函式前的核心模式堆疊和使用者模式堆疊

Fig.4-15 為執行回叫函數前的核心模式堆疊和使用者模式堆疊的狀態,此時原本的

Fig.4-15 為執行回叫函數前的核心模式堆疊和使用者模式堆疊的狀態,此時原本的

相關文件