BasicOS
狗哥出品,基于“共享栈”的协作式调度内核;BasicOS
一切都是为了用“RTOS的任务”的形式写MCU任务;【可以,但是必要不大】
任务切换时需要处理任务栈,有别于ProtoThread的Superloop(唯一栈)本质;
有栈协程 and 无栈协程
无栈协程
对比
有栈协程 (BasicOS)
每个任务配备独立私有栈,切换时完整保存所有通用寄存器、栈指针 SP、程序断点 PC 与函数局部变量;
可在任意嵌套函数内暂停 / 恢复,运行时动态记录断点位置,进出点不固定。无栈假协程 (ProtoThread,while(1)循环)
ProtoThread基于编译期静态状态机实现(语法糖,),仅用 1 个状态变量(line 行号)记录执行分支;
while(1)循环的内部函数可通过轮询状态机的形式实现假并发(软件定时器的并发);
不保存寄存器、PC、局部变量(使用全局变量保存变量持续化的数据),所有暂停 / 恢复分支地址编译时固定;
资源开销最小。
轮流执行,round-robin执行方式,前后顺序就是优先级;
内部可使用状态机实现进入点和退出点的有限控制;无栈真协程 (ctx)
无独立任务栈,共用全局主栈;
使用语法糖的形式保证了局部变量的持续化;
不是通过轮询的形式来;
仅在全局内存保存 PC 断点与少量核心寄存器,无法在嵌套子函数中 yield;?
运行时动态记录断点地址,进出点不固定,RAM 占用极低。?
大佬总结
大佬的糖
协作式调度
大家文明排队,轮到你就一直跑,
只有当你自己说 “我好了,可以换人”,调度器才能换下一个;
没人能强制把你赶下来。
CPU 使用权只能由任务主动释放,内核不会强行剥夺
高优先级任务无法打断正在运行的任务;
只有当前运行任务主动让出 CPU,调度器才有机会切换其他就绪任务。
任务一旦拿到 CPU,会持续运行直到:
- 主动调用 yield() / 任务切换接口; 【suspend】
- 主动阻塞(延时、等待信号量、等待消息); 【blocked】
- 任务执行完毕退出。
调度时机完全受控于任务代码,中断不会引发任务上下文切换
中断可以标记任务就绪,但中断返回后一定回到被打断的原任务,不会立刻切任务;
真正切换必须等到任务主动让出。
不需要复杂的临界区保护(相比抢占式)
不存在 “任务在任意指令中间被打断”,只要不在共享资源访问中间调用 yield,天然不会出现任务并发争抢破坏数据。
各线程是线性执行,不存在各线程间的资源共享;
中断都在任务栈中
不像freertos一进入中断就切换到中断栈;
因为freertos在中断中可能会调度任务;
共享栈的情况,中断都在任务栈中处理;没有独立中断栈
风险
共享资源要“一次性”处理
没有一次性处理完“共享资源”
不要苛求系统实时性
1.【考验程序设计】如果一个任务长时间运算、迟迟不 yield,外部事件对应的任务迟迟得不到调度。
例如按键检测任务需要 10ms 响应,但是后台运算任务连续运行几百毫秒不释放 CPU,按键响应严重滞后。
长时间循环内部必须周期性插入 yield,防止独占 CPU;
2.中断里“唤醒”高优先级任务【任务已经到了就绪时候】,但是当前正在运行的任务不肯 yield,新就绪任务只能==“礼貌”==排队等待。
实时需求强的场景很难满足。
3.TaskA(低优先级)正在运行
TaskB(高优先级)已经就绪
只要 TaskA 不主动 yield,高优先级 B 一直无法调度。
即使 B 事件紧急,也必须等 A 自愿放手。
只有yeild没有delay:导致任务一有空就运行,cpu没得歇
yield() 的定位:主动临时让出 CPU,不进入阻塞;任务保持「就绪态」
delay / 延时(sleep):让出 CPU + 进入「阻塞等待态」,到期才重新就绪
饥饿问题?
任务1 优先级高
任务2 3 优先级低一些且一致;
当任务2好了之后,任务1立马就绪,
有没有可能到值任务3是始终无法执行
高优先级任务空闲时必须进入阻塞态(block),而不是挂起态(suspend)
固定时间片-任务环 Round-Robin
运行固定时间后,强制切换上下文;
共享栈
栈大小动态调整:将有限的栈空间给正在运行的线程使用;其他栈保持最小尺寸;
缺点:增加栈空间调整的处理;
优点:省资源;用时间换空间;
export
把一个任务的“配置部分”和“实时数据”部分 分开
| 任务数据 | 内容 | 存储位置 |
|---|---|---|
| 配置部分 | 任务入口、任务优先级、任务名称、参数地址; | const rom段 |
| 实时数据 | 任务栈指针、任务栈地址、栈大小、超时时间; | ram |
将"配置部分"部分使用magic_word包裹;
实现初始化动态判断任务个数;
并定义“内核任务”,作为整个任务的链表头,用于比遍历;
对比其他export 使用段的形式,定义该段数据统一处理方法
并使用段开始标记和段结束标记,集中判断;
移植
初始化栈
保存寄存器:PS PSW AX BC DE HL CS
BRK中断
上下文保存
入栈push寄存器
“栈空间的调整”
出栈pop寄存器
栈空间的调整
注意往前拷贝和往后拷贝有区别;