新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > ARM學(xué)習(xí)筆記之——MiniOS

ARM學(xué)習(xí)筆記之——MiniOS

作者: 時(shí)間:2016-11-20 來(lái)源:網(wǎng)絡(luò) 收藏

1. 概述

最近,我花了大量的時(shí)間學(xué)習(xí)了楊鑄老師寫的《深入淺出嵌入式底層軟件開(kāi)發(fā)》,看完了ARM體系結(jié)構(gòu)與編程這一章。在這章節(jié)的最后,作者做了一個(gè)用于總結(jié)前面所學(xué)內(nèi)容的操作系統(tǒng)MiniOS,并附帶了其中的源代碼。我認(rèn)真學(xué)習(xí)了其中的所有代碼,悟到了其中非常巧妙的構(gòu)思。

本文引用地址:http://m.butianyuan.cn/article/201611/318808.htm

讀這個(gè)MiniOS源代碼我遇到了最大的幾個(gè)問(wèn)題如下:

(1)系統(tǒng)是怎么啟動(dòng)的?

(2)開(kāi)啟了MMU后,虛擬地址是怎么映射上物理地址上的?

(3)系統(tǒng)是怎么開(kāi)啟MMU的,為什么開(kāi)啟了MMU內(nèi)存地址重映射之后程序還能正常運(yùn)行?

(4)main( ) 函數(shù)是怎么變成task0的?

(5)任務(wù)之間是怎么切換的?

(6)任務(wù)中怎么被創(chuàng)建,并運(yùn)行起來(lái)的?

上述這幾個(gè)問(wèn)題都是很細(xì)微,但又很難搞清楚的核心知識(shí)。筆者在此把自己悟到的東西分享出來(lái),供大家參考。

其它,如:系統(tǒng)函數(shù)調(diào)用、任務(wù)調(diào)度機(jī)制、LED、UART、按鍵怎么實(shí)現(xiàn),不做過(guò)多研究。

2. 詳細(xì)內(nèi)容

2.1 系統(tǒng)是怎么啟動(dòng)的?

首先說(shuō)明,書(shū)上提供的MiniOS工程編譯后的運(yùn)行地址為0x33FF0000,不是 0x00000000,這點(diǎn)很重要。
-info totals -ro-base 0x33ff0000 -first start.o
而程序編譯完成后,生成了bin文件將被燒錄到NorFlash的0x00000000地址上,也就很重要!
ARM復(fù)位后,PC從NorFlash的0x00000000地址上取提,也就是”b Reset“,之后跳到Reset標(biāo)號(hào)上繼續(xù)執(zhí)行。代碼如下:
  1. AREAStart,CODE,READONLY
  2. ENTRY;代碼段開(kāi)始
  3. bReset
  4. ……
  5. Reset;Reset異常處理符號(hào)
  6. blclock_init;跳往時(shí)鐘初始化處理
  7. blmem_init;跳往內(nèi)存初始化處理
  8. ldrsp,=SVC_STACK;設(shè)置管理模式棧指針,common_asm.h中定義
  9. bldisable_watch_dog;關(guān)閉看門狗
之后所有的跳轉(zhuǎn)都是用到b或bl,進(jìn)行相對(duì)跳轉(zhuǎn)。再跳轉(zhuǎn)也是以PC為起始,相對(duì)位置跳轉(zhuǎn),不會(huì)受運(yùn)行地址的影響。
初始化了時(shí)鐘、SDRAM、關(guān)閉看門狗、設(shè)置sp。有人可能會(huì)問(wèn):為什么在進(jìn)行了bl之后再設(shè)置棧指針?其實(shí),哪里設(shè)置都無(wú)所謂,因?yàn)閎l指令返回地址只保存在LR寄存器中,不放在棧里。SP被設(shè)置成了0x33FF0000,向下擴(kuò)展,將來(lái)還會(huì)提及。
然后初始化SDRAM(如果不初始化,SDRAM是不能使用的),將程序自己從0x00000000地址復(fù)制一份到0x33FF0000地址上。然后再來(lái)一個(gè)絕對(duì)地址跳轉(zhuǎn),轉(zhuǎn)到0x33FF0000地址域上的xmain地址處繼續(xù)執(zhí)行。如下:
  1. copy_code;代碼拷貝開(kāi)始符號(hào)
  2. movr0,#0x0;R0中為數(shù)據(jù)開(kāi)始地址(ROM數(shù)據(jù)保存在0地址開(kāi)始處)
  3. ldrr1,=|Image
    RO
    Base|;R1中存放RO輸出域運(yùn)行地址,
  4. ldrr2,=|Image
    ZI
    Limit|;R2中存放ZI輸出域結(jié)束地址,
  5. subr2,r2,r1;R2=R2-R1,得出待拷貝數(shù)據(jù)長(zhǎng)度
  6. blCopyCode2Ram;將R0,R1,R2三個(gè)參數(shù)傳遞給CopyCode2Ram函數(shù)執(zhí)行拷貝
  7. ldrr0,=|Image
    ZI
    Base|
  8. ldrr1,=|Image
    ZI
    Limit|
  9. blclear_bss_region
  10. blstack_init;跳往棧初始化代碼處
  11. msrcpsr_c,#0x5f;開(kāi)啟系統(tǒng)中斷,進(jìn)入系統(tǒng)模式
  12. ldrlr,=halt_loop;設(shè)置返回地址
  13. ldrpc,=xmain;跳往main函數(shù),進(jìn)入OS啟動(dòng)處理
  14. halt_loop
  15. bhalt_loop;死循環(huán)
在執(zhí)行了”ldr pc, =xmain“這條指令之后,PC就指向了SDRAM的0x33FF0000地址區(qū)域上了,不再是NorFlash上了,從此達(dá)到了運(yùn)行地址與加載地址的統(tǒng)一。謹(jǐn)記!
xmain()函數(shù)定議在main.c文件中。
  1. intxmain(void)
  2. {
  3. pgtb_init();//建立頁(yè)表
  4. mmu_init();//mmu初始化
  5. uart_init();//串口初始化
  6. irq_init();//中斷初始化
  7. Timer0_init();//定時(shí)器0初始化
  8. key_init();//按鍵初始化
  9. led_init();//led燈初始化
  10. }

2.2 開(kāi)啟了MMU后,虛擬地址是怎么映射上物理地址上的?

在xmain函數(shù)中,pgtb_init() 函數(shù)的功能就是構(gòu)建頁(yè)表,TTB=0x300F0000。
  1. voidpgtb_init()
  2. {
  3. unsignedlongentry_index,SFR_base;
  4. /*建立到Norflash的2MB的地址空間的映射*/
  5. /*0xA0000000映射到0開(kāi)始的1MB地址空間*/
  6. *(mmu_tlb_base+(0xA0000000>>20))=0x0|SEC_DESC;
  7. /*0xA0100000映射到0x100000~0x1FFFFF的1MB地址空間*/
  8. *(mmu_tlb_base+(0xA0100000>>20))=0x100000|SEC_DESC;
  9. /*令0x30000000~0x34000000的64MB虛擬地址等于物理地址空間,方便miniOS內(nèi)部進(jìn)程管理*/
  10. for(entry_index=0x30000000;entry_index<0x34000000;entry_index+=0x100000){
  11. *(mmu_tlb_base+(entry_index>>20))=entry_index|SEC_DESC;
  12. }
  13. /*特殊功能寄存器0x48000000~0x60000000地址空間映射到0xC8000000~0xE0000000虛擬地址空間*/
  14. for(entry_index=0x48000000+0x80000000,SFR_base=0x48000000;
  15. SFR_base<0x60000000;entry_index+=0x100000,SFR_base+=0x100000){
  16. *(mmu_tlb_base+(entry_index>>20))=SFR_base|SEC_DESC;
  17. }
  18. /*
  19. *進(jìn)程1-23號(hào)進(jìn)程地址空間,每個(gè)進(jìn)程32MB,miniOS允許進(jìn)程使用32MB虛擬地址空間,但是只分配其1MB的實(shí)際物理空間
  20. *進(jìn)程1:物理地址空間0x30100000-0x301fffff,對(duì)應(yīng)MVA(修正虛擬地址,進(jìn)程PID<<25形成)
  21. *MVA地址空間:0x02000000-0x021fffff
  22. *進(jìn)程2:物理地址空間0x30200000-0x302fffff
  23. *MVA地址空間:0x04000000-0x041fffff
  24. *.........
  25. *進(jìn)程23:物理地址空間0x31700000-0x317fffff
  26. *MVA地址空間:0x2E000000-0x2E1fffff
  27. *對(duì)應(yīng)進(jìn)程24由于MVA地址空間是0x30000000是物理內(nèi)存起始空間,該空間用來(lái)放置頁(yè)表,并且前面已經(jīng)用該
  28. *地址空間做了映射,因此它不能被映射成,24號(hào)進(jìn)程的物理地址空間,跳過(guò)該進(jìn)程號(hào)24,同樣道理,
  29. *跳過(guò)進(jìn)程號(hào)25
  30. *進(jìn)程24:物理地址空間0x31800000-0x318fffff
  31. *MVA地址空間:0x30000000-0x31ffffff
  32. *進(jìn)程25:物理地址空間0x31900000-0x319fffff
  33. *MVA地址空間:0x32000000-0x33ffffff
  34. */
  35. for(entry_index=1;entry_index<24;entry_index++){
  36. *(mmu_tlb_base+((entry_index*0x02000000)>>20))=(entry_index*0x00100000+SDRAM_BASE)|SEC_DESC;
  37. }
  38. /*
  39. *進(jìn)程26:物理地址空間0x31A00000-0x31Afffff
  40. *MVA地址空間:0x34000000-0x35ffffff
  41. *.........
  42. *進(jìn)程62:物理地址空間0x33E00000-0x33Efffff
  43. *MVA地址空間:0xC4000000-0xC5ffffff
  44. */
  45. for(entry_index=26;entry_index
  46. *(mmu_tlb_base+((entry_index*0x02000000)>>20))=(entry_index*0x00100000+SDRAM_BASE)|SEC_DESC;
  47. }
  48. /*
  49. *異常向量表
  50. *0xFFFF0000為高地址異常向量表,可以通常設(shè)置CP15,C1寄存器V位,當(dāng)異常產(chǎn)生時(shí),由硬件自動(dòng)去0xFFFF0000
  51. *地址處執(zhí)行異常跳轉(zhuǎn)執(zhí)行,而不是之前的0地址處異常向量表跳轉(zhuǎn),我們將該虛擬地址映射到0x33F00000這1MB地址
  52. *空間,同樣,將全部miniOS代碼拷貝到這1MB地址空間來(lái)。
  53. */
  54. *(mmu_tlb_base+(0xffff0000>>20))=((VECTORS_PHY_BASE)|SEC_DESC);
  55. }

完成之后,虛擬地址映射如下:
訪問(wèn)0x33FF0000~0x33FFFFFF 與 0xFFF00000~0xFFFFFFFF地址是同一塊物理內(nèi)存空間。
0xA0000000~0xA01FFFFF地址指向0x00000000~0x001FFFFF,NorFlash物理空間。

2.3系統(tǒng)是怎么開(kāi)啟MMU的,為什么開(kāi)啟了MMU內(nèi)存地址重映射之后程序還能正常運(yùn)行?

在開(kāi)啟MMU之前,數(shù)據(jù)訪問(wèn)是直接訪問(wèn)物理地址。但是開(kāi)啟了MMU后,所有的地址訪問(wèn)都需要通過(guò)一次虛擬地址轉(zhuǎn)換。同樣一個(gè)地址并不一定提向的同一個(gè)數(shù)據(jù)內(nèi)間。
那在mmu_init()函數(shù)開(kāi)啟MMU之后出現(xiàn)什么樣的反應(yīng)呢?
  1. voidmmu_init()
  2. {
  3. unsignedlongttb=MMU_TABLE_BASE;
  4. /*reg1待清除位*/
  5. intreg0,reg1=(VECTOR|ICACHE|R_S_BIT|ENDIAN|DCACHE|ALIGN|MMU_ON);
  6. /*CP15,C1設(shè)置位:異常向量表設(shè)置在高地址,使用ICACHE,系統(tǒng)采用小端模式,
  7. 使用DCACHE,使用地址對(duì)齊檢查,開(kāi)啟MMU*/
  8. intCP15_C1_set=(VECTOR|ICACHE|DCACHE|ALIGN|MMU_ON);
  9. __asm{
  10. movreg0,#0
  11. /*使ICaches和DCaches無(wú)效*/
  12. mcrp15,0,reg0,c7,c7,0
  13. /*使能寫入緩沖器*/
  14. mcrp15,0,reg0,c7,c10,4
  15. /*使指令,數(shù)據(jù)TLB無(wú)效無(wú)效*/
  16. mcrp15,0,reg0,c8,c7,0
  17. /*頁(yè)表基址寫入C2*/
  18. mcrp15,0,ttb,c2,c0,0
  19. /*將0x2取反變成0xFFFFFFFD,Domain0=0b01為用戶模式,其它域?yàn)?b11管理模式*/
  20. mvnreg0,#0x2
  21. /*寫入域控制信息*/
  22. mcrp15,0,reg0,c3,c0,0
  23. /*取出C1寄存器中值給reg0*/
  24. mrcp15,0,reg0,c1,c0,0
  25. /*先清除不需要的功能,現(xiàn)開(kāi)啟*/
  26. bicreg0,reg0,reg1
  27. /*設(shè)置相關(guān)位并開(kāi)啟MMU*/
  28. orrreg0,reg0,CP15_C1_set
  29. mcrp15,0,reg0,c1,c0,0
  30. }
  31. //DPRINTK(KERNEL_DEBUG,"MmuinitOK");
  32. }
剛開(kāi)始,我在看上面代碼的時(shí)候,我在想。這個(gè)一開(kāi)啟MMU之后,這個(gè)函數(shù)還能正常返回嗎?原來(lái)MMU在啟時(shí)前保存的返回地址(物理地址),在MMU開(kāi)啟后這個(gè)地址(虛擬地址)對(duì)應(yīng)的還是原來(lái)的物理地址嗎?除非一種情況: 虛擬地址與物理地址一致。
上述代碼為初始化MMU的函數(shù),當(dāng)在執(zhí)行完”mcr p15, 0, reg0, c1, c0, 0“ 指令之后,MMU被開(kāi)啟了。所有的地址訪問(wèn)都要經(jīng)過(guò)MMU轉(zhuǎn)換成物理地址才能訪問(wèn)。而mmu_init()此時(shí)運(yùn)行在SDRAM中0x33FF0000地址域上。由2.2節(jié)圖中所示,0x30000000~0x33FFFFFF地址空間上的虛擬地址與物理地址是對(duì)應(yīng)的。也就是說(shuō),虛擬地址==物理地址。
所以,程序能夠正常執(zhí)行。

2.4main( ) 函數(shù)是怎么變成task0的?

OSCreateProcess()函數(shù)所創(chuàng)建任務(wù)的ID號(hào)從1開(kāi)始計(jì)數(shù)。至于任務(wù)0,就是xmain()函數(shù)自己。
xmain()自己怎么跑到task0的位置上去坐著的呢?看main.c代碼:
  1. intxmain(void)
  2. {
  3. //PC=0x33FF????,SP=0x33FF0000,MMU=關(guān)
  4. pgtb_init();//建立頁(yè)表
  5. mmu_init();//mmu初始化
  6. //PC=0x33FF????,SP=0x33FF0000,MMU=開(kāi)
  7. //對(duì)UART、IRQ、TIMER0、LED、KEY進(jìn)行初始化
  8. OS_ENTER_CRITICAL();//關(guān)閉中斷,準(zhǔn)備進(jìn)入進(jìn)程初始化函數(shù)
  9. sched_init();//進(jìn)程調(diào)度初始化
  10. OS_EXIT_CRITICAL();//開(kāi)啟中斷
  11. ENTER_USR_MODE();//進(jìn)入用戶模式
  12. //進(jìn)程0執(zhí)行內(nèi)容
  13. while(1){
  14. DPRINTK(KERNEL_DEBUG,"kernel:process0");
  15. printk("process0,idle");
  16. wait(1000000);
  17. }
  18. return0;
  19. }
執(zhí)行到 xmain 函數(shù)時(shí),PC地址是在 SDRAM 的 0x33FF???? 上的,而且SP棧指針在 start.s 中已指定向了 0x33FF0000。
在執(zhí)行完 mmu_init 函數(shù)之后,所有的數(shù)據(jù)訪問(wèn)均是通過(guò)虛擬地址訪問(wèn)的。包括接下來(lái)的UART、IRQ、TIMER0、LED、KEY的初始化,通是訪問(wèn)的虛擬地址。詳見(jiàn)uart_init 函數(shù)中,讀寫的寄存器地址。
sched_init() 函數(shù)的功能是初始化所有的PCB。在最后,初始化PCB[0]。把 current=&task[0] 。
  1. /*初始化0號(hào)進(jìn)程*/
  2. p=&task[0];//p指向0號(hào)進(jìn)程PCB
  3. p->pid=0;//設(shè)置0號(hào)進(jìn)程pid
  4. p->state=TASK_RUNNING;//設(shè)置其運(yùn)行狀態(tài)為就緒態(tài)
  5. p->count=5;//設(shè)置其時(shí)間片為5
  6. p->priority=5;//設(shè)置優(yōu)先級(jí)為5
  7. p->content[0]=0x5f;//保存狀態(tài)寄存器cpsr值,表示為系統(tǒng)模式,開(kāi)啟中斷
  8. p->content[1]=SYS_MODE_STACK_BASE;//設(shè)置當(dāng)前進(jìn)程棧指針
  9. p->content[2]=0;
  10. p->content[16]=0;//設(shè)置PC寄存器的值為0,該進(jìn)程起始地址被MMU映射為0地址
  11. current=&task[0];//當(dāng)前運(yùn)行進(jìn)程為0號(hào)進(jìn)程



關(guān)鍵詞: ARMMiniO

評(píng)論


技術(shù)專區(qū)

關(guān)閉