1. 概述
最近,我花了很多的时刻学习了杨铸教师写的《浅显易懂嵌入式底层软件开发》,看完了ARM体系结构与编程这一章。在这章节的最终,作者做了一个用于总结前面所学内容的操作体系MiniOS,并附带了其间的源代码。我认真学习了其间的一切代码,悟到了其间十分奇妙的构思。
读这个MiniOS源代码我遇到了最大的几个问题如下:
(1)体系是怎样发动的?
(2)舱位了MMU后,虚拟地址是怎样映射上物理地址上的?
(3)体系是怎样舱位MMU的,为什么舱位了MMU内存地址重映射之后程序还能正常运转?
(4)main( ) 函数是怎样变成task0的?
(5)使命之间是怎样切换的?
(6)使命中怎样被创立,并运转起来的?
上述这几个问题都是很纤细,但又很难搞清楚的中心常识。笔者在此把自己悟到的东西共享出来,供咱们参阅。
其它,如:体系函数调用、使命调度机制、LED、UART、按键怎样完结,不做过多研讨。
2. 具体内容
2.1 体系是怎样发动的?
- AREAStart,CODE,READONLY
- ENTRY;代码段开端
- bReset
- ……
- Reset;Reset反常处理符号
- blclock_init;跳往时钟初始化处理
- blmem_init;跳往内存初始化处理
- ldrsp,=SVC_STACK;设置办理形式栈指针,common_asm.h中界说
- bldisable_watch_dog;封闭看门狗
- copy_code;代码劳动开端符号
- movr0,#0x0;R0中为数据开端地址(ROM数据保存在0地址开端处)
- ldrr1,=|Image
RO Base|;R1中寄存RO输出域运转地址,
- ldrr2,=|Image
ZI Limit|;R2中寄存ZI输出域完毕地址,
- subr2,r2,r1;R2=R2-R1,得出待劳动数据长度
- blCopyCode2Ram;将R0,R1,R2三个参数传递给CopyCode2Ram函数碑文劳动
- ldrr0,=|Image
ZI Base|
- ldrr1,=|Image
ZI Limit|
- blclear_bss_region
- blstack_init;跳往栈初始化代码处
- msrcpsr_c,#0x5f;舱位体系中止,进入体系形式
- ldrlr,=halt_loop;设置回来地址
- ldrpc,=xmain;跳往main函数,进入OS发动处理
- halt_loop
- bhalt_loop;死循环
在碑文了”ldr pc, =xmain“这条指令之后,PC就指向了SDRAM的0x33FF0000地址区域上了,不再是NorFlash上了,从此达到了运转地址与加载地址的共同。谨记!
- intxmain(void)
- {
- pgtb_init();//树立页表
- mmu_init();//mmu初始化
- uart_init();//串口初始化
- irq_init();//中止初始化
- Timer0_init();//定时器0初始化
- key_init();//按键初始化
- led_init();//led灯初始化
- }
2.2 舱位了MMU后,虚拟地址是怎样映射上物理地址上的?
在xmain函数中,pgtb_init() 函数的功用便是构建页表,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内部进程办理*/
- 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是物理内存开端空间,该空间用来放置页表,并且前面现已用该
- *地址空间做了映射,因而它不能被映射成,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位,当反常发生时,由硬件主动去0xFFFF0000
- *地址处碑文反常跳转碑文,而不是之前的0地址处反常向量表跳转,咱们将该虚拟地址映射到0x33F00000这1MB地址
- *空间,相同,将悉数miniOS代码劳动到这1MB地址空间来。
- */
- *(mmu_tlb_base+(0xffff0000>>20))=((VECTORS_PHY_BASE)|SEC_DESC);
- }
2.3体系是怎样舱位MMU的,为什么舱位了MMU内存地址重映射之后程序还能正常运转?
- voidmmu_init()
- {
- unsignedlongttb=MMU_TABLE_BASE;
- /*reg1待铲除位*/
- intreg0,reg1=(VECTOR|ICACHE|R_S_BIT|ENDIAN|DCACHE|ALIGN|MMU_ON);
- /*CP15,C1设置位:反常向量表设置在高地址,运用ICACHE,体系选用小端形式,
- 运用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
- /*使指令,数据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
- /*先铲除不需求的功用,现舱位*/
- bicreg0,reg0,reg1
- /*设置相关位并舱位MMU*/
- orrreg0,reg0,CP15_C1_set
- mcrp15,0,reg0,c1,c0,0
- }
- //DPRINTK(KERNEL_DEBUG,”MmuinitOK”);
- }
2.4main( ) 函数是怎样变成task0的?
- 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();//封闭中止,预备进入进程初始化函数
- sched_init();//进程调度初始化
- OS_EXIT_CRIT%&&&&&%AL();//舱位中止
- ENTER_USR_MODE();//进入用户形式
- //进程0碑文内容
- while(1){
- DPRINTK(KERNEL_DEBUG,”kernel:process0″);
- printk(“process0,idle”);
- wait(1000000);
- }
- return0;
- }
碑文到 xmain 函数时,PC地址是在 SDRAM 的 0x33FF???? 上的,并且SP栈指针在 start.s 中已指定向了 0x33FF0000。
- /*初始化0号进程*/
- p=&task[0];//p指向0号进程PCB
- p->pid=0;//设置0号进程pid
- p->state=TASK_RUNNING;//设置其运转状况为安排妥当态
- p->count=5;//设置其时刻片为5
- p->priority=5;//设置优先级为5
- p->content[0]=0x5f;//保存状况寄存器cpsr值,一共为体系形式,舱位中止
- p->content[1]=SYS_MODE_STACK_BASE;//设置当时进程栈指针
- p->content[2]=0;
- p->content[16]=0;//设置PC寄存器的值为0,该进程开端地址被MMU映射为0地址
- current=&task[0];//当时运转进程为0号进程