目录
一、时间片轮转调度介绍
UCOSIII(MicroC/OS-III)中的时间片轮转调度(Round-Robin Scheduling)是一种用于分时系统中进程调度的算法,它主要用于解决在相同优先级下多个任务如何公平地共享CPU资源的问题。以下是UCOSIII时间片轮转调度的详细介绍:
基本概念
- 时间片:时间片是一个小的时间单位,通常为5~10ms数量级,具体值可以根据系统需求进行配置。每个任务在获得CPU时都会被分配一个时间片,并在该时间片内运行。
- 轮转调度:系统把所有就绪进程按照先入先出的原则排成一个队列。CPU依次在每个任务的时间片内运行该任务,当任务用完其时间片后,系统的计时器会发出时钟中断,调度程序便停止该任务的运行,将其放在就绪队列的末尾,并将CPU分配给就绪队列的下一个任务。
配置与使用
-
编译前配置:
- 在配置文件
os_cfg.h
中,通过宏定义OS_CFG_SCHED_ROUND_ROBIN_EN
来使能或禁用时间片轮转调度。当该宏定义为1时,表示启用时间片轮转调度;定义为0时,表示禁用。
- 在配置文件
-
运行时配置:
- 使用
OSSchedRoundRobinCfg
函数来配置时间片轮转调度的相关参数,包括是否启用时间片轮转调度以及默认的时间片长度。 - 可以通过
OSTaskCreate
函数在创建任务时指定每个任务的时间片长度。
- 使用
-
任务执行:
- 当多个相同优先级的任务都处于就绪状态时,它们将按照时间片轮转的方式依次获得CPU资源并执行。
- 如果一个任务在其时间片结束前主动放弃CPU(例如,通过调用
OSSchedRoundRobinYield
函数),则调度器会立即切换到下一个就绪任务。
实现原理
- UCOSIII通过维护一个就绪列表(ready list)来管理所有就绪的任务。每个优先级都有一个对应的就绪列表。
- 当系统时钟中断发生时,调度器会检查当前任务的时间片计数器。如果时间片已用完,则将当前任务移到就绪列表的末尾,并从列表的头部取出下一个任务执行。
- 如果当前任务在其时间片内主动放弃CPU,调度器也会执行类似的操作,但会立即进行任务切换,而不是等到时间片结束。
代码实现
当配置时间片轮转的时候,设置时间片轮转的时间片长度为1,就代表一个系统时钟节拍,根据系统时钟节拍频率(OSCfg_TickRate_Hz)为1000Hz得出时间片为1ms。
//任务优先级
#define TASK1_TASK_PRIO 4
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务控制块
OS_TCB Task1_TaskTCB;
//任务堆栈
CPU_STK TASK1_TASK_STK[TASK1_STK_SIZE];
void task1_task(void *p_arg);
//任务优先级
#define TASK2_TASK_PRIO 4
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务控制块
OS_TCB Task2_TaskTCB;
//任务堆栈
CPU_STK TASK2_TASK_STK[TASK2_STK_SIZE];
//任务函数
void task2_task(void *p_arg);
#if OS_CFG_SCHED_ROUND_ROBIN_EN //当使用时间片轮转的时候
//使能时间片轮转调度功能,设置默认的时间片长度为5ms
OSSchedRoundRobinCfg(DEF_ENABLED,5,&err);
#endif
//创建TASK1任务
OSTaskCreate((OS_TCB * )&Task1_TaskTCB,
(CPU_CHAR * )"Task1 task",
(OS_TASK_PTR )task1_task,
(void * )0,
(OS_PRIO )TASK1_TASK_PRIO,
(CPU_STK * )&TASK1_TASK_STK[0],
(CPU_STK_SIZE)TASK1_STK_SIZE/10,
(CPU_STK_SIZE)TASK1_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )10, //时间片长度10ms
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR|OS_OPT_TASK_SAVE_FP,
(OS_ERR * )&err);
//创建TASK2任务
OSTaskCreate((OS_TCB * )&Task2_TaskTCB,
(CPU_CHAR * )"task2 task",
(OS_TASK_PTR )task2_task,
(void * )0,
(OS_PRIO )TASK2_TASK_PRIO,
(CPU_STK * )&TASK2_TASK_STK[0],
(CPU_STK_SIZE)TASK2_STK_SIZE/10,
(CPU_STK_SIZE)TASK2_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )10, //时间片长度10ms
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR|OS_OPT_TASK_SAVE_FP,
(OS_ERR * )&err);
上面的代码是UCOSIII创建了两个优先级为4的任务,开启了时间片轮转调度,默认时间片为5ms,但是每个任务单独设置了时间片为10ms,所以按10ms的时间片轮转,一般任务也不会执行10ms这么久,所以执行完了这个任务也会继续切换调度任务。
注意事项
- 时间片轮转调度需要频繁地切换任务,这可能会带来一定的上下文切换开销。因此,在配置时间片长度时需要考虑系统的具体需求和性能要求。
- 在UCOSIII中,如果设置的时间片没有结束但是任务已经运行完成,系统将会进行任务切换。
- 任务单独设置的时间片以后,默认的时间片不在这个任务中生效。
- 时间片没有运行完的任务是可以被高优先级任务抢占的。
- 如果任务在执行过程中主动放弃CPU(例如,通过调用
OSTimeDly
、OSSemPend
等函数导致任务阻塞),则无论时间片是否耗尽,任务都会立即停止执行。
二、不开启时间片运转如何执行同等级任务
在UCOSIII(MicroC/OS-III)操作系统中,如果不开启时间片轮转调度(Round-Robin Scheduling),同一优先级的任务执行将遵循先来先服务(First-Come, First-Served, FCFS)的原则,但实际情况可能会因系统设计和任务的具体实现而有所不同。
具体来说,当不开启时间片轮转调度时:
-
任务调度原则:UCOSIII是一个基于优先级的抢占式实时内核。在这种情况下,任务的执行顺序主要由它们的优先级决定。高优先级的任务可以抢占正在执行的低优先级任务。当多个任务具有相同优先级时,并且这些任务都处于就绪状态(ready state),它们将按照它们在就绪列表中的顺序来执行。
-
就绪列表:在UCOSIII中,每个优先级都有一个对应的就绪列表(ready list)。这个列表包含了所有在该优先级下已经准备好执行的任务。这些任务通过任务控制块(TCB)链接在一起,形成一个双向链表。
-
执行顺序:当CPU空闲时,或者当前执行的任务被阻塞、挂起或完成时,调度器会检查就绪列表。它会从最高优先级的就绪列表开始查找,找到第一个就绪的任务并执行它。如果最高优先级的就绪列表中有多个任务,那么这些任务将按照它们在就绪列表中的顺序(通常是它们被添加到列表中的顺序)来执行。
-
执行过程:一旦一个任务开始执行,它将一直运行直到它完成、被更高优先级的任务抢占、或者被主动挂起或阻塞。对于相同优先级的任务,如果没有时间片轮转调度,那么一旦一个任务开始执行,它将独占CPU直到它完成或主动放弃CPU(例如,通过调用任务延时函数)。
-
公平性:不开启时间片轮转调度时,相同优先级的任务之间的执行公平性将依赖于它们在就绪列表中的顺序和它们各自的任务执行时间。如果某个任务执行时间较长,那么它将长时间占用CPU,导致其他相同优先级的任务等待较长时间。
需要注意的是,虽然UCOSIII允许用户通过配置宏OS_CFG_SCHED_ROUND_ROBIN_EN
来启用或禁用时间片轮转调度,但在实际应用中,是否启用该功能取决于具体的应用场景和需求。如果需要确保相同优先级的任务能够公平地获得CPU时间,那么开启时间片轮转调度是一个较好的选择。