uCOS-III 学习记录(7)——就绪列表
参考内容:《[野火]uCOS-III内核实现与应用开发实战指南——基于STM32》第 11 章。
目录
1 就绪列表和任务控制块的定义(os.h)
1.1 任务控制块链表 OS_TCB
在定义就绪列表之前,先修改一下 TCB 的内容。
TCB 是一条双向链表,每个节点都包含以下内容:
- 任务栈指针 StkPtr;
- 任务栈大小 StkSize;
- 任务阻塞时长 TaskDelayTicks;
- 任务的优先级 Prio;
- 前向指针域 PrevPtr 和后向指针域 NextPtr。
/*----------------------TCB---------------------------*/
/* TCB 重命名为大写字母格式 */
typedef struct os_tcb OS_TCB;
/* TCB 数据类型声明 */
struct os_tcb{
CPU_STK *StkPtr;
CPU_STK_SIZE StkSize;
OS_TICK TaskDelayTicks; /* 任务延时多少个 Ticks,注意 1 个 Ticks 为 10ms */
OS_PRIO Prio;
OS_TCB *NextPtr;
OS_TCB *PrevPtr;
};
1.2 就绪列表 OS_RDY_LIST
和 TCB 不同,os_rdy_list 不是一个链表,它只是一个纯粹的结构体,存储着:
- TCB 链表的头结点 HeadPtr;
- TCB 链表的尾结点 TailPtr;
- TCB 链表的节点个数 NbrEntries,即有多少个任务。
/*---------------------OS_RDY_LIST----------------------------*/
/* 就绪列表重命名为大写字母格式 */
typedef struct os_rdy_list OS_RDY_LIST;
/* 就绪列表数据类型声明,将 TCB 串成双向链表 */
struct os_rdy_list{
OS_TCB *HeadPtr;
OS_TCB *TailPtr;
OS_OBJ_QTY NbrEntries; /* 同一个索引下有多少个任务 */
};
1.3 全局变量定义
- 就绪列表数组 OSRdyList:数组元素个数是我们定义好的最大支持优先级。比如我们定义了支持的优先级是 32,则就绪列表数组有 32 个元素;
这意味着,每个数组元素都将存放与 TCB 相关的信息:TCB 链表的头指针、尾指针以及节点个数,我们可以视为每个数组元素存放着一条 TCB 双向链表。
- 当前优先级 OSPrioCur;
- 最高优先级 OSPrioHighRdy;
- 当前任务 OSTCBCurPtr;
- 最高优先级任务 OSTCBHighRdyPtr。
OS_EXT OS_TCB *OSTCBCurPtr;
OS_EXT OS_TCB *OSTCBHighRdyPtr;
OS_EXT OS_PRIO OSPrioCur; /* 当前优先级 */
OS_EXT OS_PRIO OSPrioHighRdy; /* 最高优先级 */
OS_EXT OS_RDY_LIST OSRdyList[OS_CFG_PRIO_MAX];
1.4 结构全图
如图所示,每个优先级对应一个双向链表,执行任务时,当最高优先级上有多个就绪任务时,执行的顺序是从双向链表的头到尾。
2 初始化就绪列表 OS_RdyListInit()(os_core.c)
该函数的功能:
- 将就绪列表初始化为空:所有信息全部清零。
/* 初始化就绪列表 */
void OS_RdyListInit (void)
{
OS_PRIO i;
OS_RDY_LIST *p_rdy_list;
for ( i = 0u; i < OS_CFG_PRIO_MAX; i++ )
{
p_rdy_list = &OSRdyList[i];
p_rdy_list->HeadPtr = (OS_TCB *) 0;
p_rdy_list->TailPtr = (OS_TCB *) 0;
p_rdy_list->NbrEntries = (OS_OBJ_QTY)0;
}
}
3 将 TCB 节点从链表头部移到链表尾部 OS_RdyListMoveHeadToTail()(os_core.c)
该函数的功能:
- 将任务 TCB 插入到就绪列表中。
所以要完成的步骤为:
- 根据任务的优先级,将优先级表中的相应位置置位;
- 根据优先级的大小,插入到就绪列表的相应位置:如果优先级等于当前优先级,则将当前任务 TCB 插入链表尾部;否则,将其插入到链表头部。
/* 在就绪列表中插入一个 TCB */
void OS_RdyListInsert (OS_TCB *p_tcb)
{
OS_PrioInsert (p_tcb->Prio); /* 将优先级插入当前优先级中 */
if (p_tcb->Prio == OSPrioCur)
{
OS_RdyListInsertTail (p_tcb); /* 如果优先级等于当前优先级,则将当前任务插入链表尾部 */
}
else
{
OS_RdyListInsertHead (p_tcb); /* 否则将当前任务插入链表头部 */
}
}
为此,需要实现插入链表头部和插入链表尾部的函数。
3.1 链表头部插入一个 TCB 节点 OS_RdyListInsertHead()(os_core.c)
在链表头部插入一个 TCB 分为两种情况:
- 链表为空:(1)直接创建一个节点,指针域均为空;(2)任务数加一;(3)就绪列表的 HeadPtr 和 TailPtr 均指向新的结点。
- 链表不为空:(1)使原先头结点的前向指针域指向新结点,新节点的前向指针域置 0,后向指针域指向原先头结点;(2)任务数加一;(3)就绪列表的 HeadPtr 指向新的头结点。
/* 链表头部插入一个 TCB */
void OS_RdyListInsertHead (OS_TCB *p_tcb)
{
OS_RDY_LIST *p_rdy_list;
OS_TCB *p_tcb2;
p_rdy_list = &OSRdyList[p_tcb->Prio];
if (p_rdy_list->NbrEntries == (OS_OBJ_QTY)0) /* 若链表为空 */
{
p_rdy_list->NbrEntries = (OS_OBJ_QTY)1;
p_rdy_list->HeadPtr = p_tcb;
p_rdy_list->TailPtr = p_tcb;
p_tcb->NextPtr = (OS_TCB *)0;
p_tcb->PrevPtr = (OS_TCB *)0;
}
else /* 若链表不为空 */
{
p_rdy_list->NbrEntries++;
p_tcb2 = p_rdy_list->HeadPtr; /* p_tcb2 = 原先 TCB 链表的头结点 */
p_tcb->PrevPtr = (OS_TCB *)0; /* 新的 p_tcb 的前指针为 0 */
p_tcb->NextPtr = p_rdy_list->HeadPtr;
p_tcb2->PrevPtr = p_tcb; /* p_tcb2 的前指针指向 p_tcb,即新的 p_tcb 加入 TCB 链表中,成为新的头结点 */
p_rdy_list->HeadPtr = p_tcb; /* 新加入一个 TCB 后,就绪列表的 HeadPtr指向新的头结点 */
}
}
3.2 链表尾部插入一个 TCB 节点 OS_RdyListInsertTail()(os_core.c)
在链表尾部插入一个 TCB 也分为两种情况:
- 链表为空:(1)直接创建一个节点,指针域均为空;(2)任务数加一;(3)就绪列表的 HeadPtr 和 TailPtr 均指向新的结点。
- 链表不为空:(1)使原先尾结点的后向指针域指向新结点,新节点的后向指针域置 0,前向指针域指向原先尾结点;(2)任务数加一;(3)就绪列表的 TailPtr 指向新的尾结点。
/* 链表尾部插入一个 TCB */
void OS_RdyListInsertTail (OS_TCB *p_tcb)
{
OS_RDY_LIST *p_rdy_list;
OS_TCB *p_tcb2;
p_rdy_list = &OSRdyList[p_tcb->Prio];
if (p_rdy_list->NbrEntries == (OS_OBJ_QTY)0) /* 若链表为空 */
{
p_rdy_list->NbrEntries = (OS_OBJ_QTY)1;
p_rdy_list->HeadPtr = p_tcb;
p_rdy_list->TailPtr = p_tcb;
p_tcb->NextPtr = (OS_TCB *)0;
p_tcb->PrevPtr = (OS_TCB *)0;
}
else /* 若链表不为空 */
{
p_rdy_list->NbrEntries++;
p_tcb2 = p_rdy_list->TailPtr; /* p_tcb2 = 原先 TCB 链表的尾结点 */
p_tcb->PrevPtr = p_tcb2; /* 新的 p_tcb 的前指针指向 p_tcb2 */
p_tcb->NextPtr = (OS_TCB *)0; /* 新的 p_tcb 的后指针为 0 */
p_tcb2->NextPtr = p_tcb; /* p_tcb2 的后指针指向 p_tcb,即新的 p_tcb 加入 TCB 链表中,成为新的尾结点 */
p_rdy_list->TailPtr = p_tcb; /* 新加入一个 TCB 后,就绪列表的 HeadPtr指向新的尾结点 */
}
}
4 将 TCB 节点从链表头部移到链表尾部 OS_RdyListMoveHeadToTail()(os_core.c)
将 TCB 节点从链表头部移到链表尾部,分为四种情况:
- 链表为空:什么事都不用做。
- 链表中只有一个节点:什么事都不用做。
- 链表中只有两个节点:(1)原先头结点的前向指针域指向原先尾结点,原先头结点的后向指针域置零;(2)原先尾结点的前向指针域置零,原先尾结点的前向指针域指向原先头结点;(3)就绪列表的 HeadPtr 和 TailPtr 均分别指向新的头、尾结点。
- 链表中有两个以上节点:(1)原先头结点的前向指针域指向原先尾结点,原先头结点的后向指针域置零;(2)原先头结点的下一个节点的前向指针域置零(作为新的头结点);(3)原先尾结点的前向指针域指向原先头结点;(4)就绪列表的 HeadPtr 和 TailPtr 均分别指向新的头、尾结点。
/* 将 TCB 从链表头部移到链表尾部 */
void OS_RdyListMoveHeadToTail (OS_RDY_LIST *p_rdy_list)
{
OS_TCB *p_tcb1;
OS_TCB *p_tcb2;
OS_TCB *p_tcb3;
switch (p_rdy_list->NbrEntries)
{
case 0: /* 链表为空 */
case 1: /* 链表只有一个节点 */
break;
case 2: /* 链表只有两个节点 */
p_tcb1 = p_rdy_list->HeadPtr;
p_tcb2 = p_rdy_list->TailPtr;
p_tcb1->PrevPtr = p_tcb2;
p_tcb1->NextPtr = (OS_TCB *)0;
p_tcb2->PrevPtr = (OS_TCB *)0;
p_tcb2->NextPtr = p_tcb1;
p_rdy_list->HeadPtr = p_tcb2;
p_rdy_list->TailPtr = p_tcb1;
break;
default: /* 链表有两个以上的节点 */
p_tcb1 = p_rdy_list->HeadPtr;
p_tcb2 = p_rdy_list->TailPtr;
p_tcb3 = p_tcb1->NextPtr;
p_tcb1->PrevPtr = p_tcb2;
p_tcb1->NextPtr = (OS_TCB *)0;
p_tcb3->PrevPtr = (OS_TCB *)0;
p_tcb2->NextPtr = p_tcb1;
p_rdy_list->HeadPtr = p_tcb3;
p_rdy_list->TailPtr = p_tcb1;
break;
}
}
5 链表移除一个 TCB 节点 OS_RdyListRemove()(os_core.c)
从 TCB 链表中移除一个 TCB 节点,先保存要删除的 TCB 节点的前一个和后一个节点,然后分为四种情况:
- 链表只有一个节点(或链表为空):(1)就绪列表对应的数组元素全部信息清零;(2)将优先级表中对应的优先级置 0。
- 删除的是链表的头结点:(1)后一个节点的前向指针域置零;(2)任务数减一;(3)就绪列表的 HeadPtr 指向新的头结点(即后一个节点)。
- 删除的是链表的尾结点:(1)前一个节点的后向指针域置零;(2)任务数减一;(3)就绪列表的 TailPtr 指向新的尾结点(即前一个节点)。
- 删除的是链表的普通结点:(1)前一个节点的后向指针域指向后一个节点,后一个节点的前向指针域指向前一个节点。(2)任务数减一。
最后,将被删除节点的指针域全部置零,待下一次的链表插入操作。
/* 链表移除一个 TCB */
void OS_RdyListRemove (OS_TCB *p_tcb)
{
OS_RDY_LIST *p_rdy_list;
OS_TCB *p_tcb1;
OS_TCB *p_tcb2;
/* 保存要删除的 TCB 节点的前一个和后一个节点 */
p_tcb1 = p_tcb->PrevPtr;
p_tcb2 = p_tcb->NextPtr;
p_rdy_list = &OSRdyList[p_tcb->Prio];
if (( p_tcb1 == (OS_TCB *)0 ) && ( p_tcb2 == (OS_TCB *)0 )) /* 如果链表只有一个节点 */
{
p_rdy_list->HeadPtr = (OS_TCB *) 0;
p_rdy_list->TailPtr = (OS_TCB *) 0;
p_rdy_list->NbrEntries = (OS_OBJ_QTY)0;
OS_PrioRemove (p_tcb->Prio);
}
else if (( p_tcb1 == (OS_TCB *)0 ) && ( p_tcb2 != (OS_TCB *)0 )) /* 如果删除的是链表的头节点(p_tcb1 = 0) */
{
p_tcb2->PrevPtr = (OS_TCB *) 0;
p_rdy_list->HeadPtr = p_tcb2;
p_rdy_list->NbrEntries--;
}
else if (( p_tcb1 != (OS_TCB *)0 ) && ( p_tcb2 == (OS_TCB *)0 )) /* 如果删除的是链表的尾节点(p_tcb2 = 0) */
{
p_tcb1->NextPtr = (OS_TCB *) 0;
p_rdy_list->TailPtr = p_tcb1;
p_rdy_list->NbrEntries--;
}
else /* 如果删除的是链表的普通节点 */
{
p_tcb1->NextPtr = p_tcb2;
p_tcb2->PrevPtr = p_tcb1;
p_rdy_list->NbrEntries--;
}
/* 复位从就绪列表中删除的 TCB 的 PrevPtr 和 NextPtr 这两个指针 */
p_tcb->PrevPtr = (OS_TCB *)0;
p_tcb->NextPtr = (OS_TCB *)0;
}
复习了一遍链表的数据结构,嗯,针不戳!