嵌入式系統/IO程式設計
如果嵌入式系統無法與外部世界通訊,它將毫無用處。為此,嵌入式系統需要採用I/O機制來接收外部資料,並將命令傳送回外部世界。很少有計算機科學課程會提及IO程式設計,儘管它是嵌入式系統程式設計的核心功能。因此,本章將作為I/O程式設計的速成課程,適用於有C語言背景的讀者,也適用於沒有C語言背景的讀者。
在程式設計IO匯流排控制時,有5種主要的處理方法:主執行緒輪詢、多執行緒輪詢、中斷方法、中斷+執行緒方法以及使用DMA控制器。
在這種方法中,無論何時有輸出準備傳送,您都檢查匯流排是否空閒,然後傳送它。根據匯流排的工作方式,傳送它可能需要很長時間,在此期間您可能無法執行其他操作。輸入的工作原理類似:每隔一段時間,您檢查匯流排以檢視是否有輸入。
優點
- 易於理解
缺點
- 效率非常低,尤其是在需要手動將資料推送到總線上時(而不是透過DMA)。
- 如果您需要手動推送資料,那麼您將無法執行其他操作,這可能會導致即時硬體出現問題。
- 根據輪詢頻率和輸入頻率,您可能會因為處理速度不夠快而丟失資料。
一般來說,此係統僅應在IO僅在不頻繁的間隔內發生時,或者在有更重要的事情需要處理時可以將其推遲的情況下使用。如果您的系統支援多執行緒或中斷,您應該使用其他技術。
在這種方法中,我們啟動一個特殊的執行緒來進行輪詢。如果輪詢時沒有IO,它會將自己休眠預定義的時間。如果有IO,它會在IO執行緒上處理IO,從而允許主執行緒執行所需的操作。
優點
- 不會推遲主執行緒
- 允許您透過更改執行緒的優先順序來定義IO的重要性
缺點
- 效率仍然有點低
- 如果IO頻繁發生,您的輪詢間隔可能太小,無法讓您充分休眠,從而導致其他執行緒飢餓。
- 如果您的執行緒優先順序太低,或者作業系統執行緒過多,導致作業系統無法及時喚醒執行緒,則可能會丟失資料。
- 需要支援執行緒的作業系統
此技術非常適合您的系統支援執行緒,但不支援中斷或已用盡中斷。當需要頻繁的IO時,它並不適用- 如果間隔太小,作業系統可能無法正確休眠執行緒,並且您將新增每次輪詢兩個上下文切換的開銷。
(中斷架構使用中斷,我們將在本章嵌入式系統/中斷中詳細討論)。
在這種方法中,匯流排在IO準備就緒時向處理器發出中斷。然後處理器跳轉到一個特殊函式,並放棄它正在執行的其他操作。特殊函式(稱為中斷處理程式或中斷服務例程)處理所有IO,然後返回到它正在執行的操作。
優點
- 效率很高
- 非常簡單,只需要一個函式
缺點
- 如果處理IO需要很長時間,您可能會使其他操作飢餓。如果您的處理程式遮蔽中斷,這尤其危險,因為這會導致您錯過來自即時硬體的硬體中斷。
- 如果您的處理程式花費的時間太長,以至於在您處理現有輸入之前就緒了更多輸入,則可能會丟失資料。
只要處理IO是一個短暫的過程,此技術就很棒,例如,您只需要設定DMA。如果這是一個漫長的過程,請使用多執行緒輪詢或中斷與執行緒。
- 我們在嵌入式系統/中斷中更詳細地討論了此技術。
在這種技術中,您使用中斷來檢測何時IO準備就緒。您不是直接處理IO,而是中斷向執行緒發出訊號,告知IO已準備就緒,並讓該執行緒處理IO。向執行緒發出訊號通常透過訊號量完成 - 訊號量初始化為佔用狀態。IO執行緒嘗試獲取訊號量,該操作會失敗,作業系統會將其休眠。當IO準備就緒時,中斷會觸發並釋放訊號量。然後執行緒會醒來,處理IO,然後再嘗試獲取訊號量並被重新休眠。
中斷向量指向的例程是“一級中斷處理程式”。作業系統隨後喚醒的處理其餘工作的執行緒是“二級中斷處理程式”。
優點
- 最低延遲 - 與所有其他中斷在該中斷完全處理之前被停用不同,中斷會在儘可能快的時間(在一級中斷處理程式結束時)重新啟用。
- 不會推遲主執行緒
- 允許您透過更改執行緒的優先順序來定義IO的重要性
- 效率很高 - 僅在需要時進行上下文切換,並且不會進行輪詢。
- 從架構上來說這是一個非常乾淨的解決方案,允許您在處理IO的方式上非常靈活。
- 二級中斷處理程式可以等待鎖被釋放 (嵌入式系統/鎖和臨界區).
缺點
- 需要支援執行緒的作業系統
- 最複雜的解決方案
此解決方案是最靈活的,也是效率最高的解決方案之一。它還最大限度地降低了使更重要的任務飢餓的風險。它可能是當今最常用的方法。
在某些特殊情況下,例如,當必須將一組資料傳輸到通訊IO裝置時,可能存在DMA控制器,它可以自動檢測何時IO裝置準備接收更多資料,並傳輸該資料。此技術可以與許多其他技術一起使用,例如,當資料傳輸完成時可以使用中斷。
優點
- 這提供了最佳的效能,因為I/O可以與其他程式碼執行並行發生。
缺點
- 僅適用於有限範圍的問題
- 並非所有系統都具有DMA控制器。這在更基本的8位微控制器中尤其如此。
- 並行性質可能會使系統複雜化
Dos.h標頭檔案通常包含在許多C發行版中,尤其是在DOS和Windows系統上。此檔案包含有關許多不同例程的資訊,但最重要的是,它包含可用於從C程式直接提供埠輸出的inp() 和 outp() 函式的原型。但是,許多嵌入式系統在其庫中沒有Dos.h標頭檔案,也沒有任何預編譯的C例程來處理埠輸入和輸出。因此,本章的目的是教讀者如何“自己編寫”輸入和輸出例程。
一些 C 編譯器發行版包含 <iohw.h> 介面。它允許編寫相對可移植的硬體裝置驅動程式程式碼。它用於實現標準 C++ <hardware> 介面。 [1]
x86 指令集包含 2 條指令:in 和 out,這兩個函式都接受 2 個引數,一個埠號,然後是另一個引數來接收資料或傳送資料(取決於你使用哪條命令)。
我們可以使用 CDECL 呼叫約定在彙編中定義 2 個函式,這些函式可以與我們的 C 程式連結,並從我們的 C 程式中呼叫來處理埠輸出和輸入。
資料可以同步或非同步傳輸。同步傳輸是指使用時鐘訊號傳送的傳輸。這樣接收器就能準確地知道每個位元的開始和結束位置。這樣,對噪聲和抖動的敏感性就會降低。此外,同步傳輸通常需要發射器和接收器之間進行廣泛的握手,以確保所有時序機制同步在一起。相反,非同步傳輸是在沒有時鐘訊號的情況下發送的,並且通常沒有太多握手。
- ↑ "C++ 效能技術報告" 由 Dave Abrahams 等人撰寫。2003 年