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