静态变量在函数中被多次使用,但实际上,它仅仅涉及到了读取操作,实际上并不需要每次都通过总线操作实际读取一次变量的值,因而在开启优化的情况下,编译器所生成的代码仅仅会在函数的一开头将其读取到某个通用寄存器中,并在随后的操作中直接使用对应的通用寄存器进行比较。
关键字 volatile,用以告诉编译器值是经常变化的,应该关闭窥孔优化。
volatile 关键字,告知编译器关闭针对目标变量的窥孔优化。
上下文是一个集合, 包含了任务所有共享资源的“工作现场”。
任务的切换就是上下文的切换。
上下文的切换可以浓缩为栈顶指针的切换。
以 ARM Cortex M 为例,通常我们所说的一个内核是 8 位、 16 位还是 32 位并不是指地址总线的宽度,而是 ALU 操作数的位宽,习惯上又称之为字长。对 8 位机来说, ALU 一次可以进行 8 位运算,当我们针对一个 32 位的数据进行操作时就要拆成 4 次。对 32 位机来说, ALU 一次就可以完成 32 位的运算。比较二者的区别,除了操作次数不同以外还隐含着原子性的信息,即对 8 位机来说,操作 32 位数据要分 4 个步骤来完成,这期间如果发生中断/异常,操作是会被打断的,因此不具备原子性;对 32 位机来说,由于 ALU 一次运算的过程是不可打断的,因而针对 32 位数据的运算天然具有原子性,我们称之为天然原子性
调度算法的时间 + 出入栈的时间 = 任务切换时间
RTOS:调度的时间并不是越快越好,是越准确越好,在准确的基础上越快越好。
编译器通过产生代码实现,是一种软件切换。
- 每次上下文的切换都需要消耗额外的代码资源。函数越多,消耗在切换上的资源也越多。而完全切换是通过公共的切换代码来实现的,消耗在任务切换上的代码资源是固定的。
- 每个函数对上下文的占用是本着“按需分配”的原则进行的,是时间效率最高的上下文切换方式。
- 每个函数只根据自己对上下文的使用情况进行切换,与前后执行的函数/任务无关
“临时切换”在追求处理能力的中高端处理器中是完全由硬件实现的,以获取最快的响应速率。
“每个任务独占整个地址空间”
虚拟地址空间技术:相对原本的实际物理地址空间(又叫做实地址空间)来说的,每个任务都独占一块虚拟地址空间,而虚拟地址空间中的地0x12345678,而实际每个任务的 0x12345678 都被映射在了完全不同的物理地址上。
MMU(存储器管理模块 Memory Management Unit)
MPU(存储器保护单元 Memory Protection Unit)
芯片不具有MMU,线程是操作系统的最小调度单位( RT-Thread、 µCOS、 FreeRTOS、 RTX等)。
线程间的切换:“完全切换“。
“抢占”、“响应速度快”。
ARM Cortex M:SP_main&SP_process。
芯片复位的时候,中断/异常处理程序和用户任务默认使用 SP_main 指针。在操作系统环境下, SP_main 是供中断/异常处理程序专用的,用户任务则使用 SP_process。
来维护自己专属的任务栈
高端的 MCU 为了追求中断/异常处理的响应速度,为中断/异常处理程序提供了完全独立的上下文寄
存器页,这比单纯提供两个 SP 指针更近一步:将原本通过栈作为媒介进行的上下文内容的切换替换为以寄存器页为单位的直接切换,简单说就是用户任务一个寄存器页;中断处理程序一个寄存器页——这样大家终于不用抢了
“主动放弃对处理器的控制,并指认自己的后继者”
__yield()
状态机任务之间共享同一个栈,使用“合作式”上下文切换(部分切换)。
不阻塞系统。
typedef enum {
fsm_rt_on_going = 0, //!<状态量 on going,表示状态机正在执行
fsm_rt_cpl = 1, //!<状态量 complete, 表示状态机执行完毕
} fsm_rt_t;
#define RESET_FSM() do { s_tState = START; } while(0)
//! 状态机编写的非阻塞延时函数
fsm_rt_t delay_fsm( uint32_t wTime )
{
static enum {
START = 0,
WAIT_FOR_DELAY,
} s_tState = START; //!<定义一个状态变量,用以表示状态机当前的状态
static uint32_t s_wDelay; //!<实际用于延时的静态局部变量
switch (s_tState)
{
/* START 状态机的进入“事件”,在状态机复位后首次运行时,运行且仅运行一次,用于初始化
状态机环境(比如状态机用到的各类变量) */
case START:
s_wDelay = wTime; //!<记录要延时的时间长度
s_tState = WAIT_FOR_DELAY; //!<切换状态
//break; //!<使用 fall-through 的方法,提高效率
/* 实际用于延时的状态 */
case WAIT_FOR_DELAY:
if (0 == s_wDelay)
{
RESET_FSM(); //!<复位状态机
return fsm_rt_cpl; //!<说明状态机执行完成
}
s_wDelay --; //!<更新计数器
break;
}
return fsm_rt_on_going; //!<默认情况下返回状态机正在执行
}
fsm_rt_t led_task_fsm(void)
{
static enum {
START = 0,
LED_ON,
DELAY_1,
LED_OFF,
DELAY_2
} s_tState = START;
switch (s_tState)
{
case START: //!< START 作为基本格式保留
s_tState = LED_ON; //!<切换状态
//break;
case LED_ON:
led_on(); //!<点亮 LED
s_tState = DELAY_1; //!<切换状态
break;
case LED_DELAY1:
//! 调用延时子状态机
if (fsm_rt_cpl == delay_fsm( DELAY_500MS ))
{
//! 如果延时完成了
s_tState = LED_OFF; //!<切换状态
}
break;
case LED_OFF:
led_on(); //!<熄灭 LED
s_tState = DELAY_2; //!<切换状态
break;
case LED_DELAY2:
//! 调用延时子状态机
if (fsm_rt_cpl == delay_fsm( DELAY_500MS ))
{
//! 如果延时完成了
RESET_FSM(); //!<复位状态机
return fsm_rt_cpl; //!<返回状态机完成
}
break;
}
return fsm_rt_on_going; //!<默认返回状态机正在执行
}
void main(void)
{
while(1) {
//! 这是一个非阻塞的任务,无论是否在延时,状态机都会很快释放 CPU。
led_task_fsm();
...
}
}
- 进程需要在内核中引入 MMU 的硬件支持,提供完全独占主机的开发环境;
- 线程则是传统嵌入式实时操作系统的最小调度单元,允许程序员在任务中使用阻塞代码,开发较为容易;
- 状态机是裸机环境下最常见的多任务实现形式,尽管资源消耗较小,由于强制程序员必须编写非阻塞代码,因而开发难度较大;
- 协程在资源消耗上与线程接近,开发难度上却与状态机类似(需要手动规划任务的执行细节,甚至很多时候还不如状态机灵活),因而在嵌入式开发中并不常见;
- 几乎在所有的系统中,中断/异常处理都会得到应用,用于专门处理“紧急且重要的事情”