RTOS的基本概念与线程基础知识
1 RTOS概念及线程的引入
1.1 RTOS的概念
用人来类比单片机程序和RTOS:
妈妈要一边给小孩喂饭,一边加班跟同事交流,怎么办?
对于单线条的人,不能分心,不能同时做事,她只能这样做:
- 给小孩喂一口饭
- 瞄一眼电脑,有信息就去回复
- 再回来给小孩喂一口饭
- 如果小孩吃这口饭太慢,他回复同事的信息也就慢了,被同事催,你半天都不回复我?
- 如果回复同事的信息要写一大堆,小孩就饿的大哭起来
对于眼明手快的人,她可以一心多用,她这样做:
- 左手拿勺子,给小孩喂饭
- 右手敲键盘,恢复同事
- 两不耽误,小孩“以为”妈妈在专心喂饭,同事以为“她在专心聊天”
- 但是脑子只有一个啊,虽然说一心多用,但是谁能够同时考虑两件事?
- 只是她反应快,上一秒钟在考虑夹哪个菜给小孩,下一秒钟考虑给同事回复什么信息
这种做法,在软件开发上,就是使用操作系统, 在单片机里叫做使用RTOS
RTOS的意思是:Real Time Operating System,即实时操作系统,但使用Windows,我们经常碰到程序卡死、停顿的现象,日常生活中,这是可以忍受的,但是在电梯系统中,你按住开门键时如果没有即刻反应,即使知识慢个一秒钟,也会夹住人,在专用的电子设备中,实时性很重要
1.2 程序简单示例:
//经典单片机程序void main(){ while(1) { 喂一口饭(); 回一条消息(); }}//RTOS程序int a;喂饭() 栈A{ int b=2; int c; c = a+b;==>1.b+2,2,c=new val --------------------------->切换 while(1) { 喂一口饭(); }}回信息() 栈B{ int b; while(1) { 回一个消息(); }}void main(){ create_task(喂饭); create_task(回信息); start_scheduler(); while(1) { sleep(); }}
1.2 提出问题
什么叫线程?回答这个问题之前,先想想怎么切换线程?怎么保存线程?
- 线程是函数吗?函数需要保存吗?函数在Flash上,不会被破坏,无需保存
- 函数里用到的全局变量,全局变量需要保存吗?全局变量在内存上,还能保存到哪里去?全局变量无需保存
- 函数里用到了局部变量,局部变量需要保存吗?局部变量在栈里面,也是在内存里,只要避免栈被破坏即可,局部变量无需保存
- 运算的中间值需要保存吗?中间值保存在哪里?在CPU寄存器里,另一个线程也要用到CPU寄存器,所以CPU寄存器需要保存
- 保存在哪里?保存在线程的栈里面
- 怎么理解CPU寄存器,怎么理解栈?
2.1 ARM架构及汇编
ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computor),它所用的指令比较简单,有如下特点:
1、对内存只有读、写指令
2、 对于数据的运算是在CPU内部实现
3、 使用RISC指令的CPU复杂度小一点,易于设计
对于比如a= a+b这样的算式,需要经过下面四个步骤才可以实现:
细看这几个步骤,有些疑问:
1、读a,那么a的值读出来后保存在CPU哪里?
2、读b,那么b的值都出来之后保存在哪里?
3、a+b的结果又保存在哪里?
这些问题都涉及到ARM处理器的内部,简单概括如下,我们先忽略各种CPU模式,用户模式等。
CPU运行时,先去取指令,再执行指令
1)把内存a的值读入CPU寄存器R0
2)把内存b的值读入CPU寄存器R1
3)把R0和R1累计存入R0
4)把R0的值写入内存a
CPU内部寄存器分类
CPU内至少应该有数据缓冲寄存器,栈指针类寄存器、程序指针类寄存器、程序状态类寄存器及其他功能寄存器
1、数据缓冲寄存器
CPU内数量最多的寄存器是数据缓冲寄存器,名字用寄存器英文Register的首字母加数字组成,如R0、R1、R2等,不同的CPU其种类不同。
2、栈指针类寄存器
在计算机编程中有全局变量和局部变量的概念。从存储器的角度来看,对一个具有独立功能的完整程序来说,全局变量具有固定的地址,每次读写都是那个地址。而在一个子程序中开辟的局部变量则不同,用RAM中的哪个地址是不确定的,采用“后进先出”的原则使用一段RAM区域,这段区域被称为栈区。它有一个栈底的地址, 是一开始就确定的,当有数据进栈或者出栈时,地址就会连续变动,不然就放到同一个存储地址中了,CPU需要有个地方保存这个不断变化的地址,这就是栈指针(SP)寄存器。
3、程序指针类寄存器
计算机的程序存储在存储器中,CPU中有个寄存器指示将要执行的指令在存储器中的位置,这就是程序指针类寄存器。在许多CPU中,它的名字叫做程序计数器寄存器(PC),它负责告诉CPU将要执行的指令在存储器的什么地方。
4、程序运行状态类寄存器
CPU在进行计算过程中,会出现诸如进位、借位结果为0、溢出等情况,CPU内需要有个地方把他们保存下来,以便下一条指令结合这些情况进行处理,这类寄存器就是程序状态类寄存器,不同的CPU其名称不同,有的叫做标志寄存器,有的叫做程序状态字寄存器。
5、其他功能寄存器
不同的CPU中,除了具有数据缓冲,栈指针、程序指针、程序运行状态寄存器之外、还有表示浮点数运算、中断屏蔽等寄存器。
ARM Cortex-M中的寄存器
ARM Cortex-M处理器的寄存器主要有R0-R15及3个特殊功能寄存器,如上图所示,其中R0-R12为通用寄存器,R13为堆栈指针寄存器(SP)、R14是连接寄存器,R15为程序计数器(PC),特殊功能寄存器有预定义的功能,而且必须通过专用的指令来访问。
几条汇编指令
需要掌握的汇编指令并不多,只有几条。
- 读内存指令:LDR,即Load之意
- 写内存指令:STR,即Store之意
- 加减指令:ADD与SUB
- 跳转:BL,即Branch And Link
- 出栈指令:POP
- 入栈指令:PUSH
汇编并不复杂:
加载/存储指令
加载指令LDR:LDR r0,[addrA]意思就是将地址addrA的内容加载到R0中
存储指令STR: STR r0,[addrA]意思就是将r0的值存储到地址addrA上
加法运算指令ADD:ADD r0,r1,r2意思为:r0=r1+r2
减法运算指令SUB:SUB r0,r1,r2意思为:r0=r1-r2
寄存器入栈/出栈指令
函数运行的本质
如下是一个简单的程序,主函数里调用函数add_val():
void add_val(int *pa,int *pb){ volatile int tmp; tmp = *pa; tmp = tmp + *pb; *pa = tmp;}int main(void){ int a =1 ; int b = 2; add_val(&a,&b); return 0;}
其中调用add_val函数的汇编代码如下: