start.S源码:
.globl _start
_start:
// 0 地址
b reset // 复位时,cpu跳到0地址 其实复位之后,CPU就处于管理模式(svc)
ldr pc, =undefined_instruction //cpu遇到不能识别的指令时 ,地址4,该指令到内存处读取un..的链接地址
ldr pc, _vector_swi // 当执行swi指令时, 进入swi模 式 ,地址8,该指令直接读取_vector_swi当前地址
b halt @ldr pc, _prefetch_abort // 预取中止异常
b halt @ldr pc, _data_abort // 数据访问异常
b halt @ldr pc, _not_used // 没用到
ldr pc, _irq // 0x18 中断异常
b halt @ldr pc, _fiq // 快中断异常
_irq :
.word vector_irq
_vector_swi:
.word vector_swi
vector_swi:
// 1. 保存现场
ldr sp, =0x56000000 //sp是svc模式自己的r13,要从新设置
stmdb sp!, {r0-r12, lr} // lr就是swi的下一条指令地址
// 2. 处理异常
mrs r0, cpsr
ldr r1, =swi_str
bl print_cpsr
// 3. 恢复现场
ldmia sp!, {r0-r12, pc}^ // ^表示把spsr恢复到cpsr
swi_str:
.word 0x00697773 // swi
undefined_instruction:
// 1. 保存现场
ldr sp, =0x55000000
stmdb sp!, {r0-r12, lr}
// 2. 处理异常
mrs r0, cpsr
ldr r1, =und_str
bl print_cpsr
// 3. 恢复现场
ldmia sp!, {r0-r12, pc}^ // ^表示把spsr恢复到cpsr
und_str:
.word 0x00646e75 // und
usr_str:
.word 0x00727375 // usr
vector_irq:
// 1. 保存现场
ldr sp, =0x54000000
sub lr, lr, #4
stmdb sp!, {r0-r12, lr} // lr就是swi的下一条指令地址
// 2. 处理异常
// 2.1 分辨是哪个中断
// 2.2 调用它的处理函数
// 3. 恢复现场
ldmia sp!, {r0-r12, pc}^ // ^表示把spsr恢复到cpsr
reset:
// 硬件相关的设置
// Peri port setup
ldr r0, =0x70000000
orr r0, r0, #0x13
mcr p15,0,r0,c15,c2,4 @ 256M(0x70000000-0x7fffffff)
// 关看门狗
// 往WTCON(0x7E004000)写0
ldr r0, =0x7E004000
mov r1, #0
str r1, [r0]
// 设置栈
ldr sp, =8*1024
// 设置时钟
bl clock_init
bl ddr_init
bl init_uart
// 把程序的代码段、数据段复制到它的链接地址去
adr r0, _start // 获得_start指令当前所在的地址 : 0
ldr r1, =_start // _start的链接地址 0x51000000
ldr r2, =bss_start // bss段的起始链接地址
sub r2, r2, r1
cmp r0,r1
beq clean_bss
bl copy2ddr
cmp r0, #0
bne halt
// 清BSS
// 把BSS段对应的内存清零
clean_bss:
ldr r0, =bss_start
ldr r1, =bss_end
mov r3, #0
cmp r0, r1
ldreq pc, =on_ddr
clean_loop:
str r3, [r0], #4
cmp r0, r1
bne clean_loop
ldr pc, =on_ddr //跳到DDR中运行
on_ddr:
mrs r0, cpsr //把状态寄存器的值读到r0中
bic r0,r0,#0x1f //清掉后5位
orr r0,r0,#0x10 //把第四位置1
msr cpsr,r0 // 进入user mode ,把r0的值赋给程序状态寄存器
ldr sp, =0x57000000 //用户模式下的sp
ldr r1, =usr_str
bl print_cpsr
swi 0
// cpu进入svc模式
// 把之前的cpsr保存到spsr_svc
// 切换到r13_svc, r14_svc
// 把swi的下一条指令(bl hello)的地址存到r14(lr)_svc
// 跳到地址8
bl hello
undef: //执行完hello函数以后会执行该条未定义指令,从而进入未定义指令中断
.word 0xff000000
// cpu进入Undefined模式
// 把之前的cpsr保存到spsr_und
// 切换到r13_und, r14_und
// 把下一条指令存的地址到r14(lr)_und
// 跳到地址4
swi_ret:
bl main
halt:
b halt
====================================================================
说明:
①上面代码上电复位后CPU就处于管理模式(svc),执行0地址处的b reset跳转到reset:处继续运行,把相关硬件初始化以后会清掉cpsr后5位并把第四位置1,进入user mode设置栈后运行于用户态(usr),即处理器启动时首先进入管理员模式(svc),此后进入除用户模式之外的其他模式,主要完成各模式的堆栈设置,最后进入用户模式,运行用户程序;当发生swi软中断以后cpu进入svc模式。
②swi软中断主要用于usr模式(应用程序通常运行于usr模式)切换到svc模式下。在arm的7种模式当中(已经不止7种了)usr模式是唯一一个非特权模式,其他都是特权模式,比如fiq、und等都是特权模式,他们之间的切换直接更改cpsr寄存器的低5位的模式位或者真的发生fiq、und等异常的时候就可以达到切换的目的;而usr模式不是特权模式没有办法更改cpsr寄存器的低5位进行切换,想切换到特权模式只能调用swi指令,swi指令会帮助它进入到svc模式。
③如果原来是svc模式,发生未定义指令异常后进入und(Undefined)模式,这时候要重新设置sp栈指针;如果执行swi指令的时候已经处于svc模式,那么发生swi的软中断之后仍然还是svc模式,这个时候就不用再去设置sp栈指针了(在tiny4412异常实验中因为运行的uboot,已经处于svc模式,故要注意sp指针)。
④只有处理swi和und异常的时候lr指向下一条指令,其他的异常发生的时候lr都是指向下两条指令;ARM上的每条指令长度都是32位即4个字节;swi指令也是32位且其后面跟的value值占该指令的低24位,所以在程序里可以得到swi指令的value值,具体如下:
unsigned long *pdo_swi = 0x75000000;
*pdo_swi = do_swi; //先把中断处理函数do_swi地址放在0x75000000
在发生swi异常的时候程序会自动跳到异常向量入口:"b swi\n"
接着跳转到swi处执行:
"swi:\n"
"stmfd sp!, {r0-r12, lr}\n"
//保护现场,把usr模式下的相关寄存器入栈,
//存放顺序是先存放lr、r12....r0,最终sp指向r0的地址
"mov r0, sp\n" //把上一步中指向usr模式下r0地址的sp传给r0寄存器
"mov r3, #0x75000000\n"
"ldr r3, [r3]\n"
"blx r3\n" //调用中断处理函数do_swi,参数放在r0中
//regs[0] == r0
//regs[1] == r1
//.....
//regs[12] == r12
//regs[13] == lr
void do_swi(unsigned long regs[]) //regs指向usr模式下r0地址
{
//按照入栈顺序regs[13]为usr模式下lr值,即发生swi异常时下一条指令的地址
//regs[13] - 4 = lr - 4 ; 即上一条指令的地址也就是swi异常的地址
unsigned long *instr = regs[13] - 4;
//根据ARM指令是32位的,swi指令也是32位且其后面跟的value值占该指令的低24位得到value值
printf("swi: 0x%x\n", *instr & 0xffffff);
}
====================================================================
注意异常在操作系统里的用法:
①SWI异常中断的用处是:应用程序运行于用户态(usr),当应用程序调用open/read/write函数的时候要调用内核的函数,要怎么进入内核呢?其实open/read/write函数就是一条SWI #VAL指令,带有不同的参数,一执行这条指令CPU就会发生异常,cpu会进入svc模式,跳到固定的地址,在地址里面就放有内核代码,在内核代码里面就判断SWI指令带进来的#VAL参数值,如果是open函数就调用sys_open函数等等。
②未定义指令异常用处:调试用,程序在执行的时候想打一个断点怎么打?程序在运行的时候在想打断点的地方把指令取出来,并且保存,替换为一条未定义的指令,程序在运行的时候跑到该指令处就会发生未定义指令异常,进入到未定义指令异常处理函数,在处理函数里面就可以放一些代码,比如等待用户输入其他指示,用户要查看某些寄存器,查看某些内存,就可以查看寄存器内存并把结果返回,当要继续运行的时候就把替换的指令恢复回去,让程序从这里重新运行。
入栈顺序
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』