3. 理論與實作背景
3.3. eCos
3.3.5. The Kernel
Kernel 是 eCos 系統的中樞。Kernel 提供即時作業中的標準功能例如插斷和 例外處理、排程、執行緒和同步。在 eCos 系統下可以對這些組成 kernel 的標準 功能元件完全地設定,以達到特殊的需要。eCos kernel 以 C++實作,允許用 C++
寫成的應用程式直接透過 C kernel API 介面和 kernel 溝通。eCos kernel 也支援標 準 u-ITRON 和 POSIX compatibility layers 介面。為了符合即時性需求,eCos kernel 依循下列準則發展:
z Interrupt latency — interrupt 回 應 和 開 始 執 行 ISR 的 時 間 要 少 而 且 deterministic。
z Dispatch latency—執行緒準備完成可以執行的狀態到開始執行的時間 要少並且 deterministic。
z Memory footprint—對一個設定完成的系統,程式或資料所需求的記憶 體資源要保持最小化和 deterministic。而且要確保嵌入式系統的動態記 憶體配置不會使用超出所有記憶體的量。
z Deterministic kernel primitives—所有 kernel 的行為都要是可預期的且符 合即時性的需求。
eCos kernel API 不回傳錯誤訊息。在嵌入式系統當中,處理錯誤回傳訊息會 導致許多問題,如消耗貴重的執行週期和程式空間來檢查回傳的訊息。為了程式 發展的便利性 eCos kernel 提供 assertion,它可以被 eCos package 開啟或關閉。傳 統上,assertion 在除錯階段會開啟,允許 kernel 程式顯示錯誤檢查。如果錯誤產 生了,就會回傳一個 assertion 失敗並且會中止應用程式。除錯程序完畢之後,
assertion 就在 kernel package 之中關閉。此方法有很多好處,如限制程式中錯誤 檢查的 overhead,消除應用程式錯誤檢查的需要;如果有一個錯誤發生,應用就 被暫停,可以立即知道發生錯誤的地點,而不是依賴回傳訊息再去檢查。
kernel 提供開發多執行緒應用所需的重要功能。
在硬體都啟動完成之後,HAL 中呼叫 Kernel 啟動的程序。cyg_start 是 kernel 啟重程序的啟始點。它呼叫其他預設的啟動程式來處理不同初始化的任務。只要 在應用程式之中提供相同程式名稱,預設 kernel 啟動程式可以被簡單的替換,以 完成使用者特殊的初始化工作。Kernel 啟動程序如圖 5 [18]。
Figure 5. Kernel 啟動程序
Core kernel 啟動程式, cyg_star。Cyg_start 之後下一個被呼叫的程式是 cyg_prestart。它預設不完成任何初始任務,讓使用者自行決定在其他系統初始化 之前該被完成的初始化都可以在這裡執行。接著呼叫 cyg_package_start,在應用 程式開始之前初始化將用到的 package,如 u-ITRON 和 ISO C 程式庫。之後呼叫 cyg_user_start,它是應用程式的進入點。建議 cyg_user_start 被使用來完成任何 應用指定的初始化、產生執行緒、產生 synchronization primitives、設定鬧鐘、和 註冊任何需要的 interrupt handlers。當 cyg_user_start return 時會自動執行呼叫排 程器。
3.3.5.1. The Scheduler
eCos kernel 的核心是排程器。排程器的工作是去選擇最適合的執行緒執行,
提供執行中執行緒同步的機制和控制 interrupt 在執行緒執行的影響。在排程器程
式程式碼執行期間,不會關閉 interrupt,因為 interrupt latency 十分短。排程器內 存在一個計數器,它決定排程器是自由地執行或是關閉。如果計數器非零,排程 器就被關閉,當計數器回到零,排程就會啟動。在 ISR 執行期間,HAL 預設 interrupt handler 會修改計數器來停止排程動作。執行緒也有能力開關排程器。
3.3.5.2. Multilevel Queue Scheduler
MLQ 排程器允許同一個 priority 有多個執行緒可執行。priority 可由 1 設定 到 32,對應到數字是 0(最高)到 31(最低)。MLQ 排程器允許不同 priority 可有 preemption。Preemption 是指低 priority 執行緒的被 context switch 暫停執行,因 此高 priority 的執行緒開始執行。在同一個 priority 中,MLQ 排程器有 time-slicing 功能。每一個執行緒在一段特定的時間內執行稱之為 time-slicing,統系開發者可 以設定 time-slicing 的長度。MLQ 排程器的駐列實作上是用 double linked circular list 來連結同一個層級的不同執行緒和不同層級的執行緒。圖 6 看到 MLQ 排程 器的功能[18]。
Figure 6. MLQ 排程器運做圖
三個執行緒,執行緒 ABC,priority 分別為 0, 0, 30。當 C 開始執行之後,A 準備完畢可以執行,於是 C 發生 context switch,A 開始執行。接著 B 也準備完 成,在 A 持續到它分配的時間用完,A 發生 context switch,讓 B 開始執行。AB 輪流執行完畢,才再由 C 繼續執行。
3.3.5.3. Bitmap Scheduler
Bitmap 排程器的執行緒也是有多個 priorities;然而每層級只能有一個執行 緒。這種設計簡化排程演算法,也令 bitmap 排程器非常有效率。Priority 的數量 和設定和 MLQ 相同;駐列的實作有三種,可以是 8,16 或 32 位元值,依設定的 priority 而定。因此駐列中一個位元代表一個 priority。Bitmap 排程器可以有 preemption,但是因為每個 priority 只有一個執行緒,故 time-slicing 會失去功能。
使用 bitmap 排程器時就會關閉 time-slicing。圖 7 是 bitmap 排程器的例子[18]:
Figure 7. Bitmap 排程圖運做圖
有三個不同 priority 的執行緒 ABC,其 priority 分別為 0、1、30。開始於 C
執行。接著 A 和 B 都可以執行,使得 context switch 發生,C 被置換出去。A 有 最高的 priority,所以 A 先執行,完成之後,發生 context switch,B 開始執行。
在 B 完成之後,C 才能繼續它的程序。
比較這兩種排程器,bitmap 排程器是較簡單的排程策略。但是,MLQ 排程 器可提供更多的選擇給執行緒操作,也可容納更多的執行緒,只要記憶體夠,就 沒有執行緒數量的限制。系統開發者可依應用程式的特殊需求而決定使用哪一種 排程器。
3.3.5.4. Synchronization Mechanisms
eCos kernel 提供系統中執行緒溝通機制和執行緒對分享資源存取的同步機 制。提供的機制有 Mutexes、Semaphores、Condition variables、Flags、Message boxes 和 Spinlock (for SMP systems)。
Kernel 提供同步 API,應用程式可以便利地使用這些同步機制。API 程式提 供 有 blocking 功 能 或 non-blocking 功 能 。 Blocking 程 式 呼 叫 , 如 cyg_semaphore_wait, 暫停執行緒的執行,直到 API 程式可以成功地完成。
Non-blocking 程式呼叫有兩種,第一種如 cyg_semaphore_trywait,不論程式有沒 有成功地完成,會回傳訊息指出呼叫的狀態,所以執行可以繼續執行。第二種 blocking 呼叫,必需先設定等待的時間,用時間長度為暫停長短的依據,如 cyg_semaphore_timed_wait。
Mutex 的目標和其他的都不同。Mutex 允許多個執行緒安全地存取共享的資 源。它只有兩種狀態: locked 和 unlocked。並且 Mutex 有擁有者的概念,只有擁 有者(上鎖者)可以解開 mutex。其他同步機制都是被使用來做為執行緒之間互相 溝通或從 DSR 連接到相關的 interrupt handler 到一個執行緒。
Semaphore 是 一 種 用 計 數 來 指 示 資 源 已 上 鎖 或 可 以 獲 得 的 同 步 機 制 。 Semaphore 有兩種型態,分別是 counting semaphore 和 binary semaphore。Binary semaphore 很像 counting semaphore,但是它的計數決不會大於一,所以它的狀態
只有 locked 和 unlocked;和 mutex 不同的是,它沒有擁有者的概念。意即每一個 執行緒都可以對 semaphore 上鎖和解鎖。Counting semaphore 依照計數值可以有 多種狀態。當執行緒 post semaphore 時增加的數值,同時也是當一個執行緒 wait 一個 semaphore 減少的數值。當 semaphore 回到零時,只有最高 priority 的執行 緒可以執行。
Condition variables 和 mutex 搭配使用允許多執行緒存取共享資源。傳統上,
一個執行緒產生資料,一個或多個執行緒等待這筆資料。當資料備妥時,產生資 料的執行可以發出 Condition variable 通知喚醒一個或喚醒全部執行緒。等待中的 執行緒就可以拿到需要的資料並處理之。
Flag 用 32 位元表現的同步機制。每一個位元代表一個狀態,允許執行緒去 等待一個或多個組成的狀態。當狀態符合,該執行緒就會被喚醒。
Message box 又叫 mail box,提供兩個執行緒交換資訊。因為 mailbox 的容 積有限,交換的資訊一般都是資料結構的指標,或是有特別設計過的訊息。
在對稱式多核心平台上,eCos kernel 提供另一種額外的同步機制。同時其他 的同步機制可以在對稱式多核心平台上運作。Spinlock 基本上是一個 flag,和其 它同步機制比較起來是更底層的操作,會依不同的硬體有不同的實作方式。有些 處理器提供 test-and-set 指令來實作 spinlock。在處理器執行一段特殊程式碼之前 要去檢查此 flag。如果 spinlock 沒有上鎖,處理器可以設定 flag 和繼續執行此執 行緒。如果 spinlock 被鎖上,執行緒就會持續地檢查 flag 直到它被解鎖,而沒有 被 suspend。
3.3.5.5. Threads and Interrupt Handling
Kernel 利用一種 two-level 方法處理 interrupt。連結每個 interrupt vector 是一 個 Interrupt Service Routine (ISR),它會盡可能快速的執行來回應硬體 interrupt。
ISR 只可以做少數的 kernel 呼叫,都是和 interrupt 系統相關,但不能做喚醒執行 緒的動作。如果 ISR 偵測到 I/O 完成,一個執行應該被喚醒,ISR 就會讓連結的
DSR 去執行。DSR 可以使用更多 kernel 呼叫,如發 condition variable 訊號或 post a semaphore。關閉 interrupt 可以阻止 ISR 執行,但系統中很少機會關閉 interrupt,
如果有也是很短一段時間。執行緒關閉 interrupt 的主要理由是改變一些 ISR 之間 共享的狀態設定。例如,如果一個執行緒會增加 linked list 的 node,而且 ISR 可 能在任何時候自 linked list 移除一個 node,此執行緒就要在操作 list 時關掉 interrupt。