====================================《一步一步写嵌入式操作系统——ARM编程方法与实践》读书笔记(第三章)===============================================
1、嵌入式平台的启动过程(引导程序->操作系统)
CPU上电后,ARM会首先从地址零处开始运行,此时运行引导程序,然后引导程序将操作系统加载到RAM中,
并将操作系统的第一条指令地址赋值给PC,操作系统开始执行
操作系统启动时要完成的初始化工作:
① 程序运行栈的初始化。
②、处理器及外设的初始化。
2、分析开源嵌入式操作系统freeRTOS的初始化部分
.section
.startup,"ax" /*将整段代码编译到startup段中,ax表示可重定位,可执行*/
.code 32
/*编译生成32位指令集的代码*/
.align 0 /*以2^0=1字节对齐*/
b
_start
ldr
pc, _undf
ldr pc, _swi
ldr pc, _pabt
ldr pc, _dabt
nop
ldr pc, [pc,#-0xFF0]
ldr pc, _fiq
_undf:
.word __undf
_swi:
.word vPortYieldPro
_pabt:
.word __pabt
_fiq:
.word __fiq
__undf: b
__undf
__pabt:
b
__padt
__dabt:
b
__dabt
__fiq:
b
__fiq
*******************************************
_start:
/*以下代码初始化各个模式下的堆栈*/
ldr
r0,
.LC6
msr
CPSR,
#MODE_UND|I_BIT|F_BIT
mov sp,
r0
……
sub
r0,
r0,
#SVC_STACK_SIZE
msr
CPSR,
#MODE_SYS|I_BIT|F_BIT
mov
sp, r0
msr
CPSR,
#MODE_SVC|I_BIT|F_BIT
mov
a2,
#0
mov
r12, a2
mov
r7, a2
ldr
r1, .LC1
ldr
r3, .LC2
subs r3, r3, r1
beq .end_clear_loop
mov
r2, #0
/*初始化bss段*/
.clear_loop:
strb
r2, [r1], #1
subs
r3,
r3, #1
bgt
.clear_loop
.end_clear_loop:
……
mov
r0, #0
mov
r1, #0
/*调到main函数中,在main函数中执行prvSetupHardware函数,完成特定硬件平台的初始化工作
①时钟初始化
②内存初始化
***/
b1
main
.LC1:
.word
__bss_beg__
.LC2:
.word
__bss_end__
……
.LC6:
.word
__stack_end__
3、自己编写操作系统
.section
.startup
.code
32
.align
0
.global
_start
/*
引入外部变量
*/
.extern
__vector_reset
.extern __vector_undefined
.extern __vector_swi
.extern __vector_prefetch_abort
.extern __vector_data_abort
.extern
__vector_reserved
.extern __vector_irq
.extern
__vector_fiq
_start:
ldr
pc,_vector_reset
ldr pc,_vector_undefined
ldr pc,_vector_swi
ldr pc,_vector_prefetch_abort
ldr pc,_vector_data_abort
ldr pc,_vector_reserved
ldr pc,_vector_irq
ldr pc,_vector_fiq
.align 4
_vector_reset: .word
__vector_reset
_vector_undefined: .word
__vector_undefined
_vector_swi:
.word
__vector_swi
_vector_prefetch_abort:
.word
__vector_prefetch_abort
_vector_data_abort:
.word
__vector_data_abort
_vector_reserved:
.word
__vector_reserved
_vector_irq:
.word
__vector_irq
_vector_fiq:
.word
__vector_fiq
注:将文件保存为start.s
********************************************************
.equ
DISABLE_IRQ,
0x80
.equ
DISABLE_FIQ,
0x40
.equ
SYS_MOD,
0x1f
.equ
IRQ_MOD,
0x12
.equ
FIQ_MOD,
0x11
.equ
SVC_MOD,
0x13
.equ
ABT_MOD,
0x17
.equ
UND_MOD,
0x1b
.equ
MEM_SIZE,
0x800000 /*内存的总容量为8M*/
.equ
TEXT_BASE,
0x30000000 /*外部sdram接在起始地址为0x30000000的位置上*/
.equ
_SVC_STACK,
(TEXT_BASE+MEM_SIZE-4) /*选取SVC模式堆栈的最顶端为内存的最后一个字节,并给其分配1K的堆栈空间*/
.equ
_IRQ_STACK,
(_SVC_STACK-0x400) /*其他模式的堆栈空间大小同样为1K*/
.equ
_FIQ_STACK,
(_IRQ_STACK-0x400)
.equ
_ABT_STACK,
(_FIQ_STACK-0x400)
.equ
_UND_STACK,
(_ABT_STACK-0x400)
.equ
_SYS_STACK,
(_UND_STACK-0x400)
.text
.code 32
.global __vector_reset
.extern plat_boot
.extern __bss_start__
.extern __bss_end__
__vector_reset: /*在正常模式下,程序会进入__vector_reset并运行*/
msr cpsr_c, #(DISABLE_IRQ|DISABLE_FIQ|SVC_MOD)
ldr sp,=_SVC_STACK
msr cpsr_c, #(DISABLE_IRQ|DISABLE_FIQ|IRQ_MOD)/*切换模式*/
ldr sp,=_IRQ_STACK /*堆栈寄存器的初始化*/
msr cpsr_c, #(DISABLE_IRQ|DISABLE_FIQ|FIQ_MOD)
ldr sp,=_FIQ_STACK
msr cpsr_c, #(DISABLE_IRQ|DISABLE_FIQ|ABT_MOD)
ldr sp,=_ABT_STACK
msr cpsr_c, #(DISABLE_IRQ|DISABLE_FIQ|UND_MOD)
ldr sp,=_UND_STACK
msr cpsr_c, #(DISABLE_IRQ|DISABLE_FIQ|SYS_MOD)
ldr sp,=_SYS_STACK
_clear_bss: /*清除bss段*/
ldr r1,_bss_start_ /*_bss_start_和_bss_end_来自链接脚本*/
ldr r3,_bss_end_
mov r2,#0x0
1:
cmp r1,r3
beq _main
str r2,[r1],#0x4
b 1b
_main:
b plat_boot
_bss_start_:.word
__bss_start__
_bss_end_:.word
__bss_end__
.end
注:将文件保存为init.s
********************************************************
.global __vector_undefined
.global __vector_swi
.global __vector_prefetch_abort
.global __vector_data_abort
.global __vector_reserved
.global __vector_irq
.global __vector_fiq
.text
.code 32
__vector_undefined:
nop
__vector_swi:
nop
__vector_prefetch_abort:
nop
__vector_data_abort:
nop
__vector_reserved:
nop
__vector_irq:
nop
__vector_fiq:
nop
注:
文件保存为abnormal.s
此处定义的外部变量在代码start.s中用到了
*******************************************
typedef void (*init_func)(void);
#define UFCON0 ((volatile unsigned int *)(0x50000020))
void helloworld(void){
const char *p = "helloworld\n";
while(*p){
*UFCON0 = *p ++;
}
}
static init_func init[] = {
helloworld,
0,
};
void plat_boot(void){
int i;
for(i = 0; init[i]; i ++){
init[i]();
}
while(1);
}
注:将文件保存为boot.c
plat_boot在start.s函数中用到。
*************************************
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;/*此处为什么是0,不明白*/
.text :
{
*(.startup)
*(.text)
}
. = ALIGN(32);
.data :
{
*(.data)
}
. = ALIGN(32);
__bss_start__ = .;
.bss :
{
*(.bss)
}
__bss_end__ = .;
}
注:该文件为编译链接脚本文件
文件保存为leeos.lds
*****************************************
CC=arm-elf-gcc
LD=arm-elf-ld
OBJCOPY=arm-elf-objcopy
CFLAGS= -O2 -g
ASFLAGS= -O2 -g
LDFLAGS= -Tleeos.lds -Ttext 30000000
OBJS=init.o start.o boot.o abnormal.o
.c.o:
$(CC) $(CFLAGS) -c $<
.s.o:
$(CC) $(ASFLAGS) -c $<
leeos:$(OBJS)
$(CC) -static -nostartfiles -nostdlib $(LDFLAGS) $? -o $@ -gcc
$(OBJCOPY) -O binary $@ leeos.bin
clean:
rm *.o leeos leeos.bin -f
注:文件保存为Makefile
***************************************************
cpu: arm920t
mach: s3c2410x
#physical memory
mem_bank: map=M, type=RW, addr=0x30000000,size=0x00800000,file=./leeos.bin,boot=yes
mem_bank: map=I, type=RW, addr=0x48000000,size=0x20000000
注:文件保存为skyeye.conf
4、MMU(内存管理单元)
作用:①、MMU能够很好地提供内存保护作用
②、给各进程提供独立的地址空间
根据虚拟地址找到物理地址的过程:
a、将段页表存储到内存的指定位置
b、将CPU要访问的内存地址(虚拟地址),交给MMU。
c、MMU取出虚拟地址的前12位作为页表项的偏移(相当于数组的下标),
然后将这12位地址左移2位(因为一条页表项为32位,4个字节),
与页表基地址相与,就得到了相应的页表项的地址。
d、将页表项的取出,其中前12位定位了该虚拟地址的物理地址在那一页。
就是将这12位二进制数左移10位,就是所属页的基地址。
e、将虚拟地址的后20位,与d中得到的左移之后的数字相与就可得到虚拟地址对应的具体的物理地址。
页表内容所代表的意义
a、第31~20位段表示物理地址的基地址,即在物理地址中属于哪一页。
b、第11~10位是AP位,区分了用户模式与特权模式对同一个页的不同访问权限。
当AP位为“11”时,表示任何模式下都可以对该空间进行读写。
当AP位为“10”则表示特权模式可读写该页,而用户模式只能读取该页。
c、第8~5位代表该页所属的域。
d、3位和2位分别代表cache和write buffer。
e、1~0位。用来区分页表类型,对于段页表,这两位的值总为“10”
5、编写使用MMU的操作系统
#define PAGE_TABLE_L1_BASE_ADDR_MASK
(0xffffc000) //段页表的基地址,即在内存中存放的起始地址
#define VIRT_TO_PTE_L1_INDEX(ADDR)
(((addr)&0xfff00000)>>18)
#define
PTE_L1_SECTION_NO_CACHE_AND_WB
(0x0<<2)
#define PTE_L1_SECTION_DOMAIN_DEFAULT
(0x0<<5)
#define PTE_ALL_AP_L1_SECTION_DEFAULT
(0x1<<10)
#define PTE_L1_SECTION_PADDR_BASE_MASK
(0xfff00000)
#define PTE_BITS_L1_SECTION
(0x2)
#define L1_PTR_BASE_ADDR
0x30700000
#define PHYSICAL_MEM_ADDR
0x30000000
#define VIRTUAL_MEM_ADDR
0x30000000
#define MEM_MAP_SIZE
0x800000
#define PHYSICAL_IO_ADDR
0x48000000
#define VIRTUAL_IO_ADDR
0xc8000000
#define IO_MAP_SIZE
0x18000000
//以物理地址为参数,能够返回与该物理地址对应的段页表项的内容
unsigned int gen_l1_pte(unsigned int paddr){
return (paddr&PTE_L1_SECTION_PADDR_BASE_MASK)|PTE_BITS_L1_SECTION;
}
//通过页表的基地址与虚拟地址产生该段页表项的地址
unsigned int gen_l1_pte_addr(unsigned int baddr, unsigned int vaddr){
return (baddr&PAGE_TABLE_L1_BASE_ADDR_MASK)|VIRT_TO_PTE_L1_INDEX(vaddr);
}
//通过一个循环将物理地址0x30000000~0x30800000映射到虚拟地址的0x30000000~0x30800000处
void init_sys_mmu(void){
unsigned int pte;
unsigned int pte_addr;
int j;
//将8M内存进行虚拟地址映射
for(j = 0; j < MEM_MAP_SIZE >> 20; j ++){
pte=gen_l1_pte(PHYSICAL_MEM_ADDR + (j << 20));
pte |= PTE_ALL_AP_L1_SECTION_DEFAULT;
pte |= PTE_L1_SECTION_NO_CACHE_AND_WB;
pte |= PTE_L1_SECTION_DOMAIN_DEFAULT;
pte_addr = gen_l1_pte_addr(L1_PTR_BASE_ADDR, VIRTUAL_MEM_ADDR+(j << 20));
*(volatile unsigned int *)pte_addr=pte;
}
for(j = 0; j < IO_MAP_SIZE>>20; j ++){
pte = gen_l1_pte(PHYSICAL_IO_ADDR + (j << 20));
pte |= PTE_ALL_AP_L1_SECTION_DEFAULT;
pte |= PTE_L1_SECTION_NO_CACHE_AND_WB;
pte |= PTE_L1_SECTION_DOMAIN_DEFAULT;
pte_addr = gen_l1_pte_addr(L1_PTR_BASE_ADDR, VIRTUAL_IO_ADDR + (j << 20));
*(volatile unsigned int *)pte_addr = pte;
}
}
//激活MMU
void start_mmu(void){
unsigned int ttb = L1_PTR_BASE_ADDR;
asm(
"mcr p15, 0, %0, c2, c0, 0\n"
"mvn r0, #0\n"
"mcr p15, 0, r0, c3, c0, 0\n"
"mov r0, #0x1\n"
"mcr p15, 0, r0, c1, c0, 0\n"
"mov r0, r0\n"
"mov r0, r0\n"
"mov r0, r0\n"
:
: "r" (ttb)
: "r0"
);
}
注:保存为文件mmu.c
**************************************************************
修改Makefile文件
CC=arm-elf-gcc
LD=arm-elf-ld
OBJCOPY=arm-elf-objcopy
CFLAGS= -O2 -g
ASFLAGS= -O2 -g
LDFLAGS= -Tleeos.lds -Ttext 30000000
OBJS=init.o start.o boot.o abnormal.o mmu.o
.c.o:
$(CC) $(CFLAGS) -c $<
.s.o:
$(CC) $(ASFLAGS) -c $<
leeos:$(OBJS)
$(CC) -static -nostartfiles -nostdlib $(LDFLAGS) $? -o $@ -gcc
$(OBJCOPY) -O binary $@ leeos.bin
clean:
rm *.o leeos leeos.bin -f
注:文件保存为Makefile
***********************************************************
修改boot.c文件
typedef void (*init_func)(void);
#define UFCON0 ((volatile unsigned int *)(0x50000020))
void helloworld(void){
const char *p = "helloworld\n";
while(*p){
*UFCON0 = *p ++;
}
}
static init_func init[] = {
helloworld,
0,
};
void plat_boot(void){
int i;
for(i = 0; init[i]; i ++){
init[i]();
}
init_sys_mmu();
start_mmu();
test_mmu();
while(1);
}
void test_mmu(void){
const char *p = "test_mmu\n";
while(*p){
*(volatile unsigned int *)0xd0000020 = *p++;
};
}
注:文件保存为boot.c