寫一個簡易嵌入式操作系統(tǒng)
在了解了操作系統(tǒng)的原理和工作后,我們可以自己寫一個簡易的嵌入式操作系統(tǒng),下面由學習啦小編為大家整理了寫一個簡易嵌入式操作系統(tǒng)的相關知識,希望對大家有幫助!
寫一個簡易嵌入式操作系統(tǒng)概述
1.首先確定CPU,在這里為了簡單,就選用嵌入式的CPU,比如arm系列,之所以用RISC(簡單指令集)類型的CPU,其方便之處是沒有實模式與保護模式之分,采用線性的統(tǒng)一尋址,也就是不需要進行段頁式內(nèi)存管理,還有就是芯片內(nèi)部集成了一些常用外設控制器,比如以太網(wǎng)卡,串口等等,不需要像在PC機的主板上那么多外設芯片
2.確定要實現(xiàn)的模塊和功能,為了簡單,只實現(xiàn)多任務調(diào)度(但有限制,比如最多不超過10),實現(xiàn)中斷處理(不支持中斷優(yōu)先級),不進行動態(tài)SHELL交互,不實現(xiàn)動態(tài)模塊加載,不實現(xiàn)fork之類的動態(tài)進程派生和加載(也就是說要想在你的操作系統(tǒng)上加入用戶程序,只能靜態(tài)編譯進內(nèi)核中;不支持文件系統(tǒng),不支持網(wǎng)絡,不支持PCI,磁盤等外設(除了支持串口,呵呵,串口最簡單嘛),不支持虛擬內(nèi)存管理(也就是說多任務中的每個進程都可以訪問到任何地址,這樣做的話,一個程序死了,那么這個操作系統(tǒng)也就玩完了)
3.確定要使用的編譯器,這里采用GCC,文件采用ELF格式,當然,最終的文件就是BIN格式,GCC和LINUX有著緊密的聯(lián)系,自己的操作系統(tǒng),需要C庫支持和系統(tǒng)調(diào)用支持,所以需要自己去裁剪庫,自己去實現(xiàn)系統(tǒng)調(diào)用
4.實現(xiàn)步驟:首先是CPU選型,交叉編譯環(huán)境的建立,然后就是寫B(tài)OOTLOADER,寫操作系統(tǒng)通過以上4點的學習一個簡單的嵌入式操作系統(tǒng)準備工作就差不多做好了。
寫一個簡易嵌入式操作系統(tǒng)詳解
程序本質(zhì)的剖析
寫操作系統(tǒng)這個高端大氣上檔次的工作肯定要有一些鋪墊了,最必須的就是對你寫的程序的了解,也許你會說,我寫的程序,我還能不理解嗎,但是這次咱么要從寄存器角度分析。
咱們首先從類比學習開始,咱們先來理解中斷,對于中斷,學習單片機的小朋友們肯定很理解,咱么來一起回顧下,單片機是怎么用硬件實現(xiàn)中斷的(更為具體的說明在Cortex-M3權威指南-carpter9中斷的具體行為)其實中斷就是多任務的環(huán)境了,只不過這個多任務環(huán)境只能有兩個任務(在只有一個中斷的前提下),那么只要咱么能模擬出來中斷,那實現(xiàn)自己的操作系統(tǒng)也是很簡單的呢。
CM3中斷的一個完整過程由一下幾個部分組成
1. 入棧
2. 取向量
3. 更新寄存器
4. 異常返回
入堆棧:響應異常的第一個行動,就是自動保存現(xiàn)場的必要部分:依次把xPSR, PC, LR, R12以及R3‐R0由硬件自動壓入適當?shù)亩褩V?,就是保存要切換出去的任務,因為下面就要開始執(zhí)行中斷這個任務了,如果不保存就無法實現(xiàn)這個任務的完全復原了。
取向量:其實就是找到中斷任務的入口地址,這樣才能開始執(zhí)行中斷函數(shù)
更新寄存器
異常返回:當異常服務例程執(zhí)行完畢后,需要很正式地做一個“異常返回”動作序列,從而恢復先前的系統(tǒng)狀態(tài),才能使被中斷的程序得以繼續(xù)執(zhí)行
操作系統(tǒng)的任務切換也是大同小異
1. 屏蔽所有中斷
2. 保存正在執(zhí)行的任務的寄存器信息到任務獨立的堆棧中
3. 從要投入運行的任務的堆棧中取出數(shù)據(jù)到寄存器中
4. 取消中斷屏蔽
經(jīng)過這四步,上一個任務已經(jīng)被保存起來,等待下一次的運行,要運行的任務已經(jīng)開始了運行
上面這四步只是一個大體的概述
在對CM3內(nèi)核的實現(xiàn)描述前有一些準備知識
1. CM3寄存器基礎(在Cortex-M3權威指南一書)
2. BASEPRI寄存器,用于中斷屏蔽(在Cortex-M3權威指南一書)
3. 線程模式和handler模式,在保存上下文時用(在Cortex-M3權威指南一書)
4. 特權級和用戶級,明白在Systick中斷時的情況(在Cortex-M3權威指南一書)
5. PendSV異常,在這個異常中進行任務切換(在Cortex-M3權威指南一書)
6. SVC異常,啟動OS,開始執(zhí)行第一個任務就是通過呼叫SVC異常(在Cortex-M3權威指南一書)
7. MSP和PSP雙堆棧指針的使用,保存寄存器時用(在Cortex-M3權威指南一書)
8. 中斷控制及狀態(tài)寄存器ICSR,知道如何觸發(fā)PendSV異常(在Cortex-M3權威指南一書)
9. 向量表偏移量寄存器VTOR,第一次切入任務的(在Cortex-M3權威指南一書)
10. 向量表結(jié)構,得到MSP的初始值(在Cortex-M3權威指南一書)
11. 系統(tǒng)異常優(yōu)先級寄存器,用于設置PendSV異常和Systick異常的優(yōu)先級(在Cortex-M3權威指南一書)
下面詳細說明上述幾點
1. CM3寄存器基礎
1.BASEPRI寄存器,用于中斷屏蔽(在Cortex-M3權威指南一書)
在更精巧的設計中,需要對中斷掩蔽進行更細膩的控制——只掩蔽優(yōu)先級低于某一閾值的中斷——它們的優(yōu)先級在數(shù)字上大于等于某個數(shù)。那么這個數(shù)存儲在哪里?就存儲在BASEPRI中。不過,如1果往BASEPRI中寫0,則另當別論——BASEPRI將停止掩蔽任何中斷。例如,如果你需要掩蔽所有優(yōu)先級不高于0x60的中斷,則可以如下編程:
MOV R0, #0x60
MSR BASEPRI, R0
如果需要取消 BASEPRI 對中斷的掩蔽,則示例代碼如下:
MOV R0, #0
MSR BASEPRI, R0
2.線程模式和handler模式
3.特權級和用戶級
4.PendSV異常
試想一個這個過程一個ISR正在執(zhí)行SysTick 異常會搶占其 ISR,在這時OS 不得執(zhí)行上下文切換,否則將使中斷請求被延遲,而且在真實系統(tǒng)中延遲時間還往往不可預知——任何有一丁點實時要求的系統(tǒng)都決不能容忍這種事。因此,在 CM3 中也是嚴禁沒商量——如果 OS 在某中斷活躍時嘗試切入線程模式,將觸犯用法 fault 異常?,F(xiàn)在好了,PendSV 來完美解決這個問題了。PendSV 異常會自動延遲上下文切換的請求,
直到其它的 ISR 都完成了處理后才放行。為實現(xiàn)這個機制,需要把 PendSV 編程為最低優(yōu)先級的異常。如果 OS 檢測到某 IRQ 正在活動并且被 SysTick 搶占,它將懸起一個 PendSV 異常,
以便緩期執(zhí)行上下文切換。
5.SVC異常
SVC 用于產(chǎn)生系統(tǒng)函數(shù)的調(diào)用請求。例如,操作系統(tǒng)不讓用戶
程序直接訪問硬件,而是通過提供一些系統(tǒng)服務函數(shù),用戶程序使用 SVC 發(fā)出對系統(tǒng)服務函
數(shù)的呼叫請求,以這種方法調(diào)用它們來間接訪問硬件。
6.MSP和PSP雙堆棧指針的使用
一般情況下
在線程模式下使用PSP,在handler模式下使用MSP
所以在進行任務切換的時候,只需要把通用寄存器數(shù)據(jù)壓入任務的私有堆棧。
在異常的時候,只能使用MSP堆棧指針,任務切換又是在PendSV異常中進行的,
所以進入PnedSV異常的時候,
7.先把通用寄存器的內(nèi)容保存到要切換出去的任務的私有堆棧(這是保存上文),
8.保存通用寄存器到主堆棧,
9.屏蔽所有中斷,進入臨界區(qū)
10.調(diào)用C語言函數(shù)進行切換當前任務的TCB指針,
11.返回到異常匯編函數(shù)中
12.解除中斷屏蔽
13.從主堆棧中恢復數(shù)據(jù)到通用寄存器,
14.從要切入任務的私有堆棧中恢復數(shù)據(jù)到通用寄存器
15.退出異常
16.中斷控制及狀態(tài)寄存器ICSR
ICSR的第28位是讀寫類型,向這個位寫1就可以實現(xiàn)懸起PendSV異常
17.向量表偏移量寄存器VTOR
把這個作為地址從中取出的就是向量表的第一塊內(nèi)容
18.向量表結(jié)構
向量表的第一塊內(nèi)容是MSP 的初始值
19.系統(tǒng)異常優(yōu)先級寄存器
PendSV異常和Systick異常在操作系統(tǒng)中,應該設成最低,
通過這兩個寄存器改變這兩個異常的優(yōu)先級
應該修改成0xf0
下面對于CM3這個內(nèi)核說一下詳細的實現(xiàn)步驟
咱們先從簡單的來,加入現(xiàn)在你寫了兩個函數(shù)
并且有一個任務切換函數(shù)
void TaskSwitch(void);
void Task0(void)
{
while(1)
{
//do something task
//實現(xiàn)任務的主動切換,就是把當前任務切換出去把另一個任務切換進去
TaskSwitch();
}
}
void Task1(void)
{
while(1)
{
//do something task
//實現(xiàn)任務的主動切換,就是把當前任務切換出去把另一個任務切換進去
TaskSwitch();
}
}
在main函數(shù)中調(diào)用Task0函數(shù),實現(xiàn)手動啟動Task0,這就進入了任務切換的循環(huán)了,那么TaskSwitch怎么實現(xiàn)了,下面開始進入重點,開始一步一步說明,如何實現(xiàn)這個函數(shù)。
這里有一個前提
/* 當前任務控制塊的指針 */
OS_TCB * p_OS_TCB_Current;
/* 高優(yōu)先級任務控制塊指針 */
OS_TCB * p_OS_HighPriTCB_Current;
首先先說一下TaskSwitch函數(shù)中實現(xiàn)了什么
1. 屏蔽中斷,進入臨界區(qū)
2. 根據(jù)相應的算法計算下一個應該切入的任務是那個,咱么這里很簡單,
如果正在執(zhí)行任務0,那么切換到任務1,
如果正在執(zhí)行任務1,那么切換到任務0,
這就實現(xiàn)了最簡單的任務切換。
3. 把p_OS_HighPriTCB_Current指向要切入的函數(shù)
4. 觸發(fā)PendSV異常
5. 解除中斷屏蔽,退出臨界區(qū)
這塊的任務C語言就可以實現(xiàn),但是用匯編寫效率可能會更高
下面開始演示
屏蔽中斷,進入臨界區(qū)
這里就要利用上面說的準備的知識了—BASEPRI寄存器,因為用的是一個八位寄存器的高四位作為優(yōu)先級,這里只要把一個0x10的數(shù)寫入BASEPRI寄存器,就可以實現(xiàn)屏蔽所有的中斷。
根據(jù)相應的算法計算下一個應該切入的任務是那個,咱么這里很簡單,
如果正在執(zhí)行任務0,那么切換到任務1,
如果正在執(zhí)行任務1,那么切換到任務0,
這就實現(xiàn)了最簡單的任務切換。
把p_OS_HighPriTCB_Current指向要切入的函數(shù)
用C語言即可實現(xiàn)
觸發(fā)PendSV異常
ICSR的第28位是讀寫類型,向這個位寫1就可以實現(xiàn)懸起PendSV異常
解除中斷屏蔽,退出臨界區(qū)
如果往BASEPRI中寫0,則——BASEPRI將停止掩蔽任何中斷
完成這幾步就完成了任務切換,最基本的多任務環(huán)境就實現(xiàn)了。