之前OS还没有优先级,只能任务切换。在这之后加入了优先级,数字优先级越小,逻辑优先级越高。
其实,优先级这里部分就是对之前代码添加一些优先级有关的信息,其他不变。
1.定义优先级相关全局变量
在支持任务多优先级的时候,需要在 os.h 头文件添加两个优先级相关的全局变量。
//当前优先级
OS_EXT OS_PRIO OSPrioCur
//最高优先级
OS_EXT OS_PRIO OSPrioHighRdy
2.修改OSInit()
刚刚添加的全局变量,需要在OSInit()中进行初始化。
void OSInit(OS_ERR *p_err)
{
OSRunning = OS_STATE_OS_STOPPED;
OSTCBCurPtr = (OS_TCB *)0;
OSTCBHighRdyPtr = (OS_TCB *)0;
OSPrioCur = (OS_PRIO)0;
OSPrioHighRdy = (OS_PRIO)0;
//初始化优先级表
OS_PrioInit();
//初始化就绪列表
OS_RdyListInit();
OS_IdleTaskInit(p_err);
if(*p_err != OS_ERR_NONE)
{
return;
}
}
3.修改任务控制块TCB
在任务的结构体中加入一项优先级Prio的数据类型OS_PRIO。是8位整型,所以只支持255个优先级。
struct os_tcb{
CPU_STK *StkPtr;
CPU_STK_SIZE StkSize;
OS_TICK TaskDelayTicks;
//任务优先级
OS_PRIO Prio;
OS_TCB *NextPtr;
OS_TCB *PrevPtr;
};
4.修改OSTaskCreate()函数
viod OSTaskCreate(OS_TCB *p_tcb,
OS_TASK_PTR P_task,
void *p_arg,
OS_PRIO prio,
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_size,
OS_ERR *p_err)
{
CPU_STK *p_sp;
CPU_SR_ALLOC;
OS_TaskInitTCB(p_tcb);
p_sp = OSTaskStkInit(p_task,
p_arg,
p_stk_base,
stk_size);
p_tcb->Prio = prio;
p_tcb->StkPtr = p_sp;
p_tcb->StkSize = stk_size;
OS_CRITICAL_ENTER();
OS_PrioInsert(p_tcb->Prio); //(1)
OS_RdyListInsertTail(p_tcb);
OS_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
}
(1)将任务插入就绪列表中,这里分成两步
- 根据优先级置位优先级表中相应位置
- 将TCB放到OSRdyList[优先级]中,如果同个优先级有多个任务,那么这些任务TCB就会被放到OSRdyList[优先级]串成一个双向链表。
void OS_TaskInitTCB(OS_TCB *p_tcb)
{
p_tcb->StkPtr = (CPU_STK *)0;
p_tcb->StkSize = (CPU_STK_SIZE )0u;
p_tcb->TaskDelayTicks =(OS_TICK )0u;
p_tcb->Prio =(OS_PRIO )OS_PRIO_INIT;
p_tcb->NextPtr =(OS_TCB *)0;
P_tcb->PrevPtr =(OS_TCB *)0;
}
5.修改OS_IdleTaskInit()函数
这个函数调用了OSTaskCreate(),要给空闲任务分配一个优先级。
void OS_IdleTaskInit(OS_ERR *p_err)
{
OSIdleTaskCtr = (OS_IDLE_CTR)0;
OSTaskCreate((OS_TCB *)&OSIdleTaskTCB,
(OS_TASK_PTR )OS_IdleTask,
(void *)0,
(OS_PRIO)(OS_CFG_PRIO_MAX - 1u),
(CPU_STK *)OSCfg_IdleTaskStkBasePtr,
(CPU_STK_SIZE)OSCfg_IdleTaskStkSize,
(OS_ERR)p_err);
}
空闲任务就会被运行,优先级最低。
6.修改OSStart()函数
具体哪个任务先运行,由优先级决定
void OSStart(OS_ERR *p_err)
{
if(OSRunning == OS_STATE_OS_STOPPED){
#if 0
OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
#endif
//寻找最高优先级
OSPrioHighRdy = OS_PrioGetHighest();
OSPrioCur = OSPrioHighRdy;
//找到最高优先级的TCB
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
OSTCBCurPtr = OSTCBHighRdyPtr;
OSRunning = OS_STATE_OS_RUNNING;
OSStartHighRdy();
*p_err = OS_ERR_FATAL_RETURN;
}
else{
*p_err = OS_STATE_OS_RUNNING;
}
}
- 首先调用OS_PrioGetHighest()函数从全局优先级表中获取最高优先级,然后把值赋给当前优先级OSPrioCur中。
- 根据OSPrioHighRdy值,在OSRdyList[]下标索引中找到最高优先级任务TCB,赋给OSPrioHighRdyPtr,然后赋给OSPrioCurPtr。
7.修改OSTimeDly()函数
调用OSTimeDly()函数之后,任务就处于阻塞态,需要将任务从就绪列表中移除。
void OSTimeDly(OS_TICK_dly)
{
#if 0
OSTCBCurPtr->TaskDelayTicks = dly;
OSSched();
#endif
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();
OSTCBCurPtr->TaskDelayTicks = dly;
//从就绪列表中移除
//OS_RdyListRemove(OSTCBCurPtr);
OS_PrioRemove(OSTCBCurPtr->Prio);
OS_CRITICAL_EXIT();
OSSched();
}
将任务从就绪列表中移除,这里只需将任务在优先级表中对应位清除就行。
8.修改OSSched()函数
任务调度函数不再是之前两个任务轮流切换了,需要根据优先级来调度。
void OSSched(void)
{
#if 0
if(OSTCBCurPtr == &OSIdleTaskTCB)
{
if(OSRdyList[0].HeadPtr->TaskDelayTicks == 0)
{
OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
}
else if(OSRdyList[1].HeadPtr->TaskDelayTicks == 0){
OSTCBHighRdyPtr = OSRdtList[1].HeadPtr;
}
else{
return;
}
}
else{
if(OSTCBCurPtr == OSRdyList[0].HeadPtr)
{
if(OSRdyList[1].HeadPtr->TaskDelayTicks == 0)
{
OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;
}
else if(OSTCBCurPtr->TaskDelayTicks != 0)
{
OSTCBHighRdyPtr = &OSIdleTaskTCB;
}
else {
return;
}
}
else if(OSTCBCurPtr == OSRdyList[1].HeadPtr)
{
if(OSRdyList[0].HeadPtr->TaskDelayTicks == 0)
{
OSRdyListPtr = OSRdyList[0].HeadPtr;
}
else if(OSTCBCurPtr->TaskDelayTicks != 0)
{
OSTCBHighRdyPtr = &OSIdleTaskTCB;
}
else {
return ;
}
}
}
OS_TASK_SW();
#endif
CPU_SR_ALLOC();
OS_CRITIACL_ENTER();
OSPrioHighPtr = 0S_PrioGetHighest();
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
if(OSTCBHighRdyPtr == OSTCBCurPtr)
{
OS_CRITICAL_EXIT();
return ;
}
OS_CRITICAL_EXIT();
OS_TASK_SW();
}
9.修改OSTimeTick()函数
OSTimeTick()函数在SysTicks中断服务函数中被调用,用于扫描就绪列表OSRdyList[],判断任务延时是否到期,若到期则将任务在优先级表中相应位置位。
void OSTimeTick(void)
{
unsigned int i;
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();
for(i = 0;i < OS_CFG_PRIO_MAX;i++)
{
if(OSRdyList[i].HeadPtr->TaskDelayTicks > 0)
{
OSRdyList[i].HeadPtr->TaskDelayTicks--;
}
if(OSRdyList[i].HeadPtr->TaskDelayTicks == 0)
{
//为0表示延迟时间到,让任务就绪
//OSRdyListInsert(OSRdyList[i].HeadPtr);
OS_PrioInsert(i);
}
}
}
10.Main函数
#include "os.h"
#include "ARMCU3.h"
uint32_t flag1;
uint32_t flag2;
//TCB,STACK声明
#define TASK1_STK_SIZE 20
#define TASK2_STK_SIZE 20
#define TASK3_STK_SIZE 20
static CPU_STK TASK1Stk[TASK1_STK_SIZE];
static CPU_STK TASK2Stk[TASK2_STK_SIZE];
static CPU_STK TASK3Stk[TASL3_STK_SIZE];
static OS_TCB TASK1TCB;
static OS_TCB TASK2TCB;
void Task1(void *p_arg);
void Task2(void *p_arg);
void Task3(void *p_arg);
int main(void)
{
OS_ERR err;
CPU_Init();
//关闭中断
CPU_IntDis();
//配置10ms中断一次
OS_CPU_SysTickInit(10);
//初始化全局变量
OSInit(&err);
OSTaskCreate((OS_TCB *) &Task1TCB,
(OS_TASK_PTR) Task1,
(void * ) 0,
(OS_PRIO) 1,
(CPU_STK *) &Task1Stk[0],
(CPU_STK_SIZE) Task1_STK_SIZE,
(OS_ERR *) &err);
OSTaskCreate((OS_TCB *) &Task2TCB,
(OS_TASK_PTR) Task2,
(void *) 0,
(OS_PRIO) 2,
(CPU_STK *) &Task2Stk[0],
(CPU_STK_SIZE) Task2_STK_SIZE,
(OS_ERR *) &err);
OSTaskCreate((OS_TCB *) &Task3TCB,
(OS_TASK_PTR) Task3,
(void *) 0,
(OS_PRIO) 3,
(CPU_STK *) &Task3Stk[0],
(CPU_STK_SIZE) Task3_STK_SIZE,
(OS_ERR *) &err);
#if 0
//将任务假如到就绪列表
OSRdyList[0].HeadPtr = &Task1TCB;
OSRdyList[1].HeadPtr = &Task2TCB;
#endif
//启动OS,不再返回
OSStart(&err);
void Task1(void *p_arg)
{
while(1)
{
flag1 =1;
delay(100);
flag1 = 0;
delay(100);
//手动切换任务
OSOched();
}
}
void Task2(void *p_arg)
{
while(1)
{
flag2 =1;
delay(100);
flag2 = 0;
delay(100);
//手动切换任务
OSOched();
}
}
}
11.分析
可以看到三个任务波形是同步的,就好像CPU在同时干三件事。
实际放大看,还是有点差距的。
当任务1开始运行,运行0.26ms调用OSTimeDly(1)进入延时,然后进行任务切换,切换到任务2开始运行,切换时间为0.01ms。
以此类推,,当任务都切换时,3个任务均进入了延时阻塞状态,但是没到延时的10ms,所以系统就切换到空闲任务。延时未到期之前,系统都进行空闲任务。
当第一个SysTick中断产生,中断调用OSTimeTick()扫描每个任务是否到期,如果到期之后,任务1,2,3依次进入就绪状态。就可以重复上面的过程。