前言
目前成熟的操作系统有很多,但随着版本的不断更新,以及日益增多的功能需求,操作系统的“体积”逐渐增大,对于我这种只想了解其原理的人来说很不友好(尤其是那一堆宏,你都不知道有没有用,也不清楚在哪预定义了)。于是便跟着各路大神,结合实际情况,糅合了一个简化版的“操作系统”,时隔较久,已忘记都参考哪了,在此只是记录。
原理
所有的C语言代码,最后转化为二进制的指令码,实际上都是操作寄存器和内存在一定条件下交换指定的数据。在操作系统中,每个任务拥有自己的内存(栈),但寄存器只有一份(所有任务共享一套硬件寄存器),因此操作系统的实质为:引导任务有序的分时复用寄存器。
这里的分时复用,和中断服务不能说及其相似,只能说“一模一样”。
*中断的执行流为
任务执行--->中断请求--->保护现场--->响应中断--->恢复现场--->任务执行。
*任务的切换流为:
任务1执行--->切换请求--->保护任务1栈--->查找任务--->恢复任务2栈--->执行任务2。
这不就中断的拓展版嘛,接下来我们实现这个流程。
千里之行,始于足下。流程有了,接着我们把环境搭起来,这里先介绍寄存器。
CM3的寄存器组如图所示,我将寄存器分为三组
红色:需要手动保存。
蓝色:中断发送时硬件自动入栈。
绿色:中断发送时硬件自动入栈(SP不用入栈:SP自身即为栈指针)。
自动入栈顺序如下:
1.任务栈保存与恢复
有了寄存器基础,结合前言描述的流程,就可以实现任务栈的保存与恢复了。
具体流程为:
1)在任务切换函数中当前栈指针(此时硬件应该已经自动入栈了当前任务的R0-R3,R2,RL,PC,XPSR)。
2)手动入栈当前任务的R4-R11,调试更新PSP。
3)查找并获取目标任务栈。
4)手动出栈R4-R11,触发中断返回。
5)硬件根据栈指针自动恢复目标的R0-R4,R12,RL,PC,执行目标任务。
__asm void PendSV_Handler(void) { extern threadSwitch; // 保存当前任务的寄存器内容 MRS R0, PSP // 得到PSP R0 = PSP // xPSR, PC, LR, R12, R0-R3已自动保存 STMDB R0!,{R4-R11}// 保存R4-R11共8个寄存器得到当前任务堆栈 bl threadSwitch //选择下一个任务 LDMIA R0!, {R4-R11}// 将任务堆栈中的数值加载到R4-R11中 MSR PSP, R0 // 设置PSP指向此任务 LDR LR, =0xFFFFFFFD // 表明中断返回 设置返回线程模式,使用PSP BX LR // 返回 xPSR, PC, LR, R12, R0-R3会自动的恢复 ALIGN 4 }
任务切换通过PendSV_Handler实现,即手动触发一个切换请求,本系统未完善信号量以及其他IPC,目前只实现定时切换。
/** * @brief 配置pendsvc优先级 * * @return */ __asm void SetPendSVPro(void) { NVIC_SYSPRI14 EQU 0xE000ED22 NVIC_PENDSV_PRI EQU 0xFF LDR R1, =NVIC_PENDSV_PRI /** 将值 NVIC_PENDSV_PRI 加载到R1 */ LDR R0, =NVIC_SYSPRI14 /** 将值 NVIC_SYSPRI14 加载到R0 */ STRB R1, [R0] /** 将 NVIC_PENDSV_PRI保存到NVIC_SYSPRI14 */ BX LR /** 中断返回 */ nop } /** * @brief 触发pendsvc中断 * * @return */ __asm void TriggerPendSV(void) { NVIC_INT_CTRL EQU 0xE000ED04 NVIC_PENDSVSET EQU 0x10000000 LDR R0, =NVIC_INT_CTRL /** 将常量 0xE000ED04 加载到 R0*/ LDR R1, =NVIC_PENDSVSET /** 将常量 0x10000000 加载到 R1 */ STR R1, [R0] /** 将 0x10000000保存到0xE000ED04 */ BX LR /** 中断返回 */ nop }
2.任务选择:
当有任务请求切换时,通过一定的算法,查找下一个任务,本文实现基于系统心跳的可剥夺调度算法。
static void findThread(void) { uint8_t index = 0U; uint32_t tick = sysTick; targTask = crtTask; if (THREAD_STA_READY == tcpSta[crtTask].threadSta) /** 当前任务就绪 */ { for ( index = 0u; index < crtTask; index++) /** 查找更高优先级任务,数值越小,优先级越高 */ { if (tcpSta[index].blockSta) /** 如果该优先级激活 */ { if (THREAD_STA_READY == tcpSta[index].threadSta) /** 如果任务就绪,切换到该任务 */ { targTask = index; break; } else { if (tick >= tcpSta[index].tickOut) /** 如果任务未就绪,查看是否超时,超时也就绪 */ { tcpSta[index].threadSta = THREAD_STA_READY; targTask = index; break; } } } } } else /** 当前任务未就绪 */ { if (THREAD_STA_PENDDING == tcpSta[crtTask].threadSta) /** 任务因超时阻塞 */ { if (tick >= tcpSta[crtTask].tickOut) /** 确认超时则更新为就绪 */ { targTask = crtTask; tcpSta[crtTask].threadSta = THREAD_STA_READY; } } else /** 如果是主动让出,更新为就绪 */ { //tcpSta[crtTask].threadSta = THREAD_STA_READY; } for ( index = crtTask+1U; index < TASK_MAX; index++)/** 查找最高优先级 */ { if (tcpSta[index].blockSta) /** 如果当前优先级有任务注册 */ { if (THREAD_STA_READY == tcpSta[index].threadSta) /** 检查是否就绪 */ { targTask = index; break; } else { if (tick >= tcpSta[index].tickOut) /** 超时则任务就绪 */ { targTask = index; tcpSta[targTask].threadSta = THREAD_STA_READY; break; } } } } } if (targTask != crtTask) /** 如果需要切换任务 */ { crtTask = targTask; /** 更新任务 */ crtTcp = &tcpSta[crtTask]; /** 更新控制块 */ } else { /** 没有更高优先级任务,且本任务未就绪,执行空闲任务 */ if ((THREAD_STA_PENDDING == tcpSta[crtTask].threadSta) || (THREAD_STA_YIDLE == tcpSta[crtTask].threadSta)) { crtTask = TASK_MAX-1U; crtTcp = &tcpSta[crtTask]; } } } /** * @brief 任务切换 * * @param psp 当前任务栈 * @return uint32_t* 目标任务栈 */ uint32_t* threadSwitch(uint32_t* psp) { static uint8_t firstSwitch = 1U; uint32_t *ret = psp; if (firstSwitch) /** 第一次运行任务,默认为空闲任务 */ { crtTcp = &tcpSta[IDLE_TASK_PRIORITY]; firstSwitch = 0; } else /** 记录当前任务栈 */ { crtTcp->stackPtr = psp; findThread(); /** 查找最高优先级任务 */ } ret = crtTcp->stackPtr; /** 获取任务栈地址 */ return ret; }
3.应用
创建几个不同优先级的任务,以系统定时器为心跳,定时检查任务优先级并执行切换。
threadCreate(LED1_TASK_PRIORITY,task0); threadCreate(LED2_TASK_PRIORITY,task1); threadCreate(OLED_TASK_PRIORITY,OledTask); threadCreate(UART_TASK_PRIORITY,Uart2Task); threadStart();
总结
本文有点仓促,很多细节没有体现,一个原因是时间隔得久,有点忘了,主要原因是现在各种RTOS都很成熟,我也没有将这个系统继续完善的计划,就当是备忘录吧。