一、学习RTOS
RTOS学习分为三个层次:
- 使用API
- 知道API内部机制
- 彻底掌握代码细节,能移植、改进
我们先提出一个问题:RTOS和裸机开发有什么区别?
当有多个事件需要去执行(比如事件A:吃饭和事件B:回信息),裸机通常使用轮询方式、事件中断和定时器中断这种方式,都不可避免当某个事件运行时间长会影响到其他事件。
这时我们引出RTOS,把事件拆分成很小的一段轮换运行。当轮换时间片很小就可以认为是事件是同时运行的。

二、ARM架构
2.1 SOC组成
主控芯片SOC一般包括CPU、内存Flash、GPIO模块等,我们写的程序会烧写进flash。CPU运行第一步读取Flash取指令,然后根据指令去做各种操作,我们下面会介绍这些操作的作用。

2.2 分析C函数汇编理解程序本质
什么叫做程序?
程序:①有指令 ②有运行过程中的数据。
例如:CPU在读变量A的时候,会把内存上对应地址的数据存储到CPU寄存器;同样在CPU写数据的时候会把CPU某个寄存器的值写入到内存上。这时候涉及到下面这些简单的汇编指令:
| 汇编指令 | 作用 | 解释 |
|---|---|---|
| LDR R0,[R1,#4] | 读内存,(源、目的、长度) | 从源地址R1+4的位置读取长度4字节数据,存放到目的地址为R0的位置 |
| STR R0, [R2,#8] | 写内存,(源、目的、长度) | 从源地址R0的位置读取长度4字节数据,写到目的地址为R2+8的位置 |
| ADD R0, R1, R2 | 加 | R0=R0+1 |
| SUB R0, R0, #1 | 减 | R0=R0-1 |
| PUSH {R3, LR} | 入栈 | 把R3,L2的值写入SP所指示内存,同时SP=SP-8 |
| POP {R3, PC} | 出栈 | 从SP位置读内存赋值给R3,PC |
| MOVS R0,#1 | 赋值 | R0=1 |
我们对以下函数调用程序进行反汇编,有助于我们理解程序本质
int add_val(int *pa, int *pb)
{
volatile int tmp;
tmp = *pa;
tmp += *pb;
return tmp;
}
int mymain()
{
volatile int a = 1;
volatile int b = 2;
volatile int c;
c = add_val(&a, &b);
return 0;
}
c语言反汇编后的指令,可以先保存图片,下面开始详细说指令的作用

一开始调用main函数之前栈SP会指向内存某一个地方
PUSH{r1-r3,lr} 将CPU寄存器的lr r3 r2 r1的值压入内存的栈中,同时SP=SP-16(栈顶SP=0)

MOVS r0,#1 CPU寄存器r0=1
STR r0,[sp,#8] 把r0的值写入sp+8的地方(r3),也就是此时栈区r3即a=1。(volatile int a = 1)
MOVS r0,#2 ,STR r0,[sp,#4]作用同上,栈区r2即b=2(volatile int b = 2)
ADD r1,sp,#4 r1=sp+4,这里其实是r1=&b
ADD r1,sp,#4 同上,r0=sp+8也就是r0=&a
BL add_val 先保存add_val()下一条指令地址到lr,即lr=0x08000384然后执行add_val()
在这里需要注意函数调用栈做了什么

PUSH {r3,lr} 把lr(之前保存的地址),r3压入栈

MOV r2,r0 r2=r0=&a
LDR r0,[r2,#0] 从r2+0地址处(&a)读内存,即r0=a=1
STR r0,[sp,#0] r0的值写到sp+0指针处,即栈区r3处 也就是程序tmp = *pa=1;
LDR r0,[r1,#0] 从r1+0地址处(&b)读取数据存放到r0,即r0=b=2
LDR r3,[sp,#0] 从sp+0地址处读取数据存放到r3 r3=tmp=1
ADD r0,r0,r3 r0=r0+r3=2+1=3
STR r0,[sp,#0] tmp=3
LDR r0,[SP,#0] r0=tmp=3 注意此处没有优化程序,写一遍tmp又读一遍tmp,因为加了volatile
POP {r3,pc} 从内存sp位置读取数据写给寄存器r3,pc,lr和r3出栈,此时跳转函数回到lr=0x08000384地址处,即add_val()下一条指令

STR r0,[sp,#0] 把r0的值写道sp+0的地址处,即内存r1处即c=3
2.3 ARM架构过程调用标准AAPCS
所以函数调用的时候,有以下规则:
参数传递基本原则(浮点数和结构体数组暂不考虑):
前四个参数通过寄存器 R0~R3 传递。
如果有更多的参数,它们通过栈传递,从高地址到低地址,且参数从左到右依次入栈。
返回值传递
通过r0寄存器返回
寄存器保护
R0,R1,R2,R3,R12随便使用,不需要保护
R4~R11 使用前先保存,在函数返回之前需要恢复,从汇编角度看就是通过PUSH和POP来保护

R13 SP栈指针
R14 LR返回地址(跳转前函数的下一条指令地址)
R15 PC指示当前执行的指令地址
三、栈的作用
3.1 堆和栈
堆(Heap) 就是一块空闲的内存,我们使用的时候可以取出来,不用的时候释放
malloc:从堆里划出一块空间给程序使用free:用完后,再把它标记为"空闲"的,可以再次使用
#include <stdlib.h>
void *malloc( size_t size );
void free( void *ptr );
栈(Stack) 后进先出,栈在RTOS很重要,每个任务都有自己的栈
3.2 中断处理
中断处理分为三步:①保存现场②处理中断③恢复现场
中断处理过程:当产生中断后,硬件保存CPU所有寄存器(r0~r3),硬件调用 中断向量表的处理函数,然后调用函数本身规则保障不会破坏r4~r11这些寄存器,执行完中断后,硬件从栈里面恢复所有寄存器。

要注意图中栈的返回地址是中断的返回地址,不是之前说的LR(这里LR是作为一个普通寄存器保存)
3.3 任务切换
多任务核心就是保存现场
有两个任务 A和B,通过tick中断切换,发生中断需要保存现场,现场就保存在栈:
在任务的栈会首先保存一些局部变量,然后还有运行过程中的栈。上面已经聊过发生中断硬件会保存寄存器和返回地址到栈,然后调用中断函数(此中断函数会去切换任务)通过软件保存R4~R11寄存器

445

被折叠的 条评论
为什么被折叠?



