實(shí)現(xiàn)一個(gè)最簡單的嵌入式操作系統(tǒng)
實(shí)現(xiàn)一個(gè)最簡單的嵌入式操作系統(tǒng)(一)
實(shí)現(xià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,USB,磁盤等外設(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)用支持,所以需要自己去裁剪C庫,自己去實(shí)現(xiàn)系統(tǒng)調(diào)用
4.實(shí)現(xiàn)步驟:首先是CPU選型,交叉編譯環(huán)境的建立,然后就是寫B(tài)OOTLOADER,寫操作系統(tǒng)
實(shí)現(xiàn)一個(gè)最簡單的嵌入式操作系統(tǒng)(二)
如何實(shí)現(xiàn)BOOTLOADER
1.之所以要實(shí)現(xiàn)一個(gè)專用的BOOTLOADER,一是為了更好的移植和自身的升級,二是為了方便操作系統(tǒng)的調(diào)試,當(dāng)然,你完全可以將這部分所要實(shí)現(xiàn)的與操作系統(tǒng)相關(guān)的功能集成到操作系統(tǒng)中去
2.確定一個(gè)簡單的BOOTLOADER所要完成的功能:我們這里只需要完成兩個(gè)主要功能,一是將操作系統(tǒng)加載到內(nèi)存中去運(yùn)行,二是將自己和操作系統(tǒng)內(nèi)核固化到ROM存儲(chǔ)區(qū)(這里的ROM可以是很多設(shè)備,比如嵌入式芯片中的FLASH,PC機(jī)上的軟盤,U盤,硬盤等)
3.BOOTLOADER的編寫:
第一步:要進(jìn)行相關(guān)硬件的初使化,比如在at91rm9200這塊嵌入式板子上(以后都使用這一款芯片,主要是我對這款芯片比較熟悉,嘿嘿),大概要做接下來的幾方面的工作,其一:將CPU模式切換進(jìn)系統(tǒng)模式,關(guān)閉系統(tǒng)中斷,關(guān)閉看門狗,根據(jù)具體情況進(jìn)行內(nèi)存區(qū)域映射,初始化內(nèi)存控制區(qū),包括所使用的內(nèi)存條的相關(guān)參數(shù),刷新頻率等,其二:設(shè)定系統(tǒng)運(yùn)行頻率,包括使用外部晶振,設(shè)置CPU頻率,設(shè)置總線頻率,設(shè)置外部設(shè)備所采用的頻率等。其三:設(shè)置系統(tǒng)中斷相關(guān),包括定時(shí)器中斷,是否使用FIQ中斷,外部中斷等,還有就是中斷優(yōu)先級設(shè)置,這里只實(shí)現(xiàn)兩個(gè)優(yōu)先級,只有時(shí)鐘中斷高一級,其它都一樣,而中斷向量初始化時(shí)都將這些中斷向量指向0x18處,并關(guān)閉這里的所有中斷,如果板子還接有諸如FLASH設(shè)備的話,還需要設(shè)置諸如FLASH相關(guān)操制寄存器,其四:需要關(guān)閉CACHE,到此為止,芯片相關(guān)內(nèi)容就完成初始化了
第二步:中斷向量表,ARM的中斷與PC機(jī)芯片的中斷向量表有一點(diǎn)差異,嵌入式設(shè)備為了簡單,當(dāng)發(fā)生中斷時(shí),由CPU直接跳入由0x0開始的一部分區(qū)域(ARM芯片自身決定了它中斷時(shí)就會(huì)跳入0x0開始的一片區(qū)域內(nèi),具體跳到哪個(gè)地址是由中斷的模式?jīng)Q定的,一般用到的就是復(fù)位中斷,F(xiàn)IQ,IRQ中斷,SWI中斷,指令異常中斷,數(shù)據(jù)異常中斷,預(yù)取指令異常中斷),而當(dāng)CPU進(jìn)入相應(yīng)的由0x0開始的向量表中時(shí),這就需要用戶自己編程接管中斷處理程序了,這就是需要用戶自己編寫中斷向量表,中斷向量表里存放的就是一些跳轉(zhuǎn)指令,比如當(dāng)CPU發(fā)生一個(gè)IRQ中斷時(shí),就會(huì)自動(dòng)跳入到0x18處,這里就是用戶自己編寫的一個(gè)跳轉(zhuǎn)指令,假如用戶在此編寫了一條跳轉(zhuǎn)到0x20010000處的指令,那么這個(gè)地址就是一個(gè)總的IRQ中斷處理入口,一個(gè)CPU可能有多個(gè)IRQ中斷,在這個(gè)總的入口處如何區(qū)分不同的中斷呢?就由用戶編程來決定了,具體實(shí)現(xiàn)請參見以后相關(guān)部分,中斷向量表的一般用一個(gè)vector.S文件,當(dāng)然,如何命名那是你自己的喜愛,但有一點(diǎn)需要聲明,那就是在鏈接時(shí)一定要將它定位在0x0處
第三步:設(shè)置堆棧,一般使用三個(gè)棧,一個(gè)是IRQ棧,一個(gè)是系統(tǒng)模式下的棧(系統(tǒng)模式下和用戶模式共享寄存器和內(nèi)存空間,這主要是為了簡單),設(shè)置棧的目的主要是為了進(jìn)行函數(shù)調(diào)用和局部變量的存放,不可能全用匯編,也不可能不用局部變量
第四步:將自己以后的代碼段和數(shù)據(jù)段全部拷貝至內(nèi)存,并將BSS段清零
第五步:進(jìn)行串口的初始化(主要是為了與用戶交互,進(jìn)行與PC機(jī)的文件傳輸),F(xiàn)LASH的初始化這里在FLASH中存放BOOT和內(nèi)核),F(xiàn)LASH驅(qū)動(dòng)的編寫(這里的驅(qū)動(dòng)有別于平常所說的驅(qū)動(dòng),由于FLASH不像SDRAM,只要設(shè)定了相關(guān)控制器之后就可以直接讀寫指定地址的數(shù)據(jù),對FLASH的寫操作是一塊一塊數(shù)據(jù)進(jìn)行,而不是一個(gè)字節(jié)一個(gè)字節(jié)地寫,具體請查閱相關(guān)資料)
第六步:等待一定的秒數(shù),來接收用戶進(jìn)行輸入,如果在指定的秒數(shù)內(nèi)用戶未輸入任何字符,那么
BOOT就開始在FLASH中的指定位置(可以由自己指定,這么做主要是為了簡單)讀取內(nèi)核的所有數(shù)據(jù)到內(nèi)存中(具體是內(nèi)存中的什么位置由自己指定,也可以采用LINUX之類的做法,就是在內(nèi)存的起始位置加上一個(gè)0x8000處),將跳轉(zhuǎn)到內(nèi)核的第一條代碼處);如果用戶在指定的秒數(shù)內(nèi)鍵入了字符(這主要是為了方便開發(fā),如果開發(fā)定型之后完全可以不要這段代碼),那么就在串口與用戶進(jìn)行交互,接受用戶在串口輸入的命令,比如用戶要求下載文件在FLASH中指定的位置等,具體內(nèi)容可參考U-BOOT之類的開源項(xiàng)目到這里為止,BOOT部分已完成,這個(gè)BOOT非常簡單,僅僅只是將PC機(jī)上傳下來的文件固化到FLASH中,然后再將FLASH中的操作系統(tǒng)內(nèi)核部分加載進(jìn)內(nèi)存中,并將CPU的控制權(quán)交給操作系統(tǒng),下一頁開始講解如何寫一個(gè)最簡單的操作系統(tǒng),呵,到現(xiàn)在才開始切入正題呢?。。。?br />
實(shí)現(xiàn)一個(gè)最簡單的嵌入式操作系統(tǒng)(三)
如何實(shí)現(xiàn)一個(gè)最簡單的操作系統(tǒng)
這里為了簡單,就不考慮可移植性開求,不從BOOT部分來接收參數(shù),也不對硬件進(jìn)行檢測,
也不需要進(jìn)行DATA段,代碼段的重定位。我只是讀了LINUX內(nèi)核相關(guān)部分,并未自己去實(shí)現(xiàn)
一個(gè)操作系統(tǒng),所以我以下所說的只是概念性的東西:
1.接管系統(tǒng)的中斷處理,由于BOOT部分的代碼決定了那個(gè)中斷向量表,從而決定了系統(tǒng)中斷
之后進(jìn)入的內(nèi)存位置,但BOOT并不知道操作系統(tǒng)的中斷處理函數(shù)位置所在啊,怎么辦呢?
有幾種方法,其一是:如果你的板子可以重映射地址,也就是可以將內(nèi)存條所在的位置
重映射成0x0開始,那么在鏈接內(nèi)核的時(shí)候,就將操作系統(tǒng)自己的中斷向量表定位在0x0處
并且在BOOTLOADER引導(dǎo)結(jié)束時(shí)就完成映射操作,并讓CPU跳轉(zhuǎn)到0x0處執(zhí)行;如果沒有重映
射功能,我就不曉得怎么辦了,不過我想到一個(gè)折衷的辦法,就是在BOOTLOADER啟動(dòng)完成
時(shí)(也就是將CPU控制權(quán)交給操作系統(tǒng)內(nèi)核時(shí)),重新改寫FLASH的0x0區(qū)域,就是將操作
系統(tǒng)的內(nèi)核的中斷向量表寫入FLASH區(qū)的0x0處,比如,當(dāng)一個(gè)IRQ發(fā)生時(shí),CPU決定了會(huì)
跳入0x18(假設(shè)這里FLASH占用地址總線0x0至0x0fffffff,內(nèi)存占用0x20000000至0x2fffffff)
,而BOOTLOADER在最后將0x18處的代碼修改成了0x20000000加上0x18的地址處的代碼,而這個(gè)
地址就是內(nèi)核的中斷向量表中的相關(guān)跳轉(zhuǎn)指令,就相當(dāng)于跳轉(zhuǎn)進(jìn)了內(nèi)核所關(guān)聯(lián)的IRQ處理函數(shù)
的地址上去執(zhí)行中斷處理函數(shù)了,而這樣的不好之處在于:當(dāng)系統(tǒng)重新上電之后,BOOT的
中斷向量表已經(jīng)被修改,除非BOOT本身不使用中斷,呵,在這樣簡單的系統(tǒng)中,BOOT是不
需要中斷功能的
2.這里為了簡單,所以沒有使用分頁內(nèi)存管理,就不需要建立頁表等操作,直接進(jìn)行操作
系統(tǒng)的堆棧設(shè)置,同BOOT一樣的設(shè)置過程一樣,接著就進(jìn)行BSS段清零操作,這里的BSS段
是指操作系統(tǒng)自身的BSS段,與BOOT的BSS段是同一個(gè)含義只是用在了不同的地方了,接著
就跳入了MAIN函數(shù)
3.為了最大可能的簡單,采用靜態(tài)建立任務(wù)結(jié)構(gòu)數(shù)組,比如只建立十個(gè)任務(wù),那么首先要
為這十個(gè)任務(wù)結(jié)構(gòu)分配段內(nèi)存,可以在堆上分配(這個(gè)分配的內(nèi)存直到操作系統(tǒng)結(jié)束才會(huì)
被釋放,當(dāng)然也可以指定一片操作系統(tǒng)的其它地方都用不到的內(nèi)存區(qū)域,不過這樣寫的話
就有點(diǎn)外行的味道了,而符務(wù)結(jié)構(gòu)數(shù)組的指針卻是全局變量,存放在BSS段或者DATA段),
由于在上一步中已經(jīng)分配了一個(gè)系統(tǒng)堆棧,那么我們這十個(gè)任務(wù)就分享這總體的堆棧區(qū)域
這里的重點(diǎn)就是如果定義每個(gè)任務(wù)結(jié)構(gòu)數(shù)組里面的結(jié)構(gòu),可以參照LINUX的相關(guān)部分設(shè)計(jì)
4.中斷處理:在第一步中已經(jīng)確定了CPU進(jìn)行相關(guān)的幾類型的中斷跳轉(zhuǎn)地址,而相同類型
的中斷卻只有一個(gè)入口地址,這里的中斷處理就會(huì)完成以幾個(gè)動(dòng)作:
其一:入棧操作,包括所有寄存器入棧,至于這個(gè)棧,就是在第二步中所設(shè)置的IRQ棧,
其二:屏掉所有中斷,呵,這里為了簡單起見,所以在處理中斷時(shí)不允許再次發(fā)生中斷
其三:讀取中斷相關(guān)的寄存器,判別是發(fā)生了什么中斷,以至于跳進(jìn)相關(guān)的中斷處理函
數(shù)中去執(zhí)行(在這里只包括兩種中斷,一是時(shí)鐘中斷,另一個(gè)是SWI中斷,也就是所謂
的系統(tǒng)調(diào)用時(shí)需要用到的)
其四:等待中斷處理完成,然后就開啟中斷并出棧,恢復(fù)現(xiàn)場,將CPU控制權(quán)交給被中斷
的代碼處
注意:
其一:在MIAN中必須首先確定整個(gè)系統(tǒng)有哪些需要處理的中斷,也就是有哪些中斷處理
函數(shù),然后才編寫這里的中斷處理函數(shù)
其二:本操作系統(tǒng)不處理虛擬內(nèi)存,其至連CPU異常都不處理(一切都為了簡單),一旦
發(fā)生異常,系統(tǒng)就死機(jī)
5.對TIMER的實(shí)現(xiàn),首先確定時(shí)間片,為了讓系統(tǒng)更穩(wěn)定,而且我們不需要實(shí)時(shí)功能,盡
可能讓時(shí)間片設(shè)置長一點(diǎn),比如我們讓一個(gè)任務(wù)運(yùn)行20個(gè)時(shí)鐘滴答數(shù),然后應(yīng)根據(jù)系統(tǒng)
頻率來確定每個(gè)系統(tǒng)滴答所占用的毫秒,這里使用5毫秒讓系統(tǒng)定時(shí)器中斷一次,那么就
需要寫時(shí)鐘寄存器,具體參閱芯片資料,計(jì)算下來,一個(gè)任務(wù)最大可能連續(xù)運(yùn)行100毫秒
,注意:我們的操作系統(tǒng)不支持內(nèi)核搶占,同時(shí)只支持兩級中斷優(yōu)先級,就是只有時(shí)鐘
中斷的優(yōu)先級高一點(diǎn),其它的優(yōu)先級都低一級,但是在中斷處理一節(jié)中卻屏掉了這個(gè)功能
因?yàn)橐贿M(jìn)入中斷處理,就禁止中斷,所以不管其它中斷優(yōu)先級有多高都沒有用的,這樣做
優(yōu)點(diǎn)是簡單了,但不好之處顯而易見,特別在相關(guān)中斷處理函數(shù)如果進(jìn)入了死循環(huán),那么
整個(gè)系統(tǒng)就死了,而且時(shí)間片也變得不準(zhǔn)確了,反正都不用實(shí)時(shí),也不需要實(shí)時(shí)鐘支持嘛
至于中斷優(yōu)先級設(shè)置請參閱芯片資料
6.進(jìn)程調(diào)度的實(shí)現(xiàn),也就是do_timer函數(shù)(時(shí)鐘中斷處理函數(shù)),有一個(gè)全局變量指針,
指向的就是當(dāng)前任務(wù)結(jié)構(gòu)數(shù)組(或者鏈表),當(dāng)時(shí)鐘中斷時(shí),就進(jìn)入此函數(shù)中,首先判斷
任務(wù)結(jié)構(gòu)體中的時(shí)間片是否用完,如未用完,就減一,然后退出中斷,讓CPU繼續(xù)運(yùn)行當(dāng)
前的任結(jié)構(gòu),若用完了時(shí)間片,就重置時(shí)間片,并重新尋找任何結(jié)構(gòu)數(shù)組中的下一個(gè)等待
運(yùn)行的任務(wù),若找到了,就切換至新的任務(wù),至于如何切換,請見下一頁描述,如果未找
到就切換到IDLE任務(wù)(類似于LINUX,呵呵,所有的處理就是模仿LINUX,由于本人水平太
差,所就不能自創(chuàng)一招),注意:為了簡單,所以沒有實(shí)現(xiàn)任務(wù)優(yōu)先級,也未實(shí)現(xiàn)任務(wù)
休眠等,也就是說只要靜態(tài)地決定了有十個(gè)任務(wù),這十個(gè)任務(wù)就按先后順序一個(gè)一個(gè)執(zhí)行
而且每個(gè)任務(wù)都不允許結(jié)束,就是說在每個(gè)進(jìn)程中的最后一句代碼都必須用死循環(huán),不然
的話系統(tǒng)就跑飛了),還有一點(diǎn),進(jìn)程不支持信號,沒有休眠與喚醒操作,這個(gè)CPU就是
不停地在運(yùn)行,呵呵,反正CPU又不是人,所以不需要人權(quán)的哈?。?!這種調(diào)度是不是簡
單得不能再簡單了??????。。?!
7.串口不使用中斷,這就是最大可能的降低難度,串口使用論詢的方式來實(shí)現(xiàn)讀寫(當(dāng)
然是阻塞的方式了哦,而且只有寫,不允許讀,因?yàn)樽x的時(shí)候需要涉及到采用中斷方式,
因?yàn)檩喸兎绞接袀€(gè)不好的地方,那就是正在讀的時(shí)候,這里有可能當(dāng)前進(jìn)程的時(shí)間片用
完了,系統(tǒng)切換到另一個(gè)進(jìn)程,這里你在PC機(jī)的串口輸入的數(shù)據(jù)就丟棄了,唉,又是為
了簡單嘛)
8,最后一步就是MIAN函數(shù)的最后一部分,將本進(jìn)程當(dāng)作IDLE進(jìn)程(相當(dāng)于修改任務(wù)結(jié)構(gòu)
數(shù)組中的數(shù)據(jù)),開啟中斷,將當(dāng)前進(jìn)程加入一段死循環(huán),以免它退出去。
9.編譯你的BOOTLOADER,KERNEL,并燒寫至FLASH,反復(fù)調(diào)試
10.至此將你的at91rm9200(或者是其它相類似的芯片)的串口接上PC機(jī),打開超級終端,
打開板子電源,說不定你的操作系統(tǒng)就打印出了"hello,world"了?。?!一個(gè)最簡單的操作
系統(tǒng)就出來了
下一頁是具體的功能模塊實(shí)現(xiàn)
實(shí)現(xiàn)一個(gè)最簡單的嵌入式操作系統(tǒng)(四)
任務(wù)結(jié)構(gòu)數(shù)組(或鏈表)的實(shí)現(xiàn)
我們的任務(wù)結(jié)構(gòu)就采用鏈表形式吧,但其長度是限定了的,頭指針是一個(gè)全局指針變量(
指針變量是一個(gè)無符號整型指針,其指針本身所在的地址是在BSS段,但其指向的內(nèi)容是分
配在堆上的一片內(nèi)存),分配內(nèi)核內(nèi)存的函數(shù)就用kmalloc吧,kmalloc函數(shù)需要自己編寫
呵,為了簡單,這個(gè)函數(shù)只接受一個(gè)參數(shù),就是所需分配大小,這個(gè)函數(shù)做得很簡單,首先
有一個(gè)全局針指,它在初始化時(shí)指向了整個(gè)堆的起始位置,并且固定大小,就是所謂的內(nèi)核
堆棧,在內(nèi)核堆棧之后就是用戶堆棧,由于總共有十個(gè)任務(wù),當(dāng)然不包括內(nèi)核本身的任務(wù),
所以整個(gè)堆棧就平均分成十一部分,注意:在所有任務(wù)初始化完成之后,還有一個(gè)步驟就是
將內(nèi)核這個(gè)任務(wù)移到用戶態(tài),相當(dāng)于要將自己的任務(wù)結(jié)構(gòu)的堆棧指針修改一下就行了),
判斷大小是否超出了內(nèi)核堆的可分配范圍,還有一點(diǎn),需要維護(hù)內(nèi)核堆和其它任務(wù)的堆,
需要進(jìn)行分塊,并且有一個(gè)全局的內(nèi)存使用標(biāo)識,就用數(shù)組吧,簡單,0表示相應(yīng)的內(nèi)存
部分未占用,1就表示占用,對應(yīng)的kfree就相當(dāng)于把標(biāo)志置0),
對于內(nèi)存的維護(hù),比較復(fù)雜,為了簡單,就定為4K,并且不能進(jìn)行大于四K的內(nèi)存申請,因?yàn)?br />大于4K之后,由于沒有虛擬地址的概念,就不能實(shí)現(xiàn)堆上的連續(xù)分配地址,當(dāng)然在棧上分配
是可以大于4K的,棧是由編譯器和CPU所決定了的
任務(wù)結(jié)構(gòu)包括:
1.所剩的時(shí)間片
2.本任務(wù)所指向的代碼段內(nèi)存地址,這里也就是函數(shù)入口地址
3.本任務(wù)所指向的數(shù)據(jù)段地址,這里的數(shù)據(jù)段被包含進(jìn)了整個(gè)內(nèi)核中,所以并沒有用,作為保留
4.本任務(wù)的函數(shù)體是否存在,也就是否會(huì)被調(diào)度
5.本任務(wù)所使用的棧指針
6.本任務(wù)所使用的堆指針
7.本任務(wù)的標(biāo)識,用0代表是IDLE,1代表是其它進(jìn)程
8.所有寄存器的值
9.當(dāng)前PC值,初始化時(shí)被置成了函數(shù)入口地址
首先講解一下任務(wù)數(shù)組結(jié)構(gòu)的初始化:
將先定義一個(gè)全局指針,然后將此指針強(qiáng)制轉(zhuǎn)換為一個(gè)任務(wù)結(jié)構(gòu)指針,并通過kmalloc函在內(nèi)核所
占用的堆(前而講過內(nèi)核的堆的起始就是整個(gè)堆的起始)上去分配十個(gè)任務(wù)結(jié)構(gòu)所占的內(nèi)存,這里
是絕不會(huì)超過4K的并且為這十個(gè)任務(wù)結(jié)構(gòu)賦值,將第一個(gè)任務(wù)置為IDLE,時(shí)間片為20,代碼段內(nèi)存地址為main函數(shù)的的地址,數(shù)據(jù)段地址忽略,函數(shù)體存在,可以被調(diào)度,棧指針指向的位置根據(jù)以下來計(jì)算:
假定每個(gè)給每個(gè)任務(wù)可使用的堆棧設(shè)定為64K,而整個(gè)堆的起始位置是0x20030000,那么第一個(gè)堆指針?biāo)赶虻木褪?x20030000,棧就是0x20030000+64K的位置,第二個(gè)以后就以此類推
注意:在初始化任務(wù)結(jié)構(gòu)之前,不允許系統(tǒng)使用堆,但可以使用棧,那么內(nèi)核任務(wù)棧部分就分成了
兩個(gè),在未進(jìn)行調(diào)度之前,棧就是上一頁中第二步中所設(shè)的棧,那么上一頁設(shè)置堆棧的時(shí)候就得注
意必須將堆??臻g設(shè)成十個(gè)64K再加上在本步驟使用以前的最大可能所需的??臻g
再講解一下任務(wù)切換時(shí)所要做的事情:
進(jìn)入整個(gè)中斷處理入口時(shí),會(huì)將所有寄存器推入IRQ棧之中,并把值拷貝到當(dāng)前任務(wù)結(jié)構(gòu)相應(yīng)的字段當(dāng)中,并取出被中斷的進(jìn)程的當(dāng)前PC值存入當(dāng)前任務(wù)結(jié)構(gòu)中的相應(yīng)字段中,接下就判別中斷類型,以進(jìn)入相應(yīng)的中斷處理函數(shù),這里就會(huì)進(jìn)入do_timer函數(shù)中,以下就是進(jìn)入此函數(shù)之后的流程:
內(nèi)核中還有一個(gè)全局指針,就是當(dāng)前任務(wù)指針,它本身也是在系統(tǒng)BSS段中,它的定義如上一步中的那個(gè)全局指針一樣,當(dāng)由系統(tǒng)時(shí)鐘中斷之后,就取出這個(gè)全局指針,上一步初始化完成之后,還會(huì)把這個(gè)指針指向第一個(gè)任務(wù)結(jié)構(gòu)所在位置,也就是0x20030000處,那么就取出這個(gè)任務(wù)結(jié)構(gòu)中的時(shí)間片字段,判斷其是否為0,若為0,就進(jìn)行以下的操作:保存用戶態(tài)下的棧指針至當(dāng)前任務(wù)結(jié)構(gòu),保存堆指針,并將搜索一下可以被調(diào)度的任務(wù)結(jié)構(gòu),并將此任務(wù)結(jié)構(gòu)賦給當(dāng)前任務(wù)指針,置需要進(jìn)行任務(wù)切換標(biāo)識,此標(biāo)識同樣是一個(gè)全局變量,但它是被賦了初值,會(huì)放在整個(gè)系統(tǒng)的DATA段中,返回do_timer函數(shù)。若不為0,就進(jìn)行以下操作:
將時(shí)間片減一,返回do_timer函數(shù)接下來判斷任務(wù)切換標(biāo)識,若為0,則進(jìn)行以下操作:
不需要進(jìn)行任務(wù)切換,所有寄存器出棧(這里的棧指的是IRQ棧),重新開啟中斷,切換到用戶模式,加載當(dāng)前任務(wù)結(jié)構(gòu)中的當(dāng)前PC值字段,以退出中斷處理程序若此標(biāo)識為1,則執(zhí)行以下操作:
就需要進(jìn)行任務(wù)切換,讓所有寄存器出棧(這里的棧指的是IRQ棧),將當(dāng)前任務(wù)結(jié)構(gòu)中的所有寄
存器的值恢復(fù)到相應(yīng)寄存器中,將用戶態(tài)下的棧指針恢復(fù)至當(dāng)前任務(wù)結(jié)構(gòu)棧指針,將堆指針恢復(fù)至
當(dāng)前任務(wù)結(jié)構(gòu)堆指針,并把需要進(jìn)行任務(wù)切換標(biāo)識恢復(fù)為0,重新開啟中斷,切換到用戶模式,任務(wù)切換是通過加載PC值來實(shí)現(xiàn)的,也就是通過加載當(dāng)前任務(wù)結(jié)構(gòu)中的當(dāng)前PC值字段,以退出中斷處理程序
系統(tǒng)調(diào)用的實(shí)現(xiàn)
本系統(tǒng)是完全可以不實(shí)現(xiàn)系統(tǒng)調(diào)用的,因?yàn)闆]有實(shí)現(xiàn)內(nèi)核態(tài)和用戶態(tài)的保護(hù),完全可以不實(shí)現(xiàn)
自己的C庫,所有的函數(shù)都像kmalloc之類的實(shí)現(xiàn)一樣,在內(nèi)核中直接寫函數(shù)原型,但為了以后
擴(kuò)展,還是說一下系統(tǒng)調(diào)用,這里以malloc系統(tǒng)調(diào)用來實(shí)現(xiàn)
首先說明還有一個(gè)堆指針(前面在kmalloc時(shí)有一個(gè)堆指針,不過那個(gè)堆指針是為內(nèi)核任務(wù),中
斷處理所提供),這里這個(gè)堆指針是用于用戶態(tài)的,它在系統(tǒng)初始化完成之前會(huì)賦上初值,其初
值就是第一個(gè)任務(wù)結(jié)構(gòu)所使用的堆的起始位置,也就是在內(nèi)核所使用的堆加上64K的位置
函數(shù)庫中的malloc函數(shù)實(shí)現(xiàn)步驟如下:
1.首先檢測申請大小是否超出了4K,若超出4K,就返回錯(cuò)誤
2.進(jìn)行系統(tǒng)調(diào)用(這里用_syscall1,并只傳遞一個(gè)參數(shù)(所需分配大?。?br />系統(tǒng)調(diào)用函數(shù)_syscall1的實(shí)現(xiàn):
1.將寄存器壓入堆棧(這里的棧指向就是當(dāng)前任務(wù)的棧)
2.將系統(tǒng)調(diào)用號1放至R0,參數(shù)放入R1
3.發(fā)出SWI指令以產(chǎn)生SWI中斷(就是所說的軟中斷,陷阱)
此時(shí)系統(tǒng)發(fā)生中斷,會(huì)進(jìn)入SWI中斷處理入口,下面說一下SWI入口函數(shù)的實(shí)現(xiàn)
1.取出R0的值,判斷其值,進(jìn)入相應(yīng)的分支處理代碼段
2.在此進(jìn)入_malloc處理代碼段,取出R1的值,然后再得到前面所說的當(dāng)前堆指針,并申請對應(yīng)數(shù)
據(jù)塊大小,置用于內(nèi)存占用標(biāo)識的相應(yīng)字段,將當(dāng)前堆指針放入R0,移動(dòng)當(dāng)前堆指針,改變當(dāng)前任
務(wù)結(jié)構(gòu)的堆指針,切換到用戶態(tài),返回SWI中斷系統(tǒng)調(diào)用_syscall1的返回處理:
為了簡單,在從內(nèi)核態(tài)返回用戶態(tài)時(shí),不再進(jìn)行任務(wù)的重新調(diào)度,所以上面的步驟就相對簡單
1.當(dāng)從SWI中斷返回后,系統(tǒng)就運(yùn)行在了用戶態(tài),此時(shí)取出R0的值,并賦值給需要申請內(nèi)存的指針
2.在用戶態(tài)彈出寄存器,返回到上一層函數(shù)
malloc函數(shù)的返回,此時(shí)malloc函數(shù)直接返回指針就行了,整個(gè)malloc的流程就結(jié)束了,其它的系
統(tǒng)調(diào)用同這個(gè)過程類似
到此為止,這個(gè)操作系統(tǒng)初步實(shí)現(xiàn)了,但好像什么事情都不能做,如果讓它支持串口中斷的話,或許可以做那么一點(diǎn)點(diǎn)事情,比如像單片機(jī)那樣的功能,整個(gè)系統(tǒng)的難點(diǎn)就是中斷處理和任務(wù)切換,在本例中,由于ARM不支持像0x86那樣的CPU級的保護(hù)模式,所以進(jìn)行任務(wù)切換的時(shí)候,就得自己通過加載PC值的方法來實(shí)現(xiàn),呵,因?yàn)槲蚁氩坏礁玫霓k法,但這個(gè)辦法有一個(gè)不好解決的地方,就是寄存器入棧和出棧的保護(hù),在進(jìn)入中斷時(shí),必須保護(hù)寄存器,但如果需要進(jìn)行重新調(diào)度,就得從中斷上下文切換到進(jìn)程上下文中,如何從中斷上下文切換到進(jìn)程上下文呢??我在這里所采用的方法很笨拙:
1.首先讓寄存器入棧
2.讓寄存器保存至當(dāng)前任務(wù)結(jié)構(gòu)數(shù)組,被中斷掉的進(jìn)程的PC值保存至任務(wù)結(jié)構(gòu)
3.處理timer中斷
4.如果進(jìn)行任務(wù)切換,尋找下一個(gè)可調(diào)度的進(jìn)程,然后把當(dāng)前任務(wù)結(jié)構(gòu)指下剛搜索到
的任務(wù)結(jié)構(gòu),讓寄存器出棧,恢復(fù)當(dāng)前任務(wù)結(jié)構(gòu)里的值到寄存器,恢復(fù)堆棧指針,切換到用戶態(tài),通過加載當(dāng)前任務(wù)結(jié)構(gòu)的PC值來恢復(fù)被掛起的進(jìn)程這里在中斷上下文中使用了任務(wù)結(jié)構(gòu),這在LINUX上好像是不這樣用的,中斷上下文和進(jìn)程上下文是兩個(gè)不同的概念,中斷上下文中不能訪問進(jìn)程上下文里的任務(wù)結(jié)構(gòu),我實(shí)在想不出有什么辦法來實(shí)現(xiàn)進(jìn)程調(diào)度了,所以請看到我這則文章的人提出好一點(diǎn)的方法
linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)
評論