写在前面的:
本人所著《深入浅出嵌入式底层软件开发》书中裸机部分实验为一个小型的多任务操作系统,由底层到上层应用程序全部知识面均涉及到,不过,当时由于交稿所催,很草率的实现了其功能,最近一直也在忙于其它不相关事情,所以没有将其完善。这几天,给几个大学里的学生做实训,拿出来了这个做Topic,用了两天时间接合书籍技术讨论群里对该OS代码的建议做了下功能的改善和更新,希望读者们或意图从事ARM相关嵌入式开发的朋友们能继续多多关注与支持,谢谢~
本实验的意义:
学过Linux的朋友们应该都知道Linux的发展史,Linux操作系统是Linus本人借鉴了Andrew S. Tanenbaum所写的MINIX操作系统,然后加入了具有前瞻性的开源思想,借助于网络的兴起而著名的网络操作系统。
其背景如下:
当时欧洲和美国大学计算机课程中,关于操作系统的课程是一门全新的课程,而操作系统在当时数量很少,只有在大学里开源的Unix比较普及,而Unix操作系统是一个大工程学科,不太适合于教学,因此,两会会员Andrew S. Tanenbaum自己写了一个小型的MINIX操作系统,用于其就职的大学里教学,虽然是Andrew 这哥们很出名,但是它也可能是年纪大了,仅将其用于教学中,没有将其商业应用和推广。而这时大学生Linus在看了MINIX全部源码后,感觉很多地方需要进行改进,于是,自己根据MINIX,写下了Linux操作系统,当然后面的事情就不在多说了。
通过上面的背景,我想表达的:
反观国内,由于本人一边从事IT行业,一边从事教育工作,所以,对国内大学还是比较了解的。Android系统从2008年上市到现在已经过去了快4年了,去年年底即2010年,国内的大学里才开始打算开这门课,申请新专业要一年时间,从新生招生到大四毕业要4年,也就是说,最早一批Android专业学生毕业应该是在2015年,而当前Android应用开发人员,市场上几近饱和(指应用层),国内的教育是走在企业的后面的,这种机构出来的学生,能跟上企业的需要吗???
2. 欧美等发达国家,大学里学习C语言,学习操作系统是用的Linux或Unix。
反观国内,清一色的选用Windows平台,我不是说Windows平台不好,而是整个中华人民共和国被Microsoft养懒了,做软件,拖拖拽拽,不知道数据结构的应用场合?不了解应用程序的工作机制?不知道编译是怎么回事?更不知道文件系统和内核有什么关系?
我们大学里开了很多计算机课程,大学毕业了,第一感觉是,大学里学的东西太多,根本就联系不起来?这其实就是教育方式的问题,我不敢说,我的这种说法是对的,但是,经过实验证明,我的做法让很多朋友更加明确了:计算机系统是什么?大学里的操作系统和大学里的数据结构及计算机组成原理和上层语言有什么关系。
我不敢说这个实验能让多少人会喜欢上计算机、更了解计算机、能成为计算机编程高手和老鸟,但是我可以告诉你一些你看书学不到的东西。
miniOS V2.0
版本改进:
2010.05: 为了将《深入浅出嵌入式底层软件开发》这本书里前面的裸机驱动部分全部串起来,借鉴Linux操作系统和网上前辈们的总结,写了一个小型多任务操作系统,主要有以下功能和特点:
2012.03.08:
miniOS V2.0内存分布图:
看过V1.0代码的朋友会记得:原先的物理内存地址有2M空间是未使用的,在这次的版本上,物理内存空间全部用上了。
Linux内核中内核地址空间分为:
miniOS的0x80000000~0x84000000相当于直接内存映射区,可以通过线性减一个偏移地址来管理物理内存0x30000000~0x34000000
同样外设寄存器空间0x48000000~0x60000000也是直接内存映射区,可以直接来访问。
Norflash的2MB空间被映射到了更高的地址处:0xC0000000~0xC0200000
物理内存空间结构:
由上图可知:V2.0版本,页表放到了内存地址0x30000000处
最开始的1MB物理内存地址0x30000000~0x30100000为0号内核进程地址空间,其实就是OS代码区和内核栈区及页表区。
由于OS代码放到了物理内存0x300F0000处,其被映射到虚拟地址0x800F0000处,所以ADS里设置的OS代码的运行地址为0x800F0000。
重要变化:
原先的mmu.c分成了两个文件:page_table.c和mmu.S
page_talbe.c
#include "s3c2440.h" #include "serial.h" #include "mmu.h" #define MANAGER_AP (0x3<<10) #define USER_AP (0x1<<10) #define MANAGER_DOMAIN (0x1<<5) #define USER_DOMAIN (0x0<<5) #define FAULT_DOMAIN (0x0<<5) #define CB (0x1<<2) #define DESC_FLAG (0x2<<0) #define FAULT_DESC (0x0<<0) #define SEC_DESC_WB (MANAGER_AP | MANAGER_DOMAIN | CB | DESC_FLAG) #define NONE_SEC_DESC (FAULT_DESC) #define TTB_BASE ((unsigned long *)SDRAM_BASE) #define VERTOR_NEW_BASE (SDRAM_BASE) /**************************************************************************** * 页表建立函数 *____________________________________________________________________________ * 段页表项entry:[31:20]段基址,[11:10]为AP(控制访问权限),[8:5]域, * [3:2]=CB(decide cached &buffered),[1:0]=0b10-->页表项为段描述符 * * 0~32MB:为内核进程空间 * 32MB~0x80000000:为用户进程空间 * 0x80000000~0x84000000:为物理内存完全映射区 * 0x98000000~0xB0000000:为外设寄存器完全映射区间 *____________________________________________________________________________ ****************************************************************************/ void __create_page_tables_early() { unsigned long phyaddr, viraddr; /* 建立到Norflash的2MB的地址空间的映射 */ /* 0x90000000 映射到0开始的1MB地址空间 */ *( TTB_BASE + (NOR_1M_VM >> 20) ) = 0x0 | SEC_DESC_WB; /* 0x90100000 映射到0x100000~0x1FFFFF的1MB地址空间 */ *( TTB_BASE + (NOR_2M_VM >> 20) ) = MB | SEC_DESC_WB; /* 令0x30000000~0x34000000的64MB虚拟地址等于物理地址空间,方便miniOS内部进程管理 */ for(phyaddr = SDRAM_BASE; phyaddr < SDRAM_BASE + SDRAM_SIZE; phyaddr += MB) { *(TTB_BASE + (phyaddr >> 20) ) = phyaddr | SEC_DESC_WB; } /* 令0x80000000~0x84000000的64MB虚拟地址映射到0x30000000~0x34000000 */ for(phyaddr = SDRAM_BASE, viraddr = KERNEL_SPACE_BASE; phyaddr < SDRAM_BASE + SDRAM_SIZE; phyaddr += MB, viraddr+= MB) { *(TTB_BASE + (viraddr >> 20) ) = phyaddr | SEC_DESC_WB; } /* 令0x48000000~0x60000000的虚拟地址等于物理地址空间,方便miniOS内部外设管理 */ for(phyaddr = SFR_PA_BASE; phyaddr < SFR_PA_LIMIT; phyaddr += MB) { *(TTB_BASE + (phyaddr >> 20) ) = phyaddr | SEC_DESC_WB; } /* 令0x98000000~0xB0000000的虚拟地址映射到0x48000000~0x60000000 */ for(phyaddr = SFR_PA_BASE, viraddr = phyaddr + VA_TO_PA_OFT; phyaddr < SFR_PA_LIMIT; phyaddr += MB, viraddr+= MB) { *(TTB_BASE + (viraddr >> 20) ) = phyaddr | SEC_DESC_WB; } /* * 异常向量表 * 0xFFFF0000为高地址异常向量表,可以通常设置CP15,C1寄存器V位,当异常产生时,由硬件自动去0xFFFF0000 * 地址处执行异常跳转执行,而不是之前的0地址处异常向量表跳转,我们将该虚拟地址映射到0x33F00000这1MB地址 * 空间,同样,将全部miniOS代码拷贝到这1MB地址空间来。 */ *(TTB_BASE + (0xffff0000>>20)) = (VERTOR_NEW_BASE | SEC_DESC_WB); } void IDCaches_Restart(void) { __asm{ mov r0, #0 /* 使ICaches和DCaches无效 */ mcr p15, 0, r0, c7, c7, 0 /* 使能写入缓冲器 */ mcr p15, 0, r0, c7, c10, 4 /* 使指令,数据TLB无效 */ mcr p15, 0, r0, c8, c7, 0 nop nop nop nop } } void __create_page_tables_post(void) { unsigned long taskId = 1; unsigned long phyaddr; // 切断原先的0x30000000~0x34000000的映射关系,使其为无效描述符 for(phyaddr = SDRAM_BASE; phyaddr < SDRAM_BASE + SDRAM_SIZE; phyaddr += MB) { *(TTB_BASE + (phyaddr >> 20) ) = NONE_SEC_DESC; } // 为每个进程空间进行地址映射,映射关系为 taskID -> taskID*32M~taskID*32M + 0x1fffff for(taskId = 1; taskId < TASK_SZ; taskId++) { *(TTB_BASE + ((taskId*32*MB) >> 20)) = (SDRAM_BASE + taskId*MB) | SEC_DESC_WB; } IDCaches_Restart(); }
mmu.S
INCLUDE s3c2440_h.S ;++++++++++++++++++++++++++++++++++++++++++++++++++++ FAULT_DESC EQU (0x0<<0) NONE_SEC_DESC EQU (FAULT_DESC) ;++++++++++++++++++++++++++++++++++++++++++++++++++++ VECTOR EQU (1<<13) ; 设置异常向量表位置,0 = 低地址0x0 1 = 高地址0xFFFF0000 ICACHE EQU (1<<12) ; 设置ICACHE,0 = 禁用 1 = 使用 R_S_BIT EQU (3<<8) ; 和页表项中描述符一起确定内存访问仅限 ENDIAN EQU (1<<7) ; 确定系统使用大,小端字节序,0 = 小端模式 1 = 大端模式 DCACHE EQU (1<<2) ; 设置DCACHE,0 = 禁用 1 = 使用 ALIGN EQU (1<<1) ; 设置地址对齐检查,0 = 禁用 1 = 使用 MMU_ON EQU (1<<0) ; 设置MMU,0 = 禁用 1 = 使用 ;++++++++++++++++++++++++++++++++++++++++++++++++++++ AREA MMU_INIT, CODE, READONLY EXPORT __enable_mmu __enable_mmu ldr r0, =TTB_BASE ldr r1, =VECTOR ldr r2, =ICACHE orr r3, r2, r1 ldr r1, =DCACHE orr r3, r3, r1 ldr r1, =ALIGN orr r3, r3, r1 ldr r1, =MMU_ON orr r3, r3, r1 ldr r1, =R_S_BIT orr r4, r3, r1 ldr r1, =ENDIAN orr r4, r4, r1 mov r2, #0 ; 使ICaches和DCaches无效 mcr p15, 0, r2, c7, c7, 0 ; 使能写入缓冲器 mcr p15, 0, r2, c7, c10, 4 ; 使指令,数据TLB无效无效 mcr p15, 0, r2, c8, c7, 0 ; 页表基址写入C2 mcr p15, 0, r0, c2, c0, 0 ; 将0x2取反变成0xFFFFFFFD,Domain0 = 0b01为用户模式,其它域为0b11管理模式 mvn r2, #0x2 ; 写入域控制信息 mcr p15, 0, r2, c3, c0, 0 ; 取出C1寄存器中值给reg0 mrc p15, 0, r2, c1, c0, 0 ; 先清除不需要的功能,现开启 bic r2, r2, r4 ; 设置相关位并开启MMU orr r2, r2, r3 mcr p15, 0, r2, c1, c0, 0 nop nop nop nop mov pc, lr END
start.S文件也发生了改变:
; ; start.S:主要安装异常向量表,初始化必要的硬件, ; 将代码从Norflash中拷贝到SDRAM, ; 然后跳转到SDRAM中去执行,最后调用main函数,开始miniOS启动第二阶段 ; INCLUDE s3c2440_h.S ; 以下为时钟相关寄存器地址 LOCKTIME EQU 0x4c000000 MPLLCON EQU 0x4c000004 CLKDIVN EQU 0x4c000014 RUN_BASE EQU 0x33ff0000 ; OS内存运行地址 MEM_REG_BASE EQU 0x48000000 MEM_REG_END EQU 0x48000034 WTD_TIMER EQU 0x53000000 ; 其它异常栈指针,miniOS处理了所有的异常 IMPORT HandleSWI ; 以下引入其它文件中声明函数名 IMPORT CopyCode2Ram IMPORT xmain IMPORT handle_irq IMPORT undef_excp IMPORT prefetch_abt IMPORT data_abt AREA Start, CODE, READONLY ENTRY ; 代码段开始 b Reset ; 异常向量表,其运行地址为0,pc自动由硬件设置 ; 该地址处指令为一跳转指令,跳往reset异常处理 b HandleUndef ; 未定义异常处理跳转指令,跳往_HandleUndef处 b HandleSWI ; 软件中断异常处理跳转指令,跳往_HandleSWI处 b HandlePrefetchAbort ; 预取指令中止异常处理跳转指令,跳往_HandlePrefetchAbort处 b HandleDataAbort ; 数据中止异常处理跳转指令,跳往_HandleDataAbort处 HandleNotUsed b HandleNotUsed ; 未使用异常处理跳转指令,没有处理 b HandleIRQ ; 一般中断异常处理跳转指令,跳往本源文件中 ;HandleIRQ符号处 HandleFIQ b HandleFIQ ; 快速中断异常处理跳转指令,没有处理 Reset ; Reset异常处理符号 bl clock_init ; 跳往时钟初始化处理 bl mem_init ; 跳往内存初始化处理 ldr sp, =SVC_STACK_BASE ; 设置管理模式栈指针 bl disable_watchdog ; 关闭看门狗 ; 代码拷贝,将代码拷贝到内存里去运行,如果从Nandflash ;启动运行,其RAM steppingstone只有4k,不足已运行全部代 ;码,如果从Norflash启动,其硬件特性决定其运行速度较慢, ;因此,将代码拷贝到内存里去运行 copy_code ; 代码拷贝开始符号 mov r0, #0x0 ; R0中为数据开始地址 (ROM数据保存在0地址开始处) ldr r1, =|Image$$RO$$Base| ; R1中存放RO输出域运行地址, ; 该值由符号变量Image$$EXEC_RO$$Base取得 ldr r2, =|Image$$ZI$$Limit| ; 该值由符号变量Image$$EXEC_ZI$$Limit取得 sub r2, r2, r1 ; R2 = R2 - R1,得出待拷贝数据长度 ldr r1, =KERNEL_RUN_PA bl CopyCode2Ram ; 将R0,R1,R2三个参数传递给CopyCode2Ram函数执行拷贝 ldr r0, =|Image$$ZI$$Base| ldr r1, =|Image$$ZI$$Limit| sub r0, r0, #VA_TO_PA_OFT sub r1, r1, #VA_TO_PA_OFT bl clear_bss_region ; mmu IMPORT __create_page_tables_early IMPORT __enable_mmu IMPORT __create_page_tables_post bl delay bl __create_page_tables_early ldr lr, =RUN_VM ; RUN_VM是运行地址的偏移地址(0x80000000+OFT) ldr r0, =__enable_mmu ; r0也是运行地址的偏移地址(0x80000000+__enable_mmu) sub r0, r0, #VA_TO_PA_OFT ; 因为MMU还没有使能,所以还在物理内存里执行,因此要减VA_TO_PA_OFT mov pc, r0 ; 一旦开启了MMU,虚拟地址0x80000000和0x30000000都可以使用 RUN_VM bl __create_page_tables_post ; 这儿已经开启了MMU,要切断原先的0X300000000地址映射,所以保证代码运行在0x800000000中 ; init all mode stack bl stack_init ; 跳往栈初始化代码处 msr cpsr_c, #0x5f ; 开启系统中断,进入系统模式 ldr lr, =halt_loop ; 设置返回地址 ldr pc, =xmain ; 跳往main函数,进入OS启动处理 halt_loop ; OS返回地址,其实这儿永远不可能被执行到,因为只要OS工 ; 作,它就会运行到世界末日 b halt_loop ; 死循环 delay mov r0, #0x100000 mov r1, #0 loop cmp r1, r0 sub r0, r0, #1 bne loop mov pc, lr HandleIRQ ; 系统中断处理 sub lr, lr, #4 ; 修正返回地址 ldr sp, =(IRQ_STACK_BASE + VA_TO_PA_OFT) ; 设置中断模式下栈指针 stmdb sp!, {r0-r12,lr} ; 保存现场 ldr lr, =int_return ; 设置中断处理程序的返回地址 ldr pc, =handle_irq ; 跳往中断处理程序 int_return ; 返回地址 ldmia sp!, {r0-r12,pc}^ ; 恢复被中断打断现场 clock_init ; 时钟初始化代码,详细见时钟初始化章节 ; Set lock time ldr r0, =LOCKTIME ldr r1, =0x00ffffff str r1, [r0] ; Set clock divison ldr r0, =CLKDIVN mov r1, #0x05 str r1, [r0] ; Set system clock to asynchronous mode mrc p15, 0, r1, c1, c0, 0 orr r1, r1, #0xc0000000 mcr p15, 0, r1, c1, c0, 0 ldr r0, =MPLLCON ldr r1, =0x5c011 ; MPLL is 400MHz str r1, [r0] mov pc, lr mem_init ; 内存初始化代码,详细见内存初始化章节 ldr r0, =MEM_REG_BASE ldr r1, =MEM_REG_END adr r2, memdata mem_init_loop ldr r3, [r2], #4 str r3, [r0], #4 teq r0, r1 bne mem_init_loop mov pc,lr memdata DCD 0x22111110 ;BWSCON DCD 0x00000700 ;BANKCON0 DCD 0x00000700 ;BANKCON1 DCD 0x00000700 ;BANKCON2 DCD 0x00000700 ;BANKCON3 DCD 0x00000700 ;BANKCON4 DCD 0x00000700 ;BANKCON5 DCD 0x00018005 ;BANKCON6 DCD 0x00018005 ;BANKCON7 DCD 0x008e04f4 ;REFRESH DCD 0x000000b1 ;BANKSIZE DCD 0x00000030 ;MRSRB6 DCD 0x00000030 ;MRSRB7 clear_bss_region mov r2, #0 clear_loop cmp r0, r1 beq quit_loop str r2, [r0], #4 b clear_loop quit_loop mov pc, lr disable_watchdog ldr r0, =WTD_TIMER mov r1, #0 str r1, [r0] mov pc, lr stack_init ; 栈指针初始化 ; undefine_stack ; 未定义异常 msr cpsr_c, #0xdb ldr sp, =(UND_STACK_BASE + VA_TO_PA_OFT) ; abort_stack ; 未定义异常模式 msr cpsr_c, #0xd7 ldr sp, =(ABT_STACK_BASE + VA_TO_PA_OFT) ; irq_stack ; 中断模式 msr cpsr_c, #0xd2 ldr sp, =(IRQ_STACK_BASE + VA_TO_PA_OFT) ; sys_stack ; 系统模式 msr cpsr_c, #0xdf ldr sp, =(SYS_STACK_BASE + VA_TO_PA_OFT) ; svr_stack ; 切换回管理模式 msr cpsr_c, #0xd3 mov pc, lr HandleUndef ; 未定义异常处理 add lr, lr, #4 ; 修正返回地址 stmdb sp!, {lr} ; pc stmdb sp!, {lr}^ ; lr stmdb sp!, {sp}^ ; sp stmdb sp!, {r0-r12} ; 保存现场 mrs r0, spsr ; 发生异常时,将状态寄存器里的数据保存在栈里 stmdb sp!, {r0} mov r0, sp ; 发生异常时,将将栈指针传递给异常处理函数用于打印异常现场信息 ldr pc, =undef_excp b halt_loop HandlePrefetchAbort ; 未定义异常处理 sub lr, lr, #4 ; 修正返回地址 stmdb sp!, {lr} ; pc stmdb sp!, {lr}^ ; lr stmdb sp!, {sp}^ ; sp stmdb sp!, {r0-r12} ; 保存现场 mrs r0, spsr ; 发生异常时,将状态寄存器里的数据保存在栈里 stmdb sp!, {r0} mov r0, sp ; 发生异常时,将将栈指针传递给异常处理函数用于打印异常现场信息 ldr pc, =prefetch_abt b halt_loop HandleDataAbort ; 未定义异常处理 sub lr, lr, #8 ; 修正返回地址 stmdb sp!, {lr} ; pc stmdb sp!, {lr}^ ; lr stmdb sp!, {sp}^ ; sp stmdb sp!, {r0-r12} ; 保存现场 mrs r0, spsr ; 发生异常时,将状态寄存器里的数据保存在栈里 stmdb sp!, {r0} mov r0, sp ; 发生异常时,将将栈指针传递给异常处理函数用于打印异常现场信息 ldr pc, =data_abt b halt_loop END
excp_handle.c增加了一点功能:
当出现异常状态时,蜂鸣器会长鸣,同时打印出异常发生前所有寄存器的值和Kernel panic,以方便朋友们调试。
#include "serial.h" #include "buzzer.h" #include "libs.h" /* print the kernel panic information */ #define kernel_panic() \ do{ \ printk("\r\nKernel panic!!\r\n"); \ buzzer_on(); \ while(1); \ } while(0) void undef_excp(unsigned long * sp) { printk("\r\nUndefine exception\r\n"); stack_dump(sp); kernel_panic(); // this will print the stack content } void prefetch_abt(unsigned long * sp) { printk("\r\nPrefetch abort exception\r\n"); stack_dump(sp); kernel_panic(); // this will print the stack content } void data_abt(unsigned long * sp) { printk("\r\nData abort exception\r\n"); stack_dump(sp); kernel_panic(); // this will print the stack content }
stack_dump实现在libs/libs.c中:
里面主要有:
#include "serial.h" #include "s3c2440.h" void xtos(unsigned long n){ unsigned long i; if((i=n/16)!=0) xtos(i); if(n%16 > 9) putc(n%16 - 10 +'A'); else putc(n%16+'0'); } void dtos(unsigned long n){ unsigned long i; if((i=n/10)!=0) dtos(i); putc(n%10+'0'); } void memset(char * dest, long len, int value){ if(dest == NULL || len <= 0) return; while(len--) *dest++ = value; return ; } // not safe copy char * memcpy(char * dest, const char * src, long len){ char * temp = dest; if(dest == NULL || src == NULL || len <= 0) return NULL; while((dest - temp) != len) *dest++ = *src++; return temp; } void mem_dump(const unsigned long * src, long len){ const unsigned long * temp = src; if(src == NULL || len <= 0) return; while(src-temp != len){ printk("\r\n 0x"); xtos(*src++); if((src-temp)%5 == 0) printk("\r\n"); } printk("\r\n"); } void stack_dump(unsigned long * sp){ printk("\r\n CPSR:0x"); xtos(*sp++); printk("\r\n R0:0x"); xtos(*sp++); printk("\r\n R1:0x"); xtos(*sp++); printk("\r\n R2:0x"); xtos(*sp++); printk("\r\n R3:0x"); xtos(*sp++); printk("\r\n R4:0x"); xtos(*sp++); printk("\r\n R5:0x"); xtos(*sp++); printk("\r\n R6:0x"); xtos(*sp++); printk("\r\n R7:0x"); xtos(*sp++); printk("\r\n R8:0x"); xtos(*sp++); printk("\r\n R9:0x"); xtos(*sp++); printk("\r\n R10:0x"); xtos(*sp++); printk("\r\n R11:0x"); xtos(*sp++); printk("\r\n R12:0x"); xtos(*sp++); printk("\r\n SP:0x"); xtos(*sp++); printk("\r\n LR:0x"); xtos(*sp++); printk("\r\n PC:0x"); xtos(*sp++); }
主要就是上述的改变,最新源码,请到我的CSDN空间资源中下载。
2440版本:http://download.csdn.net/detail/mr_raptor/4127806
6410版本:http://download.csdn.net/detail/mr_raptor/4902699
下载的代码只有针对QQ2440的,mini2440的和TQ2440的朋友,自己可以去将Key的驱动改一下即可。