【RTX操作系统教程】第7章 任务管理

标签:
cosiiiemwinrtxstm32f407安富莱 |
分类: RTX及其中间件 |
第7章
任务管理
对于初学者,特别是对于没有RTOS基础的同学来说,了解RTX的任务管理非常重要,了解任务管理的目的就是让初学者从裸机的,单任务编程过渡到带OS的,多任务编程上来。搞清楚了这点,那么RTX学习就算入门了。
本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407。
7.1 单任务系统
7.2 多任务系统
7.3 任务设置
7.4 任务栈设置
7.5 系统栈设置
7.6 栈溢出检测
7.7 RTX初始化和启动
7.9 任务删除
7.10 空闲任务
7.11实验例程说明
7.12
7.1
单任务系统
学习多任务系统之前,我们先来回顾下单任务系统的编程框架,即裸机时的编程框架。裸机编程主要是采用超级循环(super-loops)系统,又称前后台系统。应用程序是一个无限的循环,循环中调用相应的函数完成相应的操作,这部分可以看做后台行为,中断服务程序处理异步事件,这部分可以看做是前台行为。后台也可以叫做任务级,前台也叫作中断级。
图7.1 单任务系统
对于前后台系统的编程思路主要有以下两种方式:
7.1.1
查询方式
对于一些简单的应用,处理器可以查询数据或者消息是否就绪,就绪后进行处理,然后再等待,如此循环下去。对于简单的任务,这种方式简单易处理。但大多数情况下,需要处理多个接口数据或者消息,那就需要多次处理,如下面的流程图所示:
7.1.2
中断方式
对于查询方式无法有效执行紧急任务的情况,采用中断方式就有效的解决了这个问题,下面是中断方式简单的流程图:
采用中断和查询结合的方式可以解决大部分裸机应用,但随着工程的复杂,裸机方式的缺点就暴露出来了
u
l
l
u
l
u
l
l
l
u
l
l
7.2
多任务系统
针对这些情况,使用多任务系统就可以解决这些问题了。下面是一个多任务系统的流程图:
多任务系统或者说RTOS的实现,重点就在这个调度器上,而调度器的作用就是使用相关的调度算法来决定当前需要执行的任务。如上图所画的那样,创建了任务并完成OS初始化后,就可以通过调度器来决定任务A,任务B和任务C的运行,从而实现多任务系统。另外需要初学者注意的是,这里所说的多任务系统同一时刻只能有一个任务可以运行,只是通过调度器的决策,看起来像所有任务同时运行一样。为了更好的说明这个问题,再举一个详细的运行例子,运行条件如下:
u
u
u
u
下图7.2所示是任务的运行过程,其中横坐标是任务优先级由低到高排列,纵坐标是运行时间,时间刻度有小到大。
图7.2 多任务系统运行过程
(1)
(2)
(3)
(4)
(5)
(6)
(7)
(8)
(9)
(10)
(11)
通过上面实例的讲解,大家应该对多任务系统完整的运行过程有了一个全面的认识。随着教程后面对调度器,任务切换等知识点的讲解,大家会对这个运行过程有更深刻的理解。
RTX就是一款支持多任务运行的实时操作系统,具有时间片,抢占式和合作式三种调度方法。通过RTX实时操作系统可以将程序函数分成独立的任务,并为其提供合理的调度方式。同时RTX实时操作系统为多任务的执行提供了以下重要优势:
u
u
u
u
u
u
u
图7.3 RTX中任务通信
7.3
任务设置
RTX操作系统的配置工作是通过配置文件RTX_Conf_CM.c实现。在MDK工程中打开文件RTX_Conf_CM.c,可以看到如下图7.4所示的工程配置向导:
图7.4 RTX配置向导
用于任务配置的主要是如下两个参数:
l
参数范围0 – 250
表示同时运行的最大任务数,这个数值一定要大于等于用户实际创建的任务数,空闲任务不包含在这个里面。比如当前的数值是6,就表示用户最多可以创建6个任务。
l
参数范围0 – 250
表示自定义任务堆栈的任务数,如果这个参数定义为0的话,表示所有的任务都是使用的配置向导里面第三个参数Task statck size大小。比如:
Number of concurrent running tasks = 6
Number of tasks with user-provided stack = 0
表示允许用户创建6个任务,所有的6个任务都是分配第三个参数Task statck size大小的任务堆栈空间。
Number of concurrent running tasks = 6
Number of tasks with user-provided stack = 3
表示允许用户创建6个任务,其中3个任务是用户自定义任务堆栈大小,另外3个任务是用的第三个参数Task statck size大小的任务堆栈空间。
7.4
任务栈设置
不管是裸机编程还是RTOS编程,栈的分配大小都非常重要。局部变量,函数调时现场保护和返回地址,函数的形参,进入中断函数前和中断嵌套等都需要栈空间,栈空间定义小了会造成系统崩溃。
裸机的情况下,用户可以在这里配置栈大小:
u
u
不同于裸机编程,在RTOS下,每个任务都有自己的栈空间。任务的栈大小可以在配置向导中通过如下参数进行配置:
需要大家注意的是,默认情况下用户创建的任务栈大小是由参数Task stack size决定的。如果觉得每个任务都分配同样大小的栈空间不方便的话,可以采用自定义任务栈的方式创建任务。采用自定义方式更灵活些。
实际应用中给任务开辟多大的堆栈空间合适呢,这时可以事先给任务开辟一个稍大些的堆栈空间,然后通过第三章3.4小节中介绍的RTX调试方法可以显示任务栈的使用情况,从而调试实际给任务开辟多大的栈空间比较合适。
RTX的任务切换和中断嵌套对栈空间的影响,待我们讲解RTX的任务切换和双堆栈指针章节(此章节在后期RTX教程升级版本时再配套)时再细说。这部分知识点也非常重要,对于初学者,先搞懂这里讲解的知识点即可。
7.5
系统栈设置
上面跟大家讲解了什么是任务栈,这里的系统栈又是什么呢?裸机的情况下,凡是用到栈空间的地方都是用的这里配置的栈空间:
u
u
在RTOS下,上面两个截图中设置的栈大小有了一个新的名字叫系统栈空间,而任务栈是不使用这里的空间的。任务栈不使用这里的栈空间,哪里使用这里的栈空间呢?答案就在中断函数和中断嵌套。
对于这个问题,简单的描述如下,更详细的内容待我们讲解RTX任务切换和双堆栈指针时再细说(此章节在后期RTX教程升级版本时再配套)。
u
u
l
对于Cortex-M3内核和未使用FPU(浮点运算单元)功能的Cortex-M4内核在发生中断时需要将16个通用寄存器全部入栈,每个寄存器占用4个字节,也就是16*4 = 64字节的空间。
可能发生几次中断嵌套就是要64乘以几即可。当然,这种是最坏执行情况,也就是所有的寄存器都入栈。
(注:任务执行的过程中发生中断的话,有8个寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余寄存器入栈以及发生中断嵌套都是用的系统栈)。
l
对于具有FPU(浮点运算单元)功能的Cortex-M4内核,如果在任务中进行了浮点运算,那么在发生中断的时候除了16个通用寄存器需要入栈,还有34个浮点寄存器也是要入栈的,也就是(16+34)*4 = 200字节的空间。当然,这种是最坏执行情况,也就是所有的寄存器都入栈。
(注:任务执行的过程中发送中断的话,有8个通用寄存器和18个浮点寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余通用寄存器和浮点寄存器入栈以及发生中断嵌套都是用的系统栈。)。
7.6
栈溢出检测
如果怕任务栈溢出,那么此功能就非常的有用了,用户只需在RTX的配置向导里面使能使用任务栈检测即可:
如果调试过程中某任务的任务栈溢出的话,会在这里有提示:
(注:实际测试发现,不使能此功能,在调试状态下也能够正确的显示任务栈溢出,待进一步测试)。
7.7
RTX初始化和启动
使用如下3个函数可以实现RTX的初始化:
u
u
u
关于这3个函数的讲解及其使用方法可以看教程第3章3.3小节里面说的参考资料rlarm.chm文件
这里我们重点的说一下函数os_sys_init_user函数,因为从本章节开始所有的例子都是用的这个函数作为RTX的初始化。
函数原型:
void os_sys_init_user (
函数描述:
函数os_sys_init_user用于实现RTX操作系统的初始化和启动任务的创建,并且使用这个函数做初始化还可以自定义任务栈的大小。
u
u
u
u
使用这个函数要注意以下几个问题
1.
2.
3.
4.
使用举例:
int main (void)
{
}
7.8
任务创建
使用如下4个函数可以实现RTX的任务创建:
u
关于这4个函数的讲解及其使用方法可以看教程第3章3.3小节里面说的参考资料rlarm.chm文件
这里我们重点的说一下函数os_tsk_create_user函数,因为从本章节开始所有的例子都是用的这个函数作为RTX的任务创建。
函数原型:
OS_TID os_tsk_create_user(
函数描述:
函数os_tsk_create_use用于实现RTX操作系统的任务创建,并且还可以自定义任务栈的大小。
u
u
u
u
u
使用这个函数要注意以下问题
1.
使用举例:
static uint64_t
AppTaskUserIFStk[512/8];
OS_TID HandleTaskUserIF = NULL;
static void AppTaskCreate (void)
{
}
7.9
任务删除
使用如下2个函数可以实现RTX的任务删除:
关于这2个函数的讲解及其使用方法可以看教程第3章3.3小节里面说的参考资料rlarm.chm文件
这里重点的说一下函数os_tsk_delete,因为从本章节开始所有的例子都是用的这个函数作为RTX的任务删除。
函数原型:
OS_RESULT os_tsk_delete (
函数描述:
函数os_tsk_create_use用于实现RTX操作系统的任务删除
u
u
使用这个函数要注意以下问题:
1.
使用举例:
static uint64_t
AppTaskUserIFStk[512/8];
OS_TID HandleTaskUserIF = NULL;
static void AppTaskDelete (void)
{
}
7.10空闲任务
几乎所有的小型 RTOS 中都会有一个空闲任务,空闲任务应该属于系统任务,是必须要执行的,用户程序不能将其关闭。不光小型系统中有空闲任务,大型的系统里面也有的,比如WIN7,下面的截图就是 WIN7中的空闲进程。
空闲任务主要有以下几个作用:
u
u
RTX操作系统的空闲任务在文件RTX_Conf_CM.c文件里面,源代码如下:
__task void os_idle_demon (void) {
}
7.11实验例程说明
7.11.1STM32F103开发板实验
配套例子:
V4-402_RTX实验_任务创建和删除
实验目的:
1. 学习RTX的任务创建和删除。
实验内容:
RTX配置:
RTX配置向导详情如下:
u
l
允许创建4个任务,实际创建了如下四个任务
l
创建的4个任务都是采用自定义堆栈方式。
RTX任务调试信息:
程序设计:
u
static
uint64_t AppTaskUserIFStk[512/8];
static
uint64_t
AppTaskLEDStk[256/8];
static
uint64_t AppTaskMsgProStk[512/8];
static
uint64_t
AppTaskStartStk[512/8];
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
u
u
int main (void)
{
}
u
static void AppTaskCreate (void)
{
}
u
__task void AppTaskUserIF(void)
{
}
__task void AppTaskLED(void)
{
}
__task void AppTaskMsgPro(void)
{
}
__task void AppTaskStart(void)
{
}
7.11.2STM32F407开发板实验
配套例子:
V5-402_RTX实验_任务创建和删除
实验目的:
1. 学习RTX的任务创建和删除。
实验内容:
RTX配置:
RTX配置向导详情如下:
u
l
允许创建4个任务,实际创建了如下四个任务
l
创建的4个任务都是采用自定义堆栈方式。
RTX任务调试信息:
程序设计:
u
static
uint64_t AppTaskUserIFStk[512/8];
static
uint64_t
AppTaskLEDStk[256/8];
static
uint64_t AppTaskMsgProStk[512/8];
static
uint64_t
AppTaskStartStk[512/8];
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
u
u
int main (void)
{
}
u
static void AppTaskCreate (void)
{
}
u
__task void AppTaskUserIF(void)
{
}
__task void AppTaskLED(void)
{
}
__task void AppTaskMsgPro(void)
{
}
__task void AppTaskStart(void)
{
}
7.12总结
任务管理中涉及到的API是RTX的基本操作函数,初学者要熟练的掌握,另外任务栈和系统栈也要随着后面的学习搞清楚。