从零开始构建嵌入式实时操作系统1——任务切换

1.前言

随着计算机技术和微电子技术的迅速发展,嵌入式系统应用领域越来越广泛,尤其是其具备低功耗技术的特点得到人们的重视。随着工信部提出NB-IoT基站建设具体目标、三大运营商加速建设,即将迎来万物互联的新时代,这是信息产业继移动互联网之后的下一个万亿级市场,这些为实时操作系统的应用提供了广阔的前景。

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

2.嵌入式实时操作系统

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

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

1、任务调度子系统
2、任务通信子系统
3、内存管理子系统

在这里插入图片描述

3.实现目标

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

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

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

4.实验环境

硬件是基于意法半导体的STM32F401(ARM公司的Cortex-M4内核),软件开发使用的是KEIL V5.2 开发工具。
在这里插入图片描述
软件工程如下:
在这里插入图片描述
软件工程中包含:main.c ,startup_stm32f401xc.s 和 readme三个文件。startup_stm32f401xc.s文件为STM32F401的启动文件,main.c文件实现任务切换功能,readme文件用于记录版本修改日志。

5.代码实现

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

1、每个任务的独立栈空间。
2、实现任务的暂停和恢复。
3、实现任务的调度。

5.1实现独立栈空间
栈空间代码如下:
在这里插入图片描述
为每个任务定义一个静态数组,当任务运行时将处理器的栈指针指向任务“自己的”静态数组,从而实现独立栈空间。栈空间用来存放局部变量,中断调用和函数调用时的处理器寄存器的值。任务切换时需要将处理器寄存器的值保存到任务的独立栈空间。

在保存任务运行状态时需要保存处理器寄存器值到栈空间,因此需要深入了解处理器寄存器的用途和出入栈顺序,Cortex-M4内核的寄存器和寄存器中断自动入栈的顺序图如下:
在这里插入图片描述

初始化栈空间的代码如下:
在这里插入图片描述

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

栈是一中先入后出的数据结构,Cortex-M4内核的栈操作方式倍设置成了向下生长。psp_array用于保存任务栈指针,psp_array[0]任务0栈指针指向task0_stack[112],其中task0_stack[116]保存PC程序指针值,task0_stack[117]保存状态寄存器(符合Cortex-M4内核寄存器出栈顺序:手动出栈8个寄存器,硬件自动出栈8个寄存器)。

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

cortex-M4内核有一个PendSV(可挂起的系统调用)异常,其异常编号为14并且具有可编程的优先级。当软件将PendSV设置成挂起时,程序将进入PendSV异常(中断)。
将PendSV异常优先级设置为最低,其它中断函数都可以得到正常响应,不会受到PendSV异常影响,在PendSV异常中执行任务切换,时序框图如下:
在这里插入图片描述
PendSV_Handler为Cortex-M4内核中断服务函数,进入中断函数时处理器自动保存了R0,R1,R2,R3, R12,LR,PC,XPSR,在PendSV_Handler中断程序中完成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] 

在PendSV_Handler中断程序中完成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中断,退出SysTick_Handler中断程序时进入PendSV_Handle中断程序。

6.运行结果

代码仿真运行如下:
在这里插入图片描述在这里插入图片描述
task_num0和task_num1这两个变量依次自加,代码实现任务轮流切换功能。

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

未完待续…
实时操作系统系列将持续更新
创作不易希望朋友们点赞,转发,评论,关注。
您的点赞,转发,评论,关注将是我持续更新的动力
作者:李巍
Github:liyinuoman2017
CSDN:liyinuo2017
今日头条:程序猿李巍

版权声明:本文为CSDN博主「liyinuo2017」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/li_man_man_man/article/details/123026433

生成海报
点赞 0

liyinuo2017

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

嵌入式实时操作系统10——系统时钟节拍

1.系统节拍是什么 时间管理在操作系统内核中占有非常重要的地位,操作系统内核中有大量基于时间驱动的功能。有些任务是需要周期执行,比如一个软件定时器需要一秒钟周期性运行100次;有些功能任务需要延时一段

rt_thread hc32f460开发四:pwm驱动移植

RT-Thread 驱动开发简介 RT-Thread驱动开发最开始应该是要阅读官方的文档,理解驱动的运行原理和使用方法。PWM部分的文档在这里https://www.rt-thread.org/document/site/#/rt

合宙Air103 LuatOS开发小试

一、简介 使用LuatOS开发Air103,先点个灯熟悉流程。可以使用官方建议的vs code的LuatIDE进行开发会比较方便;  但是由于用Lua脚本语言开发,编辑器显得不那么重要了,

智慧农业接入复合式树莓派网关(一)

一、功能概述 ​树莓派网关采集485温湿度传感器以及485门磁开关状态数据上报到云端;同时收到云端的指令,树莓派网关处理之后,控制继电器动作,同时继电器返回当前的状态给云端&#xff1b