从0到1教你写UCOS-III 第十部分:就绪列表

       在 uC/OS-III 中,任务被创建后,任务的 TCB 会被放入就绪列表中,表示任务在就绪,随时可能被运行。就绪列表包含一个表示任务优先级的优先级表,一个存储任务 TCB 的TCB双向链表。


10.1 优先级表:


       优先级表在代码层面上来看,就是一个数组,在文件 os_prio.c(os_prio.c 第一次使用需要自行在文件夹 uCOS-III\Source 中新建并添加到工程的 uC/OS-III Source 组) 的开头定义,具体见代码清单 10-1。
代码清单 10-1 优先级表 OSPrioTbl[]定义

/* 定义优先级表,在 os.h 中用 extern 声明 */
CPU_DATA OSPrioTbl[OS_PRIO_TBL_SIZE];         (1)

       代码清单 10-1(1):正如我们所说, 优先级表是一个数组,数组类型为 CPU_DATA,在 Cortex-M 内 核 芯片的 MCU 中 CPU_DATA 为 32 位 整型 数组 的大 小由 宏OS_PRIO_TBL_SIZE 控制。 OS_PRIO_TBL_SIZE 的具体取值与 uC/OS-III 支持多少个优先级有关,支持的优先级越多,优先级表也就越大,需要的 RAM 空间也就越多。理论上uC/OS-III 支持无限的优先级,只要 RAM 控制足够。 宏 OS_PRIO_TBL_SIZE 在 os.h 文件定义,具体实现见代码清单 10-2。
代码清单 10-2 OS_PRIO_TBL_SIZE 宏定义

                             (1)                                    (2)
#define OS_PRIO_TBL_SIZE ((OS_CFG_PRIO_MAX - 1u) / (DEF_INT_CPU_NBR_BITS) + 1u)

        代码清单 10-2(1): OS_CFG_PRIO_MAX 表示支持多少个优先级,在 os_cfg.h 中定义,本书设置为 32,即最大支持 32 个优先级。
        代码清单 10-2(2): DEF_INT_CPU_NBR_BITS 定义 CPU 整型数据有多少位,本书适配的是基于 Cortex-M系列的 MCU,宏展开为 32位。

        所以,经过 OS_CFG_PRIO_MAX 和 DEF_INT_CPU_NBR_BITS 这两个宏展开运算之后,可得出 OS_PRIO_TBL_SIZE 的值为 1,即优先级表只需要一个成员即可表示 32 个优先级。 如果要支持 64 个优先级,即需要两个成员,以此类推。 如果 MCU 的类型是 16 位、8 位或者 64位,只需要把优先级表的数据类型 CPU_DATA改成相应的位数即可
        那么优先级表又是如何跟任务的优先级联系在一起的? 具体的优先级表的示意图见图10-1。

       在图 10-1 中,优先级表的成员是 32 位的,每个成员可以表示 32 个优先级。如果优先级超过 32 个,那么优先级表的成员就要相应的增加。 以本书为例, CPU 的类型为 32 位,支持最大的优先级为 32 个,优先级表只需要一个成员即可,即只有 OSPrioTbl[0]。假如创建一个优先级为 Prio的任务,那么就在 OSPrioTbl[0]的位[31-prio]置 1即可。如果 Prio等于3,那么就将位 28 置 1。 OSPrioTbl[0]的位 31 表示的是优先级最高的任务,以此递减,直到 OSPrioTbl[OS_PRIO_TBL_SIZE-1]]的位 0, OSPrioTbl[OS_PRIO_TBL_SIZE-1]]的位 0表示的是最低的优先级。


10.1.1 优先级表函数讲解:


优先级表相关的函数在 os_prio.c 文件中实现, 在 os.h 文件中声明, 函数汇总具体见表格 10-1。
表格 10-1 优先级表相关函数汇总

1、OS_PrioInit()函数:
      OS_PrioInit()函数用于初始化优先级表, 在 OSInit()函数中被调用, 具体实现见代码清单 10-3。
代码清单 10-3 OS_PrioInit()函数

/* 初始化优先级表 */
void OS_PrioInit( void )
{
CPU_DATA i;
/* 默认全部初始化为 0 */
for ( i=0u; i<OS_PRIO_TBL_SIZE; i++ ) {
OSPrioTbl[i] = (CPU_DATA)0;
}
}

        本书中, 优先级表 OS_PrioTbl[]只有一个成员, 即 OS_PRIO_TBL_SIZE 等于 1 经过代码清单 10-3 初始化之后,具体示意图见图 10-2。

2、OS_PrioInsert()函数:
      OS_PrioInsert()函数用于置位优先级表中相应的位,会被 OSTaskCreate()函数调用,具体实现见代码清单 10-4。
代码清单 10-4 OS_PrioInsert()函数

/* 置位优先级表中相应的位 */
void OS_PrioInsert (OS_PRIO prio)
{
CPU_DATA bit;
CPU_DATA bit_nbr;
OS_PRIO ix;
/* 求模操作,获取优先级表数组的下标索引 */
ix = prio / DEF_INT_CPU_NBR_BITS;                          (1)
/* 求余操作,将优先级限制在 DEF_INT_CPU_NBR_BITS 之内 */
bit_nbr = (CPU_DATA)prio & (DEF_INT_CPU_NBR_BITS - 1u);    (2)
/* 获取优先级在优先级表中对应的位的位置 */                    (3)
bit = 1u;
bit <<= (DEF_INT_CPU_NBR_BITS - 1u) - bit_nbr;
/* 将优先级在优先级表中对应的位置 1 */
OSPrioTbl[ix] |= bit;                                      (4)
}

       代码清单 10-4(1): 求模操作,获取优先级表数组的下标索引即定位 prio 这个优先级对应优先级表数组的哪个成员。 假设 prio等于 3, DEF_INT_CPU_NBR_BITS(用于表示 CPU一个整型数有多少位) 等于 32,那么 ix 就等于 0,即对应 OSPrioTBL[0]。
       代码清单 10-4(2) : 求余操作,将优先级限制在 DEF_INT_CPU_NBR_BITS 之内,超过 DEF_INT_CPU_NBR_BITS 的优先级就肯定要增加优先级表的数组成员了。假设 prio等于 3, DEF_INT_CPU_NBR_BITS(用于表示 CPU 一个整型数有多少位) 等于 32,那么bit_nbr就等于 3,但是这个还不是真正需要被置位的位。
       代码清单 10-4(3) : 获取优先级在优先级表中对应的位的位置。置位优先级对应的位是从高位开始的,不是从低位开始。位 31 对应的是优先级 0, 在 uC/OS-III 中,优先级数值越小,逻辑优先级就越高。 假设 prio 等于 3, DEF_INT_CPU_NBR_BITS(用于表示CPU一个整型数有多少位) 等于 32,那么 bit就等于 28。
       代码清单 10-4(4) : 将优先级在优先级表中对应的位置 1。 假设 prio 等于 3,DEF_INT_CPU_NBR_BITS(用于表示 CPU 一个整型数有多少位) 等于 32,那么置位的就是 OSPrioTbl[0]的位 28。

        在优先级最大是 32, DEF_INT_CPU_NBR_BITS 等于 32 的情况下,如果分别创建了优先级 3、 5、 8 和 11 这四个任务,任务创建成功后,优先级表的设置情况是怎么样的?具体见图 10-3。 有一点要注意的是,在 uC/OS-III 中,最高优先级和最低优先级是留给系统任务使用的,用户任务不能使用。

3、OS_PrioRemove()函数:
        OS_PrioRemove()函数用于清除优先级表中相应的位,与 OS_PrioInsert()函数的作用刚好相反,具体实现见代码清单 10-5
 ,有关代码的讲解参考代码清单 10-4 即可,不同的是置位操作改成了清 0。
代码清单 10-5 OS_PrioRemove()函数

/* 清除优先级表中相应的位 */
void OS_PrioRemove (OS_PRIO prio)
{
CPU_DATA bit;
CPU_DATA bit_nbr;
OS_PRIO ix;
/* 求模操作,获取优先级表数组的下标索引 */
ix = prio / DEF_INT_CPU_NBR_BITS;
/* 求余操作,将优先级限制在 DEF_INT_CPU_NBR_BITS 之内 */
bit_nbr = (CPU_DATA)prio & (DEF_INT_CPU_NBR_BITS - 1u);
/* 获取优先级在优先级表中对应的位的位置 */
bit = 1u;
bit <<= (DEF_INT_CPU_NBR_BITS - 1u) - bit_nbr;
/* 将优先级在优先级表中对应的位清 0 */
OSPrioTbl[ix] &= ~bit;
}

4、OS_PrioGetHighest()函数:
     OS_PrioGetHighest()函数用于从优先级表中查找最高的优先级,具体实现见代码清单10-6。
代码清单 10-6 OS_PrioGetHighest()函数

/* 获取最高的优先级 */
OS_PRIO OS_PrioGetHighest (void)
{
CPU_DATA *p_tbl;
OS_PRIO prio;
prio = (OS_PRIO)0;
/* 获取优先级表首地址 */
p_tbl = &OSPrioTbl[0];                          (1)
/* 找到数值不为 0 的数组成员 */                   (2)
while (*p_tbl == (CPU_DATA)0) {
prio += DEF_INT_CPU_NBR_BITS;
p_tbl++;
}
/* 找到优先级表中置位的最高的优先级 */
prio += (OS_PRIO)CPU_CntLeadZeros(*p_tbl);      (3)
return (prio);
}


        代码清单 10-6(1): 获取优先级表的首地址,从头开始搜索整个优先级表,直到找到最高的优先级。

        代码清单 10-6(2): 找到优先级表中数值不为 0的数组成员,只要不为 0就表示该成员里面至少有一个位是置位的。我们知道,在图 10-4 的优先级表中,优先级按照从左到右,从上到下依次减小,左上角为最高的优先级,右下角为最低的优先级, 所以我们只需要找到第一个不是 0 的优先级表成员即可。

        代码清单 10-6(3): 确定好优先级表中第一个不为 0 的成员后,然后再找出该成员中第一个置 1 的位(从高位到低位开始找)就算找到最高优先级。 在一个变量中,按照从高位到低位的顺序查找第一个置 1 的位的方法是通过计算前导 0 函数 CPU_CntLeadZeros()来实现的。 从高位开始找 1叫计算前导 0,从低位开始找 1叫计算后导 0。 如果分别创建了优先级 3、 5、 8和 11 这四个任务,任务创建成功后,优先级表的设置情况具体见图 10-5。调用 CPU_CntLeadZeros()可以计算出 OSPrioTbl[0]第一个置 1的位前面有 3 个 0,那么这个 3就是我们要查找的最高优先级,至于后面还有多少个位置 1 我们都不用管,只需要找到第一个 1 即可。

         CPU_CntLeadZeros()函数可由汇编或者 C 来实现,如果使用的处理器支持前导零指令CLZ, 可由汇编来实现,加快指令运算,如果不支持则由 C 来实现。 在 uC/OS-III 中,这两种实现方法均有提供代码, 到底使用哪种方法由 CPU_CFG_LEAD_ZEROS_ASM_PRESEN这个宏来控制,定义了这个宏则使用汇编来实现,没有定义则使用 C 来实现。
        Cortex-M 系列处理器自带 CLZ 指令,所以 CPU_CntLeadZeros()函数默认由汇编编写,具体在 cpu_a.asm 文件实现,在 cpu.h 文件声明,具体见代码清单 10-7。代码清单 10-7 CPU_CntLeadZeros()函数实现与声明
 

;*******************************************************************
; PUBLIC FUNCTIONS
;*******************************************************************
EXPORT CPU_CntLeadZeros
EXPORT CPU_CntTrailZeros
;*******************************************************************
; 计算前导 0 函数
;
; 描述 :
;
; 函数声明 : CPU_DATA CPU_CntLeadZeros(CPU_DATA val);
;
;*******************************************************************
CPU_CntLeadZeros
CLZ R0, R0 ; Count leading zeros
BX LR
;*******************************************************************
; 计算后导 0 函数
;
; 描述 :
;
; 函数声明 : CPU_DATA CPU_CntTrailZeros(CPU_DATA val);
;
;*******************************************************************
CPU_CntTrailZeros
RBIT R0, R0 ; Reverse bits
CLZ R0, R0 ; Count trailing zeros
BX LR
/*
*******************************************************************
* 函数声明
* cpu.h 文件
*******************************************************************
*/
#define CPU_CFG_LEAD_ZEROS_ASM_PRESEN
CPU_DATA CPU_CntLeadZeros (CPU_DATA val); /* 在 cpu_a.asm 定义 */
CPU_DATA CPU_CntTrailZeros(CPU_DATA val); /* 在 cpu_a.asm 定义 */

        如果处理器不支持前导 0 指令, CPU_CntLeadZeros()函数就得由 C 编写,具体在cpu_core.c 文件实现,在 cpu.h 文件声明,具体见代码清单 10-8。
代码清单 10-8 由 C 实现的 CPU_CntLeadZeros()函数
 

#ifndef CPU_CFG_LEAD_ZEROS_ASM_PRESENT
CPU_DATA CPU_CntLeadZeros (CPU_DATA val)
{
CPU_DATA nbr_lead_zeros;
CPU_INT08U ix;
/* 检查高 16 位 */
if (val > 0x0000FFFFu) {                                     (1)
/* 检查 bits [31:24] : */
if (val > 0x00FFFFFFu) {                                     (2)
/* 获取 bits [31:24]的值,并转换成 8 位 */
ix = (CPU_INT08U)(val >> 24u);                               (3)
/* 查表找到优先级 */
nbr_lead_zeros=(CPU_DATA)(CPU_CntLeadZerosTbl[ix]+0u);       (4)
}
/* 检查 bits [23:16] : */
else {
/* 获取 bits [23:16]的值,并转换成 8 位 */
ix = (CPU_INT08U)(val >> 16u);
/* 查表找到优先级 */
nbr_lead_zeros = (CPU_DATA )(CPU_CntLeadZerosTbl[ix] + 8u);
}
}
/* 检查低 16 位 */
else {
/* 检查 bits [15:08] : */
if (val > 0x000000FFu) {
/* 获取 bits [15:08]的值,并转换成 8 位 */
ix = (CPU_INT08U)(val >> 8u);
/* 查表找到优先级 */
nbr_lead_zeros = (CPU_DATA )(CPU_CntLeadZerosTbl[ix] + 16u);
}
/* 检查 bits [07:00] : */
else {
/* 获取 bits [15:08]的值,并转换成 8 位 */
ix = (CPU_INT08U)(val >> 0u);
/* 查表找到优先级 */
nbr_lead_zeros = (CPU_DATA )(CPU_CntLeadZerosTbl[ix] + 24u);
}
}
/* 返回优先级 */
return (nbr_lead_zeros);
}
#endif

在 uC/OS-III 中,由 C 实现的 CPU_CntLeadZeros()函数支持 8 位、 16 位、 32 位和 64 位
的变量的前导 0 计算, 但最终的代码实现都是分离成 8 位来计算。 这里我们只讲解 32 位的,
其它几种情况都类似。
代码清单 10-8(1): 分离出高 16 位, else 则为低 16 位。
代码清单 10-8(2): 分离出高 16 位的高 8 位, else 则为高 16 位的低 8 位。
代码清单 10-8(3): 将高 16 位的高 8 位通过移位强制转化为 8 位的变量,用于后面的查表操作。
代码清单 10-8(4): 将 8 位的变量 ix 作为数组 CPU_CntLeadZerosTbl[]的索引,返回索引对应的值,那么该值就是 8 位变量 ix 对应的前导 0,然后再加上(24-右移的位数) 就等于优先级。 数组 CPU_CntLeadZerosTbl[]在 cpu_core.c 的开头定义,具体见代码清单 10-9。
代码清单 10-9 CPU_CntLeadZerosTbl[]定义
 

#ifndef CPU_CFG_LEAD_ZEROS_ASM_PRESENT
static const CPU_INT08U CPU_CntLeadZerosTbl[256] = {/* 索引 */
8u,7u,6u,6u,5u,5u,5u,5u,4u,4u,4u,4u,4u,4u,4u,4u, /* 0x00 to 0x0F */
3u,3u,3u,3u,3u,3u,3u,3u,3u,3u,3u,3u,3u,3u,3u,3u, /* 0x10 to 0x1F */
2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u, /* 0x20 to 0x2F */
2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u,2u, /* 0x30 to 0x3F */
1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u, /* 0x40 to 0x4F */
1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u, /* 0x50 to 0x5F */
1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u, /* 0x60 to 0x6F */
1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u,1u, /* 0x70 to 0x7F */
0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u, /* 0x80 to 0x8F */
0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u, /* 0x90 to 0x9F */
0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u, /* 0xA0 to 0xAF */
0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u, /* 0xB0 to 0xBF */
0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u, /* 0xC0 to 0xCF */
0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u, /* 0xD0 to 0xDF */
0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u, /* 0xE0 to 0xEF */
0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u,0u /* 0xF0 to 0xFF */
};
#endif

       代码清单 10-8 中,对一个 32 位的变量算前导 0 个数的时候都是分离成 8 位的变量来计算,然后将这个 8 位的变量作为数组 CPU_CntLeadZerosTbl[]的索引,索引下对应的值就是这个 8 位变量的前导 0 个数。一个 8 位的变量的取值范围为 0~0XFF,这些值作为数组CPU_CntLeadZerosTbl[]的索引,每一个值的前导 0 个数都预先算出来作为该数组索引下的值。通过查 CPU_CntLeadZerosTbl[]这个表就可以很快的知道一个 8 位变量的前导 0 个数,根本不用计算,只是浪费了定义 CPU_CntLeadZerosTbl[]这个表的一点点空间而已,在处理器内存很充足的情况下,则优先选择这种空间换时间的方法。


10.2 就绪列表:


       准备好运行的任务的 TCB 都会被放到就绪列表中,系统可随时调度任务运行。 就绪列表在代码的层面上看就是一个 OS_RDY_LIST 数据类型的数组 OSRdyList[],数组的大小由宏 OS_CFG_PRIO_MAX 决定,支持多少个优先级, OSRdyList[]就有多少个成员。任务的优先级与 OSRdyList[]的索引一一对应,比如优先级 3 的任务的 TCB 会被放到 OSRdyList[3]中。 OSRdyList[]是一个在 os.h 文件中定义的全局变量,具体见代码清单 10-10。代码清单 10-10 OSRdyList[]数组定义
 

/* 就绪列表定义 */
OS_EXT OS_RDY_LIST OSRdyList[OS_CFG_PRIO_MAX];

        代码清单 10-10 中的数据类型 OS_RDY_LIST 在 os.h 中定义,专用于就绪列表,具体实现见代码清单 10-11。
        代码清单 10-11 OS_RDY_LIST 定义

typedef struct os_rdy_list OS_RDY_LIST; (1)
struct os_rdy_list {
OS_TCB *HeadPtr; (2)
OS_TCB *TailPtr;
OS_OBJ_QTY NbrEntries; (3)
};

       代码清单 10-11(1):在 uC/OS-III 中,内核对象的数据类型都会用大写字母重新定义
       代码清单 10-11(2): OSRdyList[]的成员与任务的优先级一一对应,同一个优先级的多个任务会以双向链表的形式存在 OSRdyList[]同一个索引下,那么 HeadPtr 就用于指向链表的头节点, TailPtr 用于指向链表的尾节点,该优先级下的索引成员的地址则称为该优先级下双向链表的根节点,知道根节点的地址就可以查找到该链表下的每一个节点。
       代码清单 10-11(3): NbrEntries 表示 OSRdyList[]同一个索引下有多少个任务。一个空的就绪列表, OSRdyList[]索引下的 HeadPtr、 TailPtr 和 NbrEntrie 都会被初始化为 0,具体见图 10-6。

        就绪列表相关的所有函数都在 os_core.c 实现,这些函数都是以“OS_”开头,表示是OS 的内部函数,用户不能调用,这些函数的汇总具体见表格 10-2。
表格 10-2 就绪列表相关函数汇总

10.2.1 就绪列表函数讲解:


       在实现就绪列表相关函数之前,我们需要在结构体 os_tcb 中添加 Prio、 NextPtr 和PrevPtr 这三个成员,然后在 os.h 中定义两个全局变量 OSPrioCur 和 OSPrioHighRdy, 具体定义见代码清单 10-12。接下来要实现的就绪列表相关的函数会用到几个变量。
代码清单 10-12 就绪列表函数需要用到的变量定义

struct os_tcb {
CPU_STK *StkPtr;
CPU_STK_SIZE StkSize;
/* 任务延时周期个数 */
OS_TICK TaskDelayTicks;
/* 任务优先级 */
OS_PRIO Prio;
/* 就绪列表双向链表的下一个指针 */
OS_TCB *NextPtr;
/* 就绪列表双向链表的前一个指针 */
OS_TCB *PrevPtr;
};
/* 在 os.h 中定义 */
OS_EXT OS_PRIO OSPrioCur; /* 当前优先级 */
OS_EXT OS_PRIO OSPrioHighRdy; /* 最高优先级 */

1. OS_RdyListInit()函数:
       OS_RdyListInit()用于将就绪列表 OSRdyList[]初始化为空,初始化完毕之后具体示意图见图 10-6,具体实现见代码清单 10-13。
代码清单 10-13 OS_RdyListInit()函数

void OS_RdyListInit(void)
{
OS_PRIO i;
OS_RDY_LIST *p_rdy_list;
/* 循环初始化,所有成员都初始化为 0 */
for ( i=0u; i<OS_CFG_PRIO_MAX; i++ ) {
p_rdy_list = &OSRdyList[i];
p_rdy_list->NbrEntries = (OS_OBJ_QTY)0;
p_rdy_list->HeadPtr = (OS_TCB *)0;
p_rdy_list->TailPtr = (OS_TCB *)0;
}
}

2. OS_RdyListInsertHead()函数:
       OS_RdyListInsertHead()用于在链表头部插入一个 TCB 节点,插入的时候分两种情况,第一种是链表是空链表,第二种是链表中已有节点,具体示意图见图 10-7,具体的代码实
现见代码清单 10-14,阅读代码的时候最好配套示意图来理解。

代码清单 10-14 OS_RdyListInsertHead()函数

void OS_RdyListInsertHead (OS_TCB *p_tcb)
{
OS_RDY_LIST *p_rdy_list;
OS_TCB *p_tcb2;
/* 获取链表根部 */
p_rdy_list = &OSRdyList[p_tcb->Prio];
/* CASE 0: 链表是空链表 */
if (p_rdy_list->NbrEntries == (OS_OBJ_QTY)0) {
p_rdy_list->NbrEntries = (OS_OBJ_QTY)1;
p_tcb->NextPtr = (OS_TCB *)0;
p_tcb->PrevPtr = (OS_TCB *)0;
p_rdy_list->HeadPtr = p_tcb;
p_rdy_list->TailPtr = p_tcb;
}
/* CASE 1: 链表已有节点 */
else {
p_rdy_list->NbrEntries++;
p_tcb->NextPtr = p_rdy_list->HeadPt
p_tcb->PrevPtr = (OS_TCB *)0;
p_tcb2 = p_rdy_list->HeadPt
p_tcb2->PrevPtr = p_tcb;
p_rdy_list->HeadPtr = p_tcb;
}
}

3. OS_RdyListInsertTail()函数:
       OS_RdyListInsertTail()用于在链表尾部插入一个 TCB 节点,插入的时候分两种情况,第一种是链表是空链表,第二种是链表中已有节点,具体示意图见图 10-8,具体的代码实现见,阅读代码的时候最好配套示意图来理解。

代码清单 10-15 OS_RdyListInsertTail()函数

void OS_RdyListInsertTail (OS_TCB *p_tcb)
{
OS_RDY_LIST *p_rdy_list;
OS_TCB *p_tcb2;
/* 获取链表根部 */
p_rdy_list = &OSRdyList[p_tcb->Prio];
/* CASE 0: 链表是空链表 */
if (p_rdy_list->NbrEntries == (OS_OBJ_QTY)0) {
p_rdy_list->NbrEntries = (OS_OBJ_QTY)1;
p_tcb->NextPtr = (OS_TCB *)0;
p_tcb->PrevPtr = (OS_TCB *)0;
p_rdy_list->HeadPtr = p_tcb;
p_rdy_list->TailPtr = p_tcb;
}
/* CASE 1: 链表已有节点 */
else {
p_rdy_list->NbrEntries++;
p_tcb->NextPtr = (OS_TCB *)0;
p_tcb2 = p_rdy_list->TailPtr;
p_tcb->PrevPtr = p_tcb2;
p_tcb2->NextPtr = p_tcb;
p_rdy_list->TailPtr = p_tcb;
}
}

4. OS_RdyListInsert()函数:
       OS_RdyListInsert()用于将任务的 TCB 插入到就绪列表,插入的时候分成两步第一步是根据优先级将优先级表中的相应位置位,这个调用 OS_PrioInsert()函数来实现,第二步是根据优先级将任务的 TCB 放到 OSRdyList[优先级]中,如果优先级等于当前的优先级则插入到链表的尾部,否则插入到链表的头部,具体实现见代码清单 10-16。代码清单 10-16 OS_RdyListInsert()函数
 

/* 在就绪链表中插入一个 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);
}
}

5. OS_RdyListMoveHeadToTail()函数:
       OS_RdyListMoveHeadToTail()函数用于将节点从链表头部移动到尾部,移动的时候分四种情况第一种是链表为空,无事可做;第二种是链表只有一个节点,也是无事可做;第三种是链表只有两个节点;第四种是链表有两个以上节点,具体示意图见图 10-9,具体
代码实现见代码清单 10-17,阅读代码的时候最好配套示意图来理解。

代码清单 10-17 OS_RdyListMoveHeadToTail()函数
 

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_tcb3->PrevPtr = (OS_TCB *)0;
p_tcb1->NextPtr = (OS_TCB *)0;
p_tcb1->PrevPtr = p_tcb2;
p_tcb2->NextPtr = p_tcb1;
p_rdy_list->HeadPtr = p_tcb3;
p_rdy_list->TailPtr = p_tcb1;
break;
}
}

6. OS_RdyListRemove()函数:
        OS_RdyListRemove()函数用于从链表中移除一个节点,移除的时候分为三种情况,第一种是链表为空,无事可做;第二种是链表只有一个节点;第三种是链表有两个以上节点,具体示意图见图 10-10,具体代码实现见,阅读代码的时候最好配套示意图来理解。

代码清单 10-18 OS_RdyListRemove()函数
 

void OS_RdyListRemove (OS_TCB *p_tcb)
{
OS_RDY_LIST *p_rdy_list;
OS_TCB *p_tcb1;
OS_TCB *p_tcb2;
p_rdy_list = &OSRdyList[p_tcb->Prio];
/* 保存要删除的 TCB 节点的前一个和后一个节点 */
p_tcb1 = p_tcb->PrevPtr;
p_tcb2 = p_tcb->NextPtr;
/* 要移除的 TCB 节点是链表中的第一个节点 */
if (p_tcb1 == (OS_TCB *)0) {
/* 且该链表中只有一个节点 */
if (p_tcb2 == (OS_TCB *)0) {
/* 根节点全部初始化为 0 */
p_rdy_list->NbrEntries = (OS_OBJ_QTY)0;
p_rdy_list->HeadPtr = (OS_TCB *)0;
p_rdy_list->TailPtr = (OS_TCB *)0;
/* 清除在优先级表中相应的位 */
OS_PrioRemove(p_tcb->Prio);
}
/* 该链表中不止一个节点 */
else {
/* 节点减 1 */
p_rdy_list->NbrEntries--;
p_tcb2->PrevPtr = (OS_TCB *)0;
p_rdy_list->HeadPtr = p_tcb2;
}
}
/* 要移除的 TCB 节点不是链表中的第一个节点 */
else {
p_rdy_list->NbrEntries--;
p_tcb1->NextPtr = p_tcb2;
/* 如果要删除的节点的下一个节点是 0,即要删除的节点是最后一个节点 */
if (p_tcb2 == (OS_TCB *)0) {
p_rdy_list->TailPtr = p_tcb1;
} else {
p_tcb2->PrevPtr = p_tcb1;
}
}
/* 复位从就绪列表中删除的 TCB 的 PrevPtr 和 NextPtr 这两个指针 */
p_tcb->PrevPtr = (OS_TCB *)0;
p_tcb->NextPtr = (OS_TCB *)0;
}

10.3 main 函数:


本章 main 函数没有添加新的测试代码,只需理解章节内容即可。


10.4 实验现象:

解章节内容即可。
 

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值