HarmonyOS内核进程调度

一、位图管理 BitMap

内核很多模块都使用了位图,我们将具体分析进程调度相关的位图管理。我们知道鸿蒙内核进程和线程都是32个优先级,而之所以是32个优先级,主要就是因为他们的优先级是由位图管理的,BitMap是UINT32的变量,所以进程和线程都是32个优先级,一个位一个级别,最高位优先级最低。

    UINT32          priBitMap;          /**< BitMap for recording the change of task priority,
                                             the priority can not be greater than 31 */

进程和线程在执行过程中优先级会经常变化,变量priBitMap就是用来记录所有曾经变化过的优先级(曾经有过的所有优先级历史记录),例如 0x0000004B = 0b0000 0000 0000 0000 0000 0000 0100 1011就代表该Task曾经有过 0,1,3,6 这几种优先级(0代表最高优先级)。我们还可以通过位图管理器函数,得到该Task有过的最高优先级(1出现的最低位置)和最低优先级(1出现的最高位置)。

    UINT32      queueBitmap;  //位图调度器,每一位对应一个优先级,用于标识对应优先级的就绪队列是否有就绪任务

除此之外,还可用位图表示任务状态,Lite_OS定义了六种任务状态。用每一位来表示一种不同的状态,1表示是,0表示不是。

#define OS_TASK_STATUS_INIT 0x0001U //初始化状态 
#define OS_TASK_STATUS_READY 0x0002U //就绪状态的任务都将插入就绪队列,注意就绪队列 的本质是个双向链表 
#define OS_TASK_STATUS_RUNNING 0x0004U //运行状态 
#define OS_TASK_STATUS_SUSPENDED 0x0008U
#define OS_TASK_STATUS_PENDING 0x0010U //阻塞状态 
#define OS_TASK_STATUS_PEND_TIME 0x0080U
#define OS_TASK_STATUS_DELAY 0x0020U //延期状态 
#define OS_TASK_STATUS_TIMEOUT 0x0040U //任务超时

采用这种数据形式,可以允许多种标签同时存在,比如 0x07 = 0b00000111,对应以上定义就是任务有三个标签(初始,就绪,和运行),进程和线程在运行期间是允许多种标签同时存在的。

对位的管理/运算需要有个专门的管理器:位图管理器 (见源码 los_bitmap.c )。位操作提供了4个API,进行置1、清0、获取为1的最高、最低位等操作,如下:

接口名 描述
LOS_BitmapSet 对状态字的某一标志位进行置1操作
LOS_BitmapClr 对状态字的某一标志位进行清0操作
LOS_HighBitGet 获取状态字中为1的最高位
LOS_LowBitGet 获取状态字中为1的最低位

更详细分析可参考LiteOS内核源码分析系列五 LiteOS内核位操作模块

二、双向链表 LOS_DL_LIST

双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向其前一个节点的指针。其头指针 head 是唯 一确定的。从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便, 特别是大量数据的遍历。由于双向链表具有对称性,能方便地完成各种插入、删除等操作。LOS_DL_LIST结构很简单,如下所示:

typedef struct LOS_DL_LIST {//双向链表,内核最重要结构体 
	struct LOS_DL_LIST *pstPrev; /**< Current node's pointer to the previous node *///前驱节点(左手) 
	struct LOS_DL_LIST *pstNext; /**< Current node's pointer to the next node *///后继节点(右手) 
} LOS_DL_LIST;

HarmonyOS内核进程调度_第1张图片
在创建双向链表时,可以先创建一个节点作为Head头节点,链表的节点从HEAD节点开始挂载。从head节点的依次遍历下一个节点,最后一个不等于Head节点的节点称之为Tail尾节点。这个Tail节点也是Head节点的前驱。从Head向前查找,可以更快的找到Tail节点。

内核的各个模块都能看到双向链表的身影。理解双向链表LOS_DL_LIST 及相关函数是读懂鸿蒙内核的关键,它是鸿蒙内核最重要的结构体。关于双向链表LOS_DL_LIST的初始化以及对双向链表的插入删除等操作定义在 kernel_liteos_a-master\kernel_liteos_a-master\kernel\include\los_list.h 文件中。下面是截取的相关接口及其功能描述:

接口名 功能描述
LOS_ListInit 对链表进行初始化
LOSListAdd 将新节点添加到链表中
LOS_ListTailInsert 将节点插入到双向链表尾部
LOS_ListHeadInsert 将节点插入到双向链表头部
LOS_ListDelete 将指定的节点从链表中删除
LOS_ListEmpty 判断链表是否为空
LOS_ListDelInit 将指定的节点从链表中删除使用该节点初始化链表
LOS_ListTailInsertList 将链表插入到双向链表尾部
LOS_ListHeadInsertList 将链表插入到双向链表头部

LOS_DL_LIST 是复杂结构体的最爱,在进程控制块定义的时候会用到双向链表描述一个进程的所有信息,在SortLinkList排序链表的定义使用上也起到了很重要的作用,在任务调度切换函数OsSchedTaskSwicth()中就有调用到一个排序链表插入函数OsAdd2SortLink() ,具体定义如下:

LITE_OS_SEC_TEXT VOID OsAdd2SortLink(const SortLinkAttribute *sortLinkHeader, SortLinkList *sortList)
{
    SortLinkList *listSorted = NULL;
    LOS_DL_LIST *listObject = NULL;if (sortList->idxRollNum > OS_TSK_MAX_ROLLNUM) {
        SET_SORTLIST_VALUE(sortList, OS_TSK_MAX_ROLLNUM);
    }
    listObject = sortLinkHeader->sortLink;if (listObject->pstNext == listObject) {
        LOS_ListTailInsert(listObject, &sortList->sortLinkNode);
    } else {
⑶      listSorted = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
        do {if (ROLLNUM(listSorted->idxRollNum) <= ROLLNUM(sortList->idxRollNum)) {
                ROLLNUM_SUB(sortList->idxRollNum, listSorted->idxRollNum);
            } else {ROLLNUM_SUB(listSorted->idxRollNum, sortList->idxRollNum);
                break;
            }
 
⑹         listSorted = LOS_DL_LIST_ENTRY(listSorted->sortLinkNode.pstNext, SortLinkList, sortLinkNode);
        } while (&listSorted->sortLinkNode != listObject);LOS_ListTailInsert(&listSorted->sortLinkNode, &sortList->sortLinkNode);
    }
}

分析函数定义,包含2个参数,第一个参数sortLinkHeader用于指定排序链表的头结点,第二个参数sortList是待插入的链表节点,此时该节点的滚动数等于对应阻塞任务或定时器的超时时间。⑴处代码处理滚动数超大的场景,如果滚动数大于OS_TSK_MAX_ROLLNUM,则设置滚动数等于OS_TSK_MAX_ROLLNUM。⑵处代码,如果排序链表为空, 则把链表节点尾部插入。如果排序链表不为空,则执行⑶处代码,获取排序链表上的下一个节点SortLinkList *listSorted。⑷、⑸ 处代码,如果待插入节点的滚动数大于排序链表的下一个节点的滚动数,则把待插入节点的滚动数减去下一个节点的滚动数,并继续执行⑹处代码,继续与下下一个节点进行比较。否则,如果待插入节点的滚动数小于排序链表的下一个节点的滚动数,则把下一个节点的滚动数减去待插入节点的滚动数,然后跳出循环,继续执行⑺处代码,完成待插入节点的插入。
参考:LiteOS:盘点那些重要的数据结构

三、调度队列

在任务调度模块,就绪队列是个重要的数据结构,就绪队列需要支持初始化,出入队列,从队列获取最高优先级任务等操作。鸿蒙内核进程和线程各有32个就绪队列,进程队列用全局变量存放, 创建进程时入队。队列初始化可参考OsSchedInit()函数。
线程队列中放同等优先级的task,在初始化时内核一次性创建了32个双向循环链表,每种优先级都有一个队列来记录就绪状态的tasks的位置,g_sched分配的是一个连续的内存块,存放了32个双向链表。

//* 0x80000000U = 10000000000000000000000000000000(32位,1是用于移位的)
#define OS_PRIORITY_QUEUE_NUM      32  //优先级级别数
#define PRIQUEUE_PRIOR0_BIT        0x80000000U  //第一优先级位图,所有优先级位图由该位图右移对应优先级得到

/* 任务调度队列集合 */
typedef struct {
    LOS_DL_LIST priQueueList[OS_PRIORITY_QUEUE_NUM];  //各优先级任务就绪队列,默认32级
    UINT32      readyTasks[OS_PRIORITY_QUEUE_NUM];  //各优先级任务队列中就绪任务个数
    UINT32      queueBitmap;  //位图调度器,每一位对应一个优先级,用于标识对应优先级的就绪队列是否有就绪任务
} SchedQueue;

/* 进程调度队列 */
typedef struct {
    SchedQueue queueList[OS_PRIORITY_QUEUE_NUM];  //进程优先级调度队列,默认32级
    UINT32     queueBitmap;  //位图调度器,每一位对应一个优先级,用于标识对应优先级是否有就绪进程
    SchedScan  taskScan;  //函数指针,扫描任务
    SchedScan  swtmrScan;  //函数指针,扫描定时器
} Sched;

STATIC Sched *g_sched = NULL;  //全局调度器

队列数据结构如下图所示:HarmonyOS内核进程调度_第2张图片
鸿蒙系统的调度是抢占式的,task分成32个优先级,位图调度器就是一个32位的变量,它的每一位来标记对应队列中是否有任务,在位图调度下,任务优先级的值越小则代表具有越高的优先级,每当需要进行调度时,从最低位向最高位查找出第一个置 1 的位的所在位置,即为当前最高优先级,然后从 对应优先级就绪队列获得相应的任务控制块,整个调度器的实现复杂度是 O(1),即无论任务多少,其调度时间是固定的。

四、官方文档进程状态迁移说明

Init→Ready: 进程创建或fork时,拿到该进程控制块后进入Init状态,处于进程初始化阶段,当进程初始化完成将进程插入调度队列,此时进程进入就绪状 态。
Ready→Running: 进程创建后进入就绪态,发生进程切换时,就绪列表中最高优先级的进程被执行,从而进入运行态。若此时该进程中已无其它线程处于就绪态, 则该进程从就绪列表删除,只处于运行态;若此时该进程中还有其它线程处于就绪态,则该进程依旧在就绪队列,此时进程的就绪态和运行态共 存。
Running→Pend: 进程内所有的线程均处于阻塞态时,进程在最后一个线程转为阻塞态时,同步进入阻塞态,然后发生进程切换。
Pend→Ready / Pend→Running: 阻塞进程内的任意线程恢复就绪态时,进程被加入到就绪队列,同步转为就绪态,若此时发生进程切换,则进程状态由就绪态转为运行态。
Ready→Pend: 进程内的最后一个就绪态线程处于阻塞态时,进程从就绪列表中删除,进程由就绪态转为阻塞态。
Running→Ready: 进程由运行态转为就绪态的情况有以下两种: 有更高优先级的进程创建或者恢复后,会发生进程调度,此刻就绪列表中最高优先级进程变为运行态,那么原先运行的进程由运行态变为就 绪态。 若进程的调度策略为SCHED_RR,且存在同一优先级的另一个进程处于就绪态,则该进程的时间片消耗光之后,该进程由运行态转为就绪 态,另一个同优先级的进程由就绪态转为运行态。 Running→Zombies: 当进程的主线程或所有线程运行结束后,进程由运行态转为僵尸态,等待父进程回收资源。

五、HarmonyOS内核进程调度相关函数

参考文章HarmonyOS内核进程调度相关函数

本篇文章内容为操作系统兴趣小组成员共同学习成果总结,同时参考了其他作者的总结分析文章,具体参考都已在文中标明。

你可能感兴趣的:(harmonyos,链表,华为,操作系统)