YueOS 开发日志 (1) - 内核篇:基础概念
基础结构
本系列属于开发日志,随着开发的深入某些概念/思想可能发生改变。因此,一旦发生前后文冲突,以后文为准;笔者会尽可能标注出准确定义的位置。
HAL 库标准启动流程
-
当系统上电时,由 reset 异常触发异常处理。
1.1. CortexM4 内核自动从 0x00000000 处读取异常向量表首地址(一般为 0x08000000),即 *__initial_sp
1.2 将 MSP 置位后,继续从向量表中读取并置位 PC 初值。(一般表现为进入 startup.s)注意: 笔者在 Ozone 调试时发现复位后有 SP=0x2000 3E30, PC=0x0800 03C4; 同时命令行输出为:
Target.ReadU32 (0x08000000); // returns 0x4, data is 0x20003E30
Target.SetReg (“SP”, 0x20003E30);
Target.ReadU32 (0x08000004); // returns 0x4, data is 0x80003C5
Target.SetReg (“PC”, 0x80003C5)根据 CortexM4 文档易知 C5 表示处理器为 Thumb 状态,而 PC 寄存器却指示为 0x0800 03C4 这里有可能是 Ozone 自动将指示位去掉了。
-
startup.s
2.1 使用汇编语言引导进入 SystemInit 函数(在system_stm32f4xx.c实现), 进行系统初始化。该函数需要完成三件事:
2.1.1 初始化 FPU 。
2.1.2 初始化额外存储空间 。(外置 RAM)
2.1.1 初始化 VTOR 。(向量表偏移,见 cortexM4 学习笔记)必须定义器件宏,如 STM32F407xx 。该文件需要依赖该宏进行有选择的初始化。
2.2 使用汇编语言引导进入 main 函数,自此进入 C 的世界。在 main 中需要进行初始化的顺序:
2.2.1 HAL_Init
2.2.2 SystemClock_Config
2.2.3 片上外设初始化(MX_GPIO_Init)
2.2.4 用户初始化
2.2.5 while(1)
基础概念
任务与任务切换
任务
-
重要任务:
重要任务是开发的核心需求,因为“这个需求”才有了这个开发行为。
重要任务必须是周期性的,一旦激活则系统会尝试精准调度。 -
次要任务:
次要任务是开发核心需求时所产生的一些辅助行为。 -
内核任务:
内核任务是操作系统最核心的任务,由该类任务来调度并处理一切其他任务。(注意: 最核心不代表最重要)
内核任务需要完成的任务:
+ 任务调度
+ 中断处理
+ 维持系统组件的正常运行(如系统时间)
举例:开发某电机控制器。那核心需求就是,这个电机一定是处于被控制状态的,绝对不允许出现跑飞或失控状态。如果产生了一些异常,我希望能将这些异常记录下来,那记录这个行为就是次要任务。
跑飞(定义): 由于未知原因,在没有任何内核异常被触发的情况下,任务无法释放处理器。
对于主要任务,其跑飞表现为不能释放处理器,致使看门狗被触发。
对于次要任务,其跑飞表现为总执行时间超过系统预计时间的 2 倍。
对于内核任务,其跑飞表现为不能释放处理器,致使看门狗被触发。(绝对不允许这种情况发生)
失控(定义):由于程序设计问题,重要/次要任务产生了非预期行为。这不是由内核导致的。
错误(定义):当执行某些操作时,产生了会使预定目标无法实现的行为。如果这个行为能被正常处理,则称其为错误;若不能正常处理(依旧按照未发生错误的情况继续运行),则被归类为失控。
注意: 错误是一种正常运行情况。
任务切换
本操作系统不采用时间片轮转式的调度方法。
重要任务
-
重要任务的切入:
以任一个重要任务第一次开始执行的时间为基准,所有重要任务都会被尝试周期性进入。因为控制算法往往具有积分环节,对时间控制有一定要求,所以重要任务会使用 DWT 为时基。
-
重要任务的切出:
重要任务执行时,除看门狗、fault外所有可屏蔽中断都将被屏蔽,并延迟至内核任务时处理。重要任务只有在出现 Fault 时才会被动切出,换言之,任何正常运行的情况下重要任务都不会被动释放处理器。- 当出现 fault 时,在调试环境下,内核会执行急停任务并进入锁死状态以保证开发人员解决问题;在应用环境下,则内核将会记录并尝试处理问题,若无法解决则内核会执行急停任务并死锁发出报警。
重要任务的主动切出存在两种情况:(以传感器多段数据采集,并在采集结束后进行数据处理为例)
-
暂时释放:
裸机情况:触发 DMA 发送信息序列后,异步等待接收中断。并在多段数据均收到后,触发控制算法。
本内核情况:
1. 触发 DMA 发送信息序列
2. 等待中断事件触发(释放,调度进入次要任务)
3. (触发后)回到重要任务,进行阶段数据解算
4. 重复上述过程,直至全部接收。
5. 执行控制算法。 -
执行结束:
本轮算法完成,通知内核将该任务挂起。
多个重要任务不允许出现交替执行的情况(因为交替执行会出现不稳定的时间顺序)。当一个重要任务被暂时释放时,一定是去执行次要任务的。
暂时释放时,将存在一个参数,这个参数决定了系统调度的行为(预调度(给定值),预调度(由系统决定),被动调度)。
次要任务
-
次要任务的切入:
任何具有足够时间长度的 IDLE 时间段,都将被切换进入次要任务。 -
次要任务的切出:
被中断事件打断,或主动释放。
内核任务
-
内核任务的切入:
任何重要/次要任务的切出,都将切入内核任务。 -
内核任务的切出:
完成系统内定任务后,切换进重要/次要任务。
进程 0
进程 0 是一个特殊的内核任务。系统开始运作后,执行的第一个任务即为进程 0 。
进程 0 负责系统的初始化,且所有空闲时间都将被进程 0 占据。
