第四章 驅動程式層網路事件通知機制之實作
4.2 事件及其相關處理的實作
4.2.3 事件的格式化以及記錄
Fig.4- 2 事件區塊的記錄方式
圖 Fig.4-3 為事件處理者的處理流程,其流程的核心部分為一個 for 迴圈,迴圈中每 次選取一個註冊過的行程,檢查其是否對目前發生的事件感興趣,若感興趣,則將事件 格式化,並且插入行程敘述子的事件串列中,若非,則選取下一個註冊過的行程,直到 所有行程檢查完畢。
Fig.4- 3 事件處理者之流程 4.3 行程管理實作
為了要記錄和管理註冊過行程以及發生過的事件,我們必須對原本的行程資料結構 作一些改變,在核心之中,每一個使用者空間行程都由一個行程描述子代表,行程描述 子記錄了行程的所有資訊,例如記憶體空間,開過的檔案資訊等等…,在本系統中,為 了要支援程式能夠註冊其感興趣的事件,因此我們必須對行程敘述子增加一些欄位。
Fig.4- 4 對行程敘述子的修改
圖 Fig.4-4 是我們針對本系統增加於行程敘述子的資料結構,其個別的意義如下:
z struct task_struct 內新增的欄位:
devname_buf:使用者空間的字串指標,用來儲存發生事件的設備名稱。
event_blocked:一個位元遮罩,表示現在不想收到的信號。
iw_handler[]:一組使用者空間的回叫函數指標
iw_chain:用來記錄目前註冊過的行程的串鍊
vt_blk_chain:發生過的感興趣事件。
z struct thread_info 內新增的欄位:
iw_flag:表示目前的行程是否尚有未處理的事件,當事件串列清空時,將 會清除這個旗標。
當使用者空間行程呼叫系統呼叫註冊其對某些事件感興趣時,若是該行程之前沒有 註冊過時,系統便會將其加入一個在系統內靜態宣告 (static declare) 的串列中,之後每 次有事件發生便會去該串鍊檢查是否有行程對事件感興趣,若有,則將事件區塊加入該 事件的事件區塊串鍊中。
以圖 Fig.4-5 為例,當要啟用某個網路介面的時候,使用者會通過 ioctl 系統呼叫控 制下層的網路介面,在本例中使用者下達了 ioctl 命令設定網路介面的 IFF_UP 旗標,該
命令在核心內最後會由通用驅動程式層來處理,通用驅動程式層會先去呼叫驅動程式的 處理函式 (在本例中為 dev->open()) ,當處理函式執行完了之後,通用驅動程式層便會 呼叫 call_netdevice_notifiers()函數,該函數會去對 netdev 通知者串列註冊的回叫函數一 一呼叫,當呼叫到我們註冊的事件處理通知者區塊時,便會呼叫事件處理者函式,該函 式會將事件實體化,並且將其加入對該事件感到興趣的行程敘述子之發生事件串鍊中,
等待之後核心的處理。
Fig.4- 5 事件的發生及儲存之實例 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 所示的規 則來進行,此外,當系統發現目前行程有設備信號尚未被處理時,。
我們根據行程是否註冊處理函數來討論,如果使用者空間的行程沒有註冊處理函
我們根據行程是否註冊處理函數來討論,如果使用者空間的行程沒有註冊處理函