在鸿蒙轻内核源码分析系列一和系列二,我们分析了双向循环链表、优先级就绪队列的源码。本文会继续给读者介绍鸿蒙轻内核源码中重要的数据结构:任务排序链表TaskSortLinkAttr
。鸿蒙轻内核的任务排序链表,用于任务延迟到期/超时唤醒等业务场景,是一个非常重要、非常基础的数据结构。本文中所涉及的源码,以OpenHarmony LiteOS-M
内核为例。
1 任务排序链表
我们先看下任务排序链接的数据结构。任务排序链表是一个环状的双向链表数组,任务排序链表属性结构体TaskSortLinkAttr
作为双向链表的头结点,指向双向链表数组的第一个元素,还维护游标信息,记录当前的位置信息。我们先看下排序链表属性的结构体的定义。
1.1 任务排序链表属性结构体定义
在kernel\include\los_task.h
头文件中定义了排序链表属性的结构体TaskSortLinkAttr
。该结构体定义了排序链表的头节点LOS_DL_LIST *sortLink
,游标UINT16 cursor
,还有一个保留字段,暂时没有使用。
源码如下:
typedef struct {
LOS_DL_LIST *sortLink;
UINT16 cursor;
UINT16 reserved;
} TaskSortLinkAttr;
在文件kernel\src\los_task.c
中定义了排序链表属性结构体TaskSortLinkAttr
类型的全局变量g_taskSortLink
,该全局变量的成员变量sortLink
作为排序链表的头结点,指向一个长度为32的环状的双向链表数组,成员变量cursor
作为游标记录环状数组的当前游标位置。源代码如下。
LITE_OS_SEC_BSS TaskSortLinkAttr g_taskSortLink;
我们使用示意图来讲述一下。任务排序链表是环状双向链表数组,长度为32,每一个元素是一个双向链表,挂载任务LosTaskCB
的链表节点timerList
。任务LosTaskCB
的成员变量idxRollNum
记录数组的索引和滚动数。全局变量g_taskSortLink
的成员变量cursor
记录当前游标位置,每过一个Tick
,游标指向下一个位置,转一轮需要32 ticks
。当运行到的数组位置,双向链表不为空,则把第一个节点维护的滚动数减1。这样的数据结构类似钟表表盘,也称为时间轮
。
我们举个例子来说明,基于时间轮实现的任务排序链表是如何管理任务延迟超时的。假如当前游标cursor
为1,当一个任务需要延时72 ticks
,72=2*32+8,表示排序索引sortIndex
为8,滚动数rollNum
为2。会把任务插入数组索引为sortIndex
+cursor
=9的双向链表位置,索要9处的双向链表维护节点的滚动为2。随着Tick
时间的进行,从当前游标位置运行到数组索引位置9,历时8 ticks
。运行到9时,如果滚动数大于0,则把滚动数减1。等再运行2轮,共需要72 ticks
,任务就会延迟到期,可以从排序链表移除。每个数组元素对应的双向链表的第一个链表节点的滚动数表示需要转多少轮,节点任务才到期。第二个链表节点的滚动数需要加上第一个节点的滚动数,表示第二个节点需要转的轮数。依次类推。
示意图如下:
1.2 任务排序链表宏定义
在OS_TSK_SORTLINK_LEN
头文件中定义了一些和任务排序链表相关的宏定义。延迟任务双向链表数组的长度定义为32,高阶bit
位位数为5,低阶bit
位位数为27。对于任务的超时时间,取其高27位作为滚动数,低5位作为数组索引。
源码如下:
/**
* 延迟任务双向链表数组的数量(桶的数量):32
*/
#define OS_TSK_SORTLINK_LEN 32
/**
* 高阶bit位数目:5
*/
#define OS_TSK_HIGH_BITS 5U
/**
* 低阶bit位数目:27
*/
#define OS_TSK_LOW_BITS (32U - OS_TSK_HIGH_BITS)
/**
* 滚动数最大值:0xFFFF FFDF,1111 0111 1111 1111 1111 1111 1101 1111
*/
#define OS_TSK_MAX_ROLLNUM (0xFFFFFFFFU - OS_TSK_SORTLINK_LEN)
/**
* 任务延迟时间数的位宽:5
*/
#define OS_TSK_SORTLINK_LOGLEN 5
/**
* 延迟任务的桶编号的掩码:31、0001 1111
*/
#define