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