ARM學習筆記之——MiniOS
1. 概述
最近,我花了大量的時間學習了楊鑄老師寫的《深入淺出嵌入式底層軟件開發(fā)》,看完了ARM體系結(jié)構(gòu)與編程這一章。在這章節(jié)的最后,作者做了一個用于總結(jié)前面所學內(nèi)容的操作系統(tǒng)MiniOS,并附帶了其中的源代碼。我認真學習了其中的所有代碼,悟到了其中非常巧妙的構(gòu)思。
本文引用地址:http://m.butianyuan.cn/article/201611/318808.htm讀這個MiniOS源代碼我遇到了最大的幾個問題如下:
(1)系統(tǒng)是怎么啟動的?
(2)開啟了MMU后,虛擬地址是怎么映射上物理地址上的?
(3)系統(tǒng)是怎么開啟MMU的,為什么開啟了MMU內(nèi)存地址重映射之后程序還能正常運行?
(4)main( ) 函數(shù)是怎么變成task0的?
(5)任務之間是怎么切換的?
(6)任務中怎么被創(chuàng)建,并運行起來的?
上述這幾個問題都是很細微,但又很難搞清楚的核心知識。筆者在此把自己悟到的東西分享出來,供大家參考。
其它,如:系統(tǒng)函數(shù)調(diào)用、任務調(diào)度機制、LED、UART、按鍵怎么實現(xiàn),不做過多研究。
2. 詳細內(nèi)容
2.1 系統(tǒng)是怎么啟動的?
首先說明,書上提供的MiniOS工程編譯后的運行地址為0x33FF0000,不是 0x00000000,這點很重要。
-info totals -ro-base 0x33ff0000 -first start.o
而程序編譯完成后,生成了bin文件將被燒錄到NorFlash的0x00000000地址上,也就很重要!
ARM復位后,PC從NorFlash的0x00000000地址上取提,也就是”b Reset“,之后跳到Reset標號上繼續(xù)執(zhí)行。代碼如下:
- AREAStart,CODE,READONLY
- ENTRY;代碼段開始
- bReset
- ……
- Reset;Reset異常處理符號
- blclock_init;跳往時鐘初始化處理
- blmem_init;跳往內(nèi)存初始化處理
- ldrsp,=SVC_STACK;設置管理模式棧指針,common_asm.h中定義
- bldisable_watch_dog;關閉看門狗
之后所有的跳轉(zhuǎn)都是用到b或bl,進行相對跳轉(zhuǎn)。再跳轉(zhuǎn)也是以PC為起始,相對位置跳轉(zhuǎn),不會受運行地址的影響。
初始化了時鐘、SDRAM、關閉看門狗、設置sp。有人可能會問:為什么在進行了bl之后再設置棧指針?其實,哪里設置都無所謂,因為bl指令返回地址只保存在LR寄存器中,不放在棧里。SP被設置成了0x33FF0000,向下擴展,將來還會提及。
然后初始化SDRAM(如果不初始化,SDRAM是不能使用的),將程序自己從0x00000000地址復制一份到0x33FF0000地址上。然后再來一個絕對地址跳轉(zhuǎn),轉(zhuǎn)到0x33FF0000地址域上的xmain地址處繼續(xù)執(zhí)行。如下:
- copy_code;代碼拷貝開始符號
- movr0,#0x0;R0中為數(shù)據(jù)開始地址(ROM數(shù)據(jù)保存在0地址開始處)
- ldrr1,=|ImageBase|;R1中存放RO輸出域運行地址,
RO - ldrr2,=|ImageLimit|;R2中存放ZI輸出域結(jié)束地址,
ZI - subr2,r2,r1;R2=R2-R1,得出待拷貝數(shù)據(jù)長度
- blCopyCode2Ram;將R0,R1,R2三個參數(shù)傳遞給CopyCode2Ram函數(shù)執(zhí)行拷貝
- ldrr0,=|ImageBase|
ZI - ldrr1,=|ImageLimit|
ZI - blclear_bss_region
- blstack_init;跳往棧初始化代碼處
- msrcpsr_c,#0x5f;開啟系統(tǒng)中斷,進入系統(tǒng)模式
- ldrlr,=halt_loop;設置返回地址
- ldrpc,=xmain;跳往main函數(shù),進入OS啟動處理
- halt_loop
- bhalt_loop;死循環(huán)
xmain()函數(shù)定議在main.c文件中。
- intxmain(void)
- {
- pgtb_init();//建立頁表
- mmu_init();//mmu初始化
- uart_init();//串口初始化
- irq_init();//中斷初始化
- Timer0_init();//定時器0初始化
- key_init();//按鍵初始化
- led_init();//led燈初始化
- }
2.2 開啟了MMU后,虛擬地址是怎么映射上物理地址上的?
在xmain函數(shù)中,pgtb_init() 函數(shù)的功能就是構(gòu)建頁表,TTB=0x300F0000。- voidpgtb_init()
- {
- unsignedlongentry_index,SFR_base;
- /*建立到Norflash的2MB的地址空間的映射*/
- /*0xA0000000映射到0開始的1MB地址空間*/
- *(mmu_tlb_base+(0xA0000000>>20))=0x0|SEC_DESC;
- /*0xA0100000映射到0x100000~0x1FFFFF的1MB地址空間*/
- *(mmu_tlb_base+(0xA0100000>>20))=0x100000|SEC_DESC;
- /*令0x30000000~0x34000000的64MB虛擬地址等于物理地址空間,方便miniOS內(nèi)部進程管理*/
- for(entry_index=0x30000000;entry_index<0x34000000;entry_index+=0x100000){
- *(mmu_tlb_base+(entry_index>>20))=entry_index|SEC_DESC;
- }
- /*特殊功能寄存器0x48000000~0x60000000地址空間映射到0xC8000000~0xE0000000虛擬地址空間*/
- for(entry_index=0x48000000+0x80000000,SFR_base=0x48000000;
- SFR_base<0x60000000;entry_index+=0x100000,SFR_base+=0x100000){
- *(mmu_tlb_base+(entry_index>>20))=SFR_base|SEC_DESC;
- }
- /*
- *進程1-23號進程地址空間,每個進程32MB,miniOS允許進程使用32MB虛擬地址空間,但是只分配其1MB的實際物理空間
- *進程1:物理地址空間0x30100000-0x301fffff,對應MVA(修正虛擬地址,進程PID<<25形成)
- *MVA地址空間:0x02000000-0x021fffff
- *進程2:物理地址空間0x30200000-0x302fffff
- *MVA地址空間:0x04000000-0x041fffff
- *.........
- *進程23:物理地址空間0x31700000-0x317fffff
- *MVA地址空間:0x2E000000-0x2E1fffff
- *對應進程24由于MVA地址空間是0x30000000是物理內(nèi)存起始空間,該空間用來放置頁表,并且前面已經(jīng)用該
- *地址空間做了映射,因此它不能被映射成,24號進程的物理地址空間,跳過該進程號24,同樣道理,
- *跳過進程號25
- *進程24:物理地址空間0x31800000-0x318fffff
- *MVA地址空間:0x30000000-0x31ffffff
- *進程25:物理地址空間0x31900000-0x319fffff
- *MVA地址空間:0x32000000-0x33ffffff
- */
- for(entry_index=1;entry_index<24;entry_index++){
- *(mmu_tlb_base+((entry_index*0x02000000)>>20))=(entry_index*0x00100000+SDRAM_BASE)|SEC_DESC;
- }
- /*
- *進程26:物理地址空間0x31A00000-0x31Afffff
- *MVA地址空間:0x34000000-0x35ffffff
- *.........
- *進程62:物理地址空間0x33E00000-0x33Efffff
- *MVA地址空間:0xC4000000-0xC5ffffff
- */
- for(entry_index=26;entry_index
- *(mmu_tlb_base+((entry_index*0x02000000)>>20))=(entry_index*0x00100000+SDRAM_BASE)|SEC_DESC;
- }
- /*
- *異常向量表
- *0xFFFF0000為高地址異常向量表,可以通常設置CP15,C1寄存器V位,當異常產(chǎn)生時,由硬件自動去0xFFFF0000
- *地址處執(zhí)行異常跳轉(zhuǎn)執(zhí)行,而不是之前的0地址處異常向量表跳轉(zhuǎn),我們將該虛擬地址映射到0x33F00000這1MB地址
- *空間,同樣,將全部miniOS代碼拷貝到這1MB地址空間來。
- */
- *(mmu_tlb_base+(0xffff0000>>20))=((VECTORS_PHY_BASE)|SEC_DESC);
- }
完成之后,虛擬地址映射如下:
訪問0x33FF0000~0x33FFFFFF 與 0xFFF00000~0xFFFFFFFF地址是同一塊物理內(nèi)存空間。
0xA0000000~0xA01FFFFF地址指向0x00000000~0x001FFFFF,NorFlash物理空間。
2.3系統(tǒng)是怎么開啟MMU的,為什么開啟了MMU內(nèi)存地址重映射之后程序還能正常運行?
在開啟MMU之前,數(shù)據(jù)訪問是直接訪問物理地址。但是開啟了MMU后,所有的地址訪問都需要通過一次虛擬地址轉(zhuǎn)換。同樣一個地址并不一定提向的同一個數(shù)據(jù)內(nèi)間。
那在mmu_init()函數(shù)開啟MMU之后出現(xiàn)什么樣的反應呢?
- voidmmu_init()
- {
- unsignedlongttb=MMU_TABLE_BASE;
- /*reg1待清除位*/
- intreg0,reg1=(VECTOR|ICACHE|R_S_BIT|ENDIAN|DCACHE|ALIGN|MMU_ON);
- /*CP15,C1設置位:異常向量表設置在高地址,使用ICACHE,系統(tǒng)采用小端模式,
- 使用DCACHE,使用地址對齊檢查,開啟MMU*/
- intCP15_C1_set=(VECTOR|ICACHE|DCACHE|ALIGN|MMU_ON);
- __asm{
- movreg0,#0
- /*使ICaches和DCaches無效*/
- mcrp15,0,reg0,c7,c7,0
- /*使能寫入緩沖器*/
- mcrp15,0,reg0,c7,c10,4
- /*使指令,數(shù)據(jù)TLB無效無效*/
- mcrp15,0,reg0,c8,c7,0
- /*頁表基址寫入C2*/
- mcrp15,0,ttb,c2,c0,0
- /*將0x2取反變成0xFFFFFFFD,Domain0=0b01為用戶模式,其它域為0b11管理模式*/
- mvnreg0,#0x2
- /*寫入域控制信息*/
- mcrp15,0,reg0,c3,c0,0
- /*取出C1寄存器中值給reg0*/
- mrcp15,0,reg0,c1,c0,0
- /*先清除不需要的功能,現(xiàn)開啟*/
- bicreg0,reg0,reg1
- /*設置相關位并開啟MMU*/
- orrreg0,reg0,CP15_C1_set
- mcrp15,0,reg0,c1,c0,0
- }
- //DPRINTK(KERNEL_DEBUG,"MmuinitOK");
- }
剛開始,我在看上面代碼的時候,我在想。這個一開啟MMU之后,這個函數(shù)還能正常返回嗎?原來MMU在啟時前保存的返回地址(物理地址),在MMU開啟后這個地址(虛擬地址)對應的還是原來的物理地址嗎?除非一種情況: 虛擬地址與物理地址一致。
上述代碼為初始化MMU的函數(shù),當在執(zhí)行完”mcr p15, 0, reg0, c1, c0, 0“ 指令之后,MMU被開啟了。所有的地址訪問都要經(jīng)過MMU轉(zhuǎn)換成物理地址才能訪問。而mmu_init()此時運行在SDRAM中0x33FF0000地址域上。由2.2節(jié)圖中所示,0x30000000~0x33FFFFFF地址空間上的虛擬地址與物理地址是對應的。也就是說,虛擬地址==物理地址。
所以,程序能夠正常執(zhí)行。
2.4main( ) 函數(shù)是怎么變成task0的?
OSCreateProcess()函數(shù)所創(chuàng)建任務的ID號從1開始計數(shù)。至于任務0,就是xmain()函數(shù)自己。
xmain()自己怎么跑到task0的位置上去坐著的呢?看main.c代碼:
- intxmain(void)
- {
- //PC=0x33FF????,SP=0x33FF0000,MMU=關
- pgtb_init();//建立頁表
- mmu_init();//mmu初始化
- //PC=0x33FF????,SP=0x33FF0000,MMU=開
- //對UART、IRQ、TIMER0、LED、KEY進行初始化
- OS_ENTER_CRITICAL();//關閉中斷,準備進入進程初始化函數(shù)
- sched_init();//進程調(diào)度初始化
- OS_EXIT_CRITICAL();//開啟中斷
- ENTER_USR_MODE();//進入用戶模式
- //進程0執(zhí)行內(nèi)容
- while(1){
- DPRINTK(KERNEL_DEBUG,"kernel:process0");
- printk("process0,idle");
- wait(1000000);
- }
- return0;
- }
在執(zhí)行完 mmu_init 函數(shù)之后,所有的數(shù)據(jù)訪問均是通過虛擬地址訪問的。包括接下來的UART、IRQ、TIMER0、LED、KEY的初始化,通是訪問的虛擬地址。詳見uart_init 函數(shù)中,讀寫的寄存器地址。
sched_init() 函數(shù)的功能是初始化所有的PCB。在最后,初始化PCB[0]。把 current=&task[0] 。
- /*初始化0號進程*/
- p=&task[0];//p指向0號進程PCB
- p->pid=0;//設置0號進程pid
- p->state=TASK_RUNNING;//設置其運行狀態(tài)為就緒態(tài)
- p->count=5;//設置其時間片為5
- p->priority=5;//設置優(yōu)先級為5
- p->content[0]=0x5f;//保存狀態(tài)寄存器cpsr值,表示為系統(tǒng)模式,開啟中斷
- p->content[1]=SYS_MODE_STACK_BASE;//設置當前進程棧指針
- p->content[2]=0;
- p->content[16]=0;//設置PC寄存器的值為0,該進程起始地址被MMU映射為0地址
- current=&task[0];//當前運行進程為0號進程
評論