0
点赞
收藏
分享

微信扫一扫

从零开始写实时操作系统1——任务切换

像小强一样活着 2022-02-20 阅读 85

1.前言

嵌入式实时操作系统将会部署到越来越多的设备中,这就要求工程师深入地了解嵌入式实时操作系统。本系列文章将和大家一起从零开始构建一个嵌入式实时操作系统,我们将用最简单直白的方式一步一步搭建,搭建中的每个节点阶段我们将以一篇文章来总结每个阶段,并开源源代码和工程。

2.嵌入式实时操作系统

嵌入式实时操作系统是一个特殊的程序,是一个支持多任务的运行环境。嵌入式实时操作系统最大的特点就是“实时性”,如果有一个任务需要执行,实时操作系统会立即执行该任务,不会有较长的延时。典型的实时操作系统有uCOS ,RT-Thread,FreeRTOS ,VxWorks,WinCE等。
在这里插入图片描述
**嵌入式实时操作系统是一个特殊的程序(通常称为内核),它可以创建,销毁,控制所有任务。**嵌入式实时操作系统除了包含一个内核以外,还提供其他服务,如文件系统,协议栈,图形用户界面等。本文的重点在于了解嵌入式实时操作系统内核的工作原理和结构,因此文中提到的实时操作系统通常指的是操作系统内核。实时操作系统内核通常要占用5%左右的CPU运行时间,另外内核是一个软件代码,需要额外占用ROM空间和RAM空间。

嵌入式实时操作系主要由以下3个子系统组成:

在这里插入图片描述

3.实现目的

本文讲解构建嵌入式实时操作系统的第一个节点阶段:实现简单的任务切换功能。
由于代码区的数据是不变的,处理器寄存器的值和栈空间的值决定程序运行状态。让每个任务“独享”一个栈空间,当我们将任务运行时的处理器寄存器的值保存起来时,这样就实现保存任务的运行状态。同样的当我们把保存的任务运行时的处理器寄存器的值装载到处理的寄存器中时,这样就恢复了任务的运行状态,任务继续运行起来。

切换任务的原理是:每个任务有一个“独享”栈空间,通过保存和装载任务运行时的处理器寄存器的值,实现任务的暂停和恢复运行。暂停一个任务后再恢复另外一个任务就完成了一次任务切换。

任务代码,任务栈空间和处理器状态如下图:
在这里插入图片描述

4.实验环境

硬件系统是基于意法半导体的STM32F401系列MCU,改处理器使用的是ARM公司的Cortex-M4内核。软件开发使用的是KEIL V5.2 开发工具。
在这里插入图片描述
软件工程如下:
在这里插入图片描述

5.代码实现

切换任务的原理是让每个任务都有一个“独享”栈空间,通过保存和装载任务运行时的处理器寄存器的值,实现任务的暂停和恢复运行。暂停一个任务后再恢复另外一个任务就完成了一次任务切换。
因此需要实现:

5.1实现独立栈空间
栈空间代码如下:
在这里插入图片描述
为每个任务定义一个静态数组,当任务运行时将处理器的栈指针指向任务“自己的”静态数组。

Cortex-M4内核的寄存器如下图:
在这里插入图片描述
初始化栈空间的代码如下:
在这里插入图片描述

栈空间初始化后的状态如下:
在这里插入图片描述

psp_array[0] = ((uint32_t)task0_stack) + (sizeof task0_stack) - 16*4;   	/* 将任务psp栈指针指向任务栈底部*/	
*( (uint32_t *) ( psp_array[0] +(14<<2) ) )=  (unsigned long) task0; 		/* 初始化任务栈中的程序寄存器 */
*( (uint32_t *) ( psp_array[0] +(15<<2) ) )=  0x01000000; 					/* 初始化任务栈中的XPSR*/

代码实现psp_array[0]指向task0_stack[112],task0_stack[116]保存PC程序指针值,task0_stack[117]保存状态寄存器。

5.2实现任务的暂停和恢复
代码如下:
在这里插入图片描述

cortex-M4核有一个PendSV(可挂起的系统调用)异常,其异常编号为14并且具有可编程的优先级。当软件将PendSV设置成挂起时,程序将进入PendSV异常(可中断)。将PendSV异常优先级设置为最低,这样其他的中断函数都可以正常响应中断,不会受到PendSV异常影响,在PendSV异常中执行任务切换时序框图如下:
在这里插入图片描述
PendSV_Handler为Cortex-M4内核中断服务函数,进入中断函数时处理器自动保存了R0,R1,R2,R3, R12,LR,PC,XPSR,因此只需要保存R4~R11就实现了寄存器保存工作。

/* 读取当前进程栈指针数值 */
MRS R0,PSP                        	
/* 保存R4-R11八个寄存器的值到当前任务栈中  同时将回写的地址写入R0 */
STMDB R0!,{R4-R11} 

psp_array[0]为任务0的栈指针, psp_array[1]为任务1的栈指针。以下代码实现任务栈指针切换

/* 读取psp_array 地址 */
LDR R3, =__cpp(&psp_array)         
/* 将当前进程PSP指针值 写入 相应的 PSP_array 位置  */
STR  R0,[R3,R2,LSL #2]             
/* 获取下个进程序号 */
LDR R4,=__cpp(&next_task)          
LDR R4,[R4]
/* R1为&curr_task   将下个进程序号写入curr_task中 */
STR R4,[R1]                        
/* psp_array读取更新后的curr_task的PSP指针数值 */
LDR R0,[R3,R4,LSL #2] 

程序出栈R4~R11寄存器,PendSV_Handler中断程序返回时处理器自动出栈R0,R1,R2,R3, R12,LR,PC,XPSR。

/* 出栈 R4-R11八个寄存器 */
LDMIA R0!,{R4-R11}                 
/* 设置PSP指针 */
MSR PSP,R0	
/* 中断返回 */
BX LR 

5.3实现任务的调度
代码如下:
在这里插入图片描述
SysTick_Handler为定时器中断程序实现轮流改变目标任务,并挂起PendSV_Handle中断。

6.运行结果

代码仿真运行如下:
在这里插入图片描述在这里插入图片描述
代码仿真验证结果为:实现任务轮流切换。

希望获取源码的朋友们在评论区里留言。

举报

相关推荐

0 条评论