生产bin文件
fromelf --bin --output=led.bin Objects\led_c.axf
生产汇编文件
fromelf --text -a -c --output=led.dis Objects\led_c.axf
1.AAPCS函数调用规则
- R0-R3:传递参数
- R0:传递返回值
- SP(R13):栈指针
- LR(R14):函数返回地址
- PC(R15):程序执行地址
- AAPCS规定函数调用过程不会破坏R4-R11寄存器
- 函数中随意使用R0-R3寄存器,无需保存
- 函数R4-R11,可以使用,但是用完以后需要恢复;
故意使用一个寄存器:
register int sum sam("r4");
进入add_val函数时需要保存R4,退出函数时需要还原R4
寄存器
一共16个寄存器,13个通用寄存器,3个特殊寄存器
R13 是栈指针,使用PUSH 或POP 实现存储的访问,物理上存在3个栈指针寄存器,一个是MSP,一个是PSP寄存器
R14是链接寄存器,用于函数和子程序程序调用时返回地址的保存,在异常处理期间,LR寄存器自动更新位特殊的数值,之后改数值将在异常处理结束时触发异常返回
R15程序计数器PC,读操作返回当前指令地址加4
异常和中断
中断也是一种异常
异常列表
中断列表
2.中断处理流程
1.进入中断保存现场
硬件自动保存R0,R1,R,R3,R3,LR,返回地址,xPSR到栈中
返回地址:是中断的返回地址,这里LR仅仅是一个普通的寄存器而已(LR函数返回就是函数的返回地址)
2.调用处理函数
要确保R4-R11不变,软件需要将会把这些寄存器入栈保存,函数执行完成后,恢复R4-R11寄存器。
3.退出中断需要恢复现场
硬件从栈中恢复R0,R1,R,R3,R3,LR,返回地址,xPSR。
任务切换
C文件
void task_a(char *str)
{
char buf[10] = "task a";
volatile int i;
puts(buf);
while (1)
{
i= 0;
while (str[i])
putchar(str[i]);
i++;
}
}
void task_b(char *str)
{
char buf[10] = "task b";
volatile int i;
puts(buf);
while (1)
{
i= 0;
while (str[i])
putchar(str[i]);
i++;
}
}
汇编文件
task_a
0x08000410: b51f .. PUSH {r0-r4,lr};入栈
0x08000412: 4604 .F MOV r4,r0
0x08000414: 4a0b .J LDR r2,[pc,#44] ; [0x8000444] = 0x80004d0
0x08000416: ca07 .. LDM r2,{r0-r2}
0x08000418: ab01 .. ADD r3,sp,#4
0x0800041a: c307 .. STM r3!,{r0-r2}
0x0800041c: a801 .. ADD r0,sp,#4
0x0800041e: f7ffffeb .... BL puts ; 0x80003f8
0x08000422: e00d .. B 0x8000440 ; task_a + 48
0x08000424: 2000 . MOVS r0,#0
0x08000426: 9000 .. STR r0,[sp,#0]
0x08000428: e003 .. B 0x8000432 ; task_a + 34
0x0800042a: 9900 .. LDR r1,[sp,#0]
0x0800042c: 5c60 `\ LDRB r0,[r4,r1]
0x0800042e: f7ffffb5 .... BL putchar ; 0x800039c
0x08000432: 9800 .. LDR r0,[sp,#0]
0x08000434: 5c20 \ LDRB r0,[r4,r0]
0x08000436: 2800 .( CMP r0,#0
0x08000438: d1f7 .. BNE 0x800042a ; task_a + 26
0x0800043a: 9800 .. LDR r0,[sp,#0]
0x0800043c: 1c40 @. ADDS r0,r0,#1
0x0800043e: 9000 .. STR r0,[sp,#0]
0x08000440: e7f0 .. B 0x8000424 ; task_a + 20
$d
0x08000442: 0000 .. DCW 0
0x08000444: 080004d0 .... DCD 134218960
$t
i.task_b
task_b
0x08000448: b51f .. PUSH {r0-r4,lr}
0x0800044a: 4604 .F MOV r4,r0
0x0800044c: 4a0b .J LDR r2,[pc,#44] ; [0x800047c] = 0x80004dc
0x0800044e: ca07 .. LDM r2,{r0-r2}
0x08000450: ab01 .. ADD r3,sp,#4
0x08000452: c307 .. STM r3!,{r0-r2}
0x08000454: a801 .. ADD r0,sp,#4
0x08000456: f7ffffcf .... BL puts ; 0x80003f8
0x0800045a: e00d .. B 0x8000478 ; task_b + 48
0x0800045c: 2000 . MOVS r0,#0
0x0800045e: 9000 .. STR r0,[sp,#0]
0x08000460: e003 .. B 0x800046a ; task_b + 34
0x08000462: 9900 .. LDR r1,[sp,#0]
0x08000464: 5c60 `\ LDRB r0,[r4,r1]
0x08000466: f7ffff99 .... BL putchar ; 0x800039c
0x0800046a: 9800 .. LDR r0,[sp,#0]
0x0800046c: 5c20 \ LDRB r0,[r4,r0]
0x0800046e: 2800 .( CMP r0,#0
0x08000470: d1f7 .. BNE 0x8000462 ; task_b + 26
0x08000472: 9800 .. LDR r0,[sp,#0]
0x08000474: 1c40 @. ADDS r0,r0,#1
0x08000476: 9000 .. STR r0,[sp,#0]
0x08000478: e7f0 .. B 0x800045c ; task_b + 20
产生中断时
- 硬件保存A现场:R0,R1,R,R3,R3,LR,返回地址,xPSR
- 调用处理函数systick_Handler
- 保存A现场R4-R11
- 取出任务B的现场
- 软件取出任务B的现场R4-R11
- 硬件恢复任务B的现场R0,R1,R,R3,R3,LR,返回地址,xPSR
- 中断返回
保存A的现场
- 指令保存在在flash中,不需要保存
- 全局变量不需要保存
- 局部变量本来就在他自己的栈里,不破坏即可
创建任务
实质就是伪造现场
返回地址:就是任务函数的入口,R0即使任务的参数
创建任务的代码实现
创建任务:
实质就是伪造一个现场
0.调整栈指针,因为栈时向下生长的,先进先出的特性
1.首先伪造软件恢复的寄存器部分R4~R11
2.然后伪造硬件自动恢复的寄存器部分R0~R3,R12,LR,SP,PSR
2.1 R0中保存的时任务参数
2.2 R15(PC)中保存的时程序入口
3.最后将栈顶指针保存在任务管理数组中
注意:这里没有伪造MSP或PSP寄存器的值
/*
*/
void create_task(task_function f, void *param, char *stack, int stack_len)
{
int *top = (int *)(stack + stack_len);
/* 伪造现场 */
top -= 16;
/* r4~r11 */
top[0] = 0; /* r4 */
top[1] = 0; /* r5 */
top[2] = 0; /* r6 */
top[3] = 0; /* r7 */
top[4] = 0; /* r8 */
top[5] = 0; /* r9 */
top[6] = 0; /* r10 */
top[7] = 0; /* r11 */
/* r0~r3 */
top[8] = (int)param; /* r0 */
top[9] = 0; /* r1 */
top[10] = 0; /* r2 */
top[11] = 0; /* r3 */
/* r12,lr */
top[12] = 0; /* r12 */
top[13] = 0; /* lr */
/* 返回地址 */
top[14] = (int)f; /* 任务入口 */
/* PSR */
top[15] = (1<<24); /* psr 使用thumb指令集 */
/* 记录栈的位置 */
task_stacks[task_count++] = (int)top;
}
psr寄存器第24位标识使用哪个指令集
thumb指令 16位,消耗空间小,但是执行效率低
arm指令 32位 消耗空间大,但是执行效率高
启动任务
StartTask_asm PROC
; 从任务的栈里把R4~R11读出来写入寄存器
; r0 : 保存有任务的栈
; r1 : 保存有LR(特殊值)
LDMIA r0!, { r4 - r11 }
; 更新SP
MSR MSP, R0
;MOV SP, R0
; 触发硬件中断返回: 它会把栈里其他值读出来写入寄存器(R0,R1,R2,R3,R12,PSR)
BX R1
ENDP
切换任务
- 硬件保存了一部分现场,并且让LR等于了一个特殊值
- 保存R4-R11
- 将LR传递给R0寄存器,讲SP
- 保存LR
- sp-4
- 保存老的任务栈,切换新的任务栈
- 恢复栈
- 返回
韦老师的任务切换函数
SysTick_Handler_asm PROC
; 在这里保存R4~R11
STMDB sp!, { r4 - r11 }
STMDB sp!, { lr }
MOV R0, LR ; LR是一个特殊值
ADD R1, SP, #4;这里保存了一个LR的值,所以需要做一个sp+4
BL SysTick_Handler ; 这个C函数保证不破坏R4~R11
;经过测试以下部分代码根本不得执行
LDMIA sp!, { r0 };拿出特殊的LR值
LDMIA sp!, { r4 - r11 }
BX R0
ENDP
END
本人修改简化的任务切换
SysTick_Handler_asm PROC
; 在这里保存R4~R11
STMDB sp!, { r4 - r11 }
MOV R0, LR ; LR是一个特殊值
MOV R1, SP ; LR是一个特殊值
BL SysTick_Handler ; 这个C函数保证不破坏R4~R11
ENDP
SysTick 中断处理函数
/**
lr_bak:LR特殊值
old_sp:老的栈
*/
void SysTick_Handler(int lr_bak, int old_sp)
{
int stack;
int pre_task;
int new_task;
SCB_Type * SCB = (SCB_Type *)SCB_BASE_ADDR;
/* clear exception status */
SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk;
/* 如果还没有创建好任务, 直接返回 */
if (!is_task_running())
{
return; // 表示无需切换
}
/* 启动第1个任务或者切换任务 */
if (cur_task == -1)
{
/* 启动第1个任务 */
cur_task = 0;
/* 从栈里恢复寄存器 */
/* 写汇编 */
stack = get_stack(cur_task);
StartTask_asm(stack, lr_bak);
return ; /* 绝对不会运行到这里 */
}
/* 切换任务 */
else
{
// 取出下一个任务
pre_task = cur_task;
new_task = get_next_task();
if (pre_task != new_task)
{
/* 保存 pre_task: 在汇编里已经保存了 */
/* 将上一个任务的栈保存起来,更新sp */
set_task_stack(pre_task, old_sp);
/* 切换 new_task */
stack = get_stack(new_task);
cur_task = new_task;
StartTask_asm(stack, lr_bak);
}
}
}
RTOS的本质
- 任务切换:本质就是保存A的栈,恢复B的栈
- 任务的休眠和唤醒