论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514
第13章 任务调度—抢占式,时间片和合作式
本章教程为大家将介绍ThreadX操作系统支持的任务调度方式:抢占式,时间片和合作(轮询)式,这部分算是ThreadX操作系统的核心了。对于初学者来说,要一下子就能够理解这些比较困难,需要多花些时间把这些基本概念搞清楚,然后阅读下源码,深入理解实现方法。
13.1 关于合作式调度器的特别说明
13.2 ThreadX支持的调度方式
13.3 什么是调度器
13.4 抢占式调度器
13.5 时间片调度器
13.6 实验例程说明
13.7 总结
13.1 ThreadX支持的调度方式
ThreadX操作系统支持三种调度方式:抢占式调度,时间片调度和合作(轮询)式调度。实际应用主要是抢占式调度和时间片调度,合作(轮询)式调度用到的很少。
- 抢占式调度
每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的API函数,比如tx_thread_sleep。
- 时间片调度
每个任务都有相同的优先级,任务会运行固定的时间片个数或者遇到阻塞式的API函数,比如tx_thread_sleep,才会执行同优先级任务之间的任务切换。
13.2 什么是调度器
简单的说,调度器就是使用相关的调度算法来决定当前需要执行的任务。所有的调度器有一个共同的特性:
- 调度器可以区分就绪态任务和挂起任务(由于延迟,信号量等待,邮箱等待,事件组等待等原因而使得任务被挂起)。
- 调度器可以选择就绪态中的一个任务,然后激活它(通过执行这个任务)。当前正在执行的任务是运行态的任务。
- 不同调度器之间最大的区别就是如何分配就绪态任务间的完成时间。
嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法。任务切换的实现在不同的嵌入式实时操作系统中区别不大,基本相同的硬件内核架构,任务切换也是相似的。调度算法就有些区别了。下面我们主要了解一下抢占式调度器和时间片调度器。
13.3 抢占式调度器
13.3.1 抢占式调度器基本概念
在实际的应用中,不同的任务需要不同的响应时间。例如,我们在一个应用中需要使用电机,键盘和LCD显示。电机比键盘和LCD需要更快速的响应,如果我们使用合作式调度器或者时间片调度,那么电机将无法得到及时的响应,这时抢占式调度是必须的。
如果使用了抢占式调度,最高优先级的任务一旦就绪,总能得到CPU的控制权。比如,当一个运行着的任务被其它高优先级的任务抢占,当前任务的CPU使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了CPU的控制权并运行。又比如,如果中断服务程序使一个高优先级的任务进入就绪态,中断完成时,被中断的低优先级任务被挂起,优先级高的那个任务开始运行。
使用抢占式调度器,使得最高优先级的任务什么时候可以得到CPU的控制权并运行是可知的,同时使得任务级响应时间得以最优化。
总的来说,学习抢占式调度要掌握的最关键一点是:每个任务都被分配了不同的优先级,抢占式调度器会获得就绪列表中优先级最高的任务,并运行这个任务。
13.3.2 ThreadX抢占式调度器的实现
如果用户使用函数tx_thread_create创建禁止使用时间片调度,并且每个任务配置不同的优先级。当ThreadX多任务启动执行后,基本会按照如下的方式去执行:
1、 首先执行的最高优先级的任务Task1,Task1会一直运行直到遇到系统阻塞式的API函数,比如延迟,事件标志等待,信号量等待,Task1任务会被挂起,也就是释放CPU的执行权,让低优先级的任务得到执行。
2、 ThreadX操作系统继续执行任务就绪列表中下一个最高优先级的任务Task2,Task2执行过程中有两种情况:
- Task1由于延迟时间到,接收到信号量消息等方面的原因,使得Task1从挂起状态恢复到就绪态,在抢占式调度器的作用下,Task2的执行会被Task1抢占。
- Task2会一直运行直到遇到系统阻塞式的API函数,比如延迟,事件标志等待,信号量等待,Task2任务会被挂起,继而执行就绪列表中下一个最高优先级的任务。
3、 如果用户创建了多个任务并且采用抢占式调度器的话,基本都是按照上面两条来执行。根据抢占式调度器,当前的任务要么被高优先级任务抢占,要么通过调用阻塞式API来释放CPU使用权让低优先级任务执行,没有用户任务执行时就执行空闲任务。
下面我们通过如下的框图来说明一下抢占式调度在ThreadX中的运行过程,让大家有一个形象的认识。
运行条件:
- 这里仅对抢占式调度进行说明。
- 创建3个任务Task1,Task2和Task3。
- Task1的优先级为1,Task2的优先级为2,Task3的优先级为3。FreeRTOS操作系统是设置的数值越小任务优先级越低,故Task3的优先级最高,Task1的优先级最低。
- 此框图是ThreadX操作系统运行过程中的一部分。
运行过程描述如下:
- 此时任务Task1在运行中,运行过程中由于Task2就绪,在抢占式调度器的作用下任务Task2抢占Task1的执行。Task2进入到运行态,Task1由运行态进入到就绪态。
- 任务Task2在运行中,运行过程中由于Task3就绪,在抢占式调度器的作用下任务Task3抢占Task2的执行。Task3进入到运行态,Task2由运行态进入到就绪态。
- 任务Task3运行过程中调用了阻塞式API函数,比如tx_thread_sleep,任务Task3被挂起,在抢占式调度器的作用下查找到下一个要执行的最高优先级任务是Task2,任务Task2由就绪态进入到运行态。
- 任务Task2在运行中,运行过程中由于Task3再次就绪,在抢占式调度器的作用下任务Task3抢占Task2的执行。Task3进入到运行态,Task2由运行态进入到就绪态。
上面就是一个简单的不同优先级任务通过抢占式调度进行任务调度和任务切换的过程。
13.4 时间片调度器
13.4.1 时间片调度器基本概念
在小型的嵌入式RTOS中,最常用的的时间片调度算法就是Round-robin调度算法。这种调度算法
可以用于抢占式或者合作式的多任务中。另外,时间片调度适合用于不要求任务实时响应的情况。
实现Round-robin调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,并为每个任务分配一个时间片(也就是需要运行的时间长度,时间片用完了就进行任务切换)。
13.4.2 ThreadX时间片调度器的实现
在ThreadX操作系统中只有同优先级任务才会使用时间片调度,通过函数tx_thread_create的第9个形参可以设置时间片大小,参数范围0,到0xFFFFFFFF,设置为TX_NO_TIME_SLICE(对应的是数值0)表示禁止时间片。
下面我们通过如下的框图来说明一下时间片调度在ThreadX中的运行过程,让大家有一个形象的认识。
运行条件:
- 这里仅对时间片调度进行说明。
- 创建4个同优先级任务Task1,Task2,Task3和Task4。
- 每个任务分配的时间片大小是5个系统时钟节拍。
运行过程描述如下:
- 先运行任务Task1,运行够5个系统时钟节拍后,通过时间片调度切换到任务Task2。
- 任务Task2运行够5个系统时钟节拍后,通过时间片调度切换到任务Task3。
- 任务Task3在运行期间调用了阻塞式API函数,调用函数时,虽然5个系统时钟节拍的时间片大小还没有用完,此时依然会通过时间片调度切换到下一个任务Task4。(注意,没有用完的时间片不会再使用,下次任务Task3得到执行还是按照5个系统时钟节拍运行)
- 任务Task4运行够5个系统时钟节拍后,通过时间片调度切换到任务Task1。
上面就是一个简单的同优先级任务通过时间片调度进行任务调度和任务切换的过程。
13.5 关于合作式调度器的特别说明
注:合作式调用很少使用,我们这里不做研究。
ThreadX支持具有相同优先级的多个线程的循环调度。这是通过对tx_thread_relinquish的协作调用来完成的。该服务为所有其他具有相同优先级的就绪线程提供了在tx_thread_relinquish调用程序再次执行之前执行的机会。
13.6 实验例程
配套例子:
V6-3008_ThreadX Round-robin Scheduling
实验目的:
- 学习ThreadX时间片调度。
- MspPro任务和COM任务设置成相同优先级,时间片大小都设置为2。
实验内容:
1、共创建了如下几个任务,通过按下按键K1可以通过串口或者RTT打印任务堆栈使用情况
========================================================
OS CPU Usage = 1.05%
========================================================
任务优先级 任务栈大小 当前使用栈 最大栈使用 任务名
Prio StackSize CurStack MaxStack Taskname
2 4092 303 459 App Task Start
30 1020 303 303 App Task STAT
31 1020 87 71 App Task IDLE
5 4092 167 167 App Msp Pro
4 4092 167 167 App Task UserIF
5 4092 167 167 App Task COM
0 1020 191 191 System Timer Thread
串口软件可以使用SecureCRT或者H7-TOOL RTT查看打印信息。
App Task Start任务 :启动任务,这里用作BSP驱动包处理。
App Task MspPro任务 :消息处理,这里用作LED闪烁。
App Task UserIF任务 :按键消息处理。
App Task COM任务 :这里用作LED闪烁。
App Task STAT任务 :统计任务
App Task IDLE任务 :空闲任务
System Timer Thread任务:系统定时器任务
2、(1) 凡是用到printf函数的全部通过函数App_Printf实现。
(2) App_Printf函数做了信号量的互斥操作,解决资源共享问题。
3、默认上电是通过串口打印信息,如果使用RTT打印信息
(1) MDK AC5,MDK AC6或IAR通过使能bsp.h文件中的宏定义为1即可
#define Enable_RTTViewer 1
(2) Embedded Studio继续使用此宏定义为0, 因为Embedded Studio仅制作了调试状态RTT方式查看。
串口打印信息方式(AC5,AC6和IAR):
波特率 115200,数据位 8,奇偶校验位无,停止位 1
RTT打印信息方式(AC5,AC6和IAR):
Embedded Studio仅支持调试状态RTT打印:
由于Embedded Studio不支持中文,所以中文部分显示乱码,不用管。
程序执行框图:
13.7 总结
本章节是ThreadX操作系统的核心,初学者要深入理解的话需要多花些时间。当然,如果有其它RTOS的基础的话,这个学起来也是很快的。