【找到了一种简单易懂的实验实现方式,适合基础较薄弱的同学,见第二部分】
最终效果如下:
依次创建了3个任务线程,以One、Two、Three指代,时间分别为15秒、30秒、10秒。
如果按生成顺序输出应该是:One->Two->Three,但我们修改了OsPriQueueEnqueue函数,由原先的“先进先出”,转化为“短作业优先”,所以实际的执行顺序变成了Three->One->Two,满足实验所要求的“短作业优先”要求。
一、实验要求
(可忽略下面要求描述)
短作业优先调度策略为优化LiteOS的吞吐量。该策略在将线程TCB插入就绪队列时,按照线程执行的时间长短进行排序。运行时间短的线程先运行,运行时间长的线程后运行。该Proj要完成以下内容:
第1步:修改LiteOS内核的PriQueInsert函数,在其中添加相关代码,实现按照任务运行时间的长短将任务的TCB插入就绪队列。
解析:很多同学可能并没有找到PriQueInsert函数,其实是因为韦东山老师在这里下载的是OpenHarmony的1.0版本,这个是2019年的早期版本,至今Openharmony已到4.0版本:
但是不建议大家下载更高版本,因为可能会面临无法打补丁的问题,最终导致无法编译。所以我们仍旧在OpenHarmony的1.0版本中进行修改,最终实现编译。
下面介绍一下下载高版本OpenHarmony的方法,我在这里下载的是3.2的Release版本:
repo init -u https://gitee.com/openharmony/manifest.git -b OpenHarmony-3.2-Release
repo sync -c -j8
(下载的时间略长,差不多1小时...)
在新版本中具有PriQueInsert函数,展示在这里:
第2步:在短作业优先调度策略中,采用LiteOS中定义的SchedParam结构体中的timeSlice成员作为该作业的运行时间。
解析:SchedParam结构体我找了5分钟,发现是在liteos_a/kernel/base/include下的los_sched_pri.h文件夹里。
这里补充一下:进入到.h或者.c文件后,可以用快捷键Ctrl+F进行关键词搜索。
需要具备一个意识:可以顺着头文件去进行搜寻,效率较高。
第3步:在用户层生成至少3个线程,通过pthread_attr_getschedparam函数和pthread_attr_setschedparam函数,修改所生成线程的调度策略为“短作业优先“调度策略,并对timeSlice进行赋值,表示该线程的运行时间。
解析:pthread_attr_getschedparam函数和pthread_attr_setschedparam函数是属于POSIX线程库提供的函数,没有具体的实现方法,只有定义。
所在的位置是在openharmony/third_party/musl/include下的pthread.h文件里:
pthread_attr_getschedparam函数:用于获取线程的调度参数,包括线程的优先级等信息。
pthread_attr_setschedparam函数:用于设置线程的调度参数,包括线程的优先级等信息。
这里补充一下:大家可以直接在Files文件夹的根目录里,按下快捷键Ctrl+F,然后输入pthread.h直接搜索到这个文件:
第4步:验证你所编写的调度策略是正确的。线程内部可以用while语句循环timeSlice规定的时间,然后打印自己的TID后退出。例如,对这3个线程的timeSlice分别赋予1、5、9。那么这3个线程也按照这个顺序完成,即第一个线程1秒后退出,第二个线程5秒后退出,第三个线程9秒后退出。
用户层采用pthread库实现,相关代码参考下面博客:
二、实验过程
修改步骤:
第1步:修改优先级队列函数OsPriQueueEnqueue,在openharmony/kernel/liteos_a/kernel/base/sched/sched_sq/los_priqueue.c中。
代码大致实现思路是:通过比较thisTime和frontTime的大小,来安排链表结点的顺序,时间短的排在前面,时间长的排在后面;排在前面的结点先执行,排在后面的后执行,从而实现“短作业优先”。
代码如下,可直接复制粘贴:
VOID OsPriQueueEnqueue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem, UINT32 priority)
{
if (LOS_ListEmpty(&priQueueList[priority])) {
*bitMap |= PRIQUEUE_PRIOR0_BIT >> priority;
LOS_ListTailInsert(&priQueueList[priority], priqueueItem);
}else{
LOS_DL_LIST *currentItem = priQueueList[priority].pstNext;
LOS_DL_LIST *prevItem = &priQueueList[priority];
while(currentItem != &priQueueList[priority]){
UINT32 thisTime = *(UINT32*)(priqueueItem + 1);
UINT32 frontTime = *(UINT32*)(currentItem + 1);
if(thisTime < frontTime){
priqueueItem->pstNext = currentItem;
prevItem->pstNext = priqueueItem;
if(prevItem->pstPrev){
priqueueItem->pstPrev = prevItem;
}
if(currentItem->pstPrev){
currentItem->pstPrev = priqueueItem;
}
return;
}
prevItem = currentItem;
currentItem = currentItem->pstNext;
}
prevItem->pstNext = priqueueItem;
priqueueItem->pstNext = &priQueueList[priority];
priqueueItem->pstPrev = prevItem;
}
}
第2步:修改LosTaskCB结构体,在openharmony/kernel/liteos_a/kern
第3步:修改TSK_INIT_PARAM_S结构体,在openharmony/kernel/liteos_a/kernel/include/los_task.h中。
在policy下添加一条UINT32 RunTime;
第4步:修改任务赋值(初始化)函数,在openharmony/kernel/liteos_a/kernel/base/core/los_task. c中。
在taskCB->RunTime下添加一条:taskCB->RunTime = initParam->RunTime;
第5步:编写验证测试程序
借用Project1旭哥的系统调用函数:在openharmony/kernel/liteos_a/syscall下hx_syscall.c函数里。
大致解释一下代码:
void HxSyscall(int num)是函数的入口。
然后分别定义了3个任务(见上图的1 2 3),任务的具体参数都是赋值到initParam里,initParam是TSK_INIT_PARAM_S结构体(参考之前的第3步),重要的是initParam.RunTime所赋的值,这就是运行时间。
LOS_TaskCreate()函数就是生成线程的函数(具体实现见第三部分的第1节)。
代码如下,可直接复制粘贴:
#include "los_printf.h"
#include "los_task_pri.h"
#include "los_base_pri.h"
#include "los_priqueue_pri.h"
#include "los_sem_pri.h"
#include "los_event_pri.h"
#include "los_mux_pri.h"
#include "los_hw_pri.h"
#include "los_exc.h"
#include "los_memstat_pri.h"
#include "los_mp.h"
#include "los_spinlock.h"
#include "los_percpu_pri.h"
#include "los_process_pri.h"
#ifdef __cplusplus
#if __cplusplus
#endif
#endif
#include "stdio.h"
#include "los_task_pri.h"
UINT32 FIRST;
UINT32 SECOND;
UINT32 THIRD;
#define PRIOR 1
void PRIOR_FIRST_TASK(VOID)
{
PRINTK("Task:One , RunTime:15 s\r\n");
return;
}
void PRIOR_SECOND_TASK(VOID)
{
PRINTK("Task:Two , RunTime:30 s\r\n");
return;
}
void PRIOR_THIRD_TASK(VOID)
{
PRINTK("Task:Three , RunTime:10 s\r\n");
return;
}
void HxSyscall(int num){
UINT32 ret;
TSK_INIT_PARAM_S initParam;
LOS_TaskLock();
PRINTK("\nLOS_TaskLock() Success!\r\n\n");
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)PRIOR_FIRST_TASK;
initParam.usTaskPrio = PRIOR;
initParam.pcName = "PRIOR_FIRST_TASK";
initParam.uwStackSize = OS_TASK_RESOURCE_STATCI_SIZE;
initParam.RunTime = 15;
initParam.uwResved = LOS_TASK_STATUS_DETACHED;
ret = LOS_TaskCreate(&FIRST,&initParam);
if(ret != LOS_OK)
{
LOS_TaskUnlock();
PRINTK("Failed_1!\r\n");
return;
}
PRINTK("Init_Task1_Success!\r\n");
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)PRIOR_SECOND_TASK;
initParam.usTaskPrio = PRIOR;
initParam.pcName = "PRIOR_SECOND_TASK";
initParam.uwStackSize = OS_TASK_RESOURCE_STATCI_SIZE;
initParam.RunTime = 30;
initParam.uwResved = LOS_TASK_STATUS_DETACHED;
ret = LOS_TaskCreate(&SECOND,&initParam);
if(ret != LOS_OK)
{
LOS_TaskUnlock();
PRINTK("Failed_2!\r\n");
return;
}
PRINTK("Init_Task2_Success!\r\n");
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)PRIOR_THIRD_TASK;
initParam.usTaskPrio = PRIOR;
initParam.pcName = "PRIOR_THIRD_TASK";
initParam.uwStackSize = OS_TASK_RESOURCE_STATCI_SIZE;
initParam.RunTime = 10;
initParam.uwResved = LOS_TASK_STATUS_DETACHED;
ret = LOS_TaskCreate(&THIRD,&initParam);
if(ret != LOS_OK)
{
LOS_TaskUnlock();
PRINTK("Failed_3!\r\n");
return;
}
PRINTK("Init_Task3_Success!\r\n\n");
LOS_TaskUnlock();
return;
}
第6步:开发板测试
按照Project1的方式运行,如果你还不会,请我见下面博客。
鸿蒙LiteOs读源码教程+向LiteOS中添加一个系统调用-CSDN博客
实验结果如下,很清晰:
这种方式较简单,适合基础较薄弱的同学,快的话估计耗时20~30分钟。
【如果有帮助记得点赞👍收藏☀️嗷~】
三、实验原理讲解(待更新)
第1节:任务创建解析
上面第5步的LOS_TaskCreate函数是在下面的文件路径下:
具体的代码放在下面,重点是OS_TASK_SCHED_QUEUE_ENQUEUE(taskCB,0)这段代码:
大家可能会问,上面这个函数是如何与OsPriQueueEnqueue函数连接在一起的?
其实连接的过程很曲折,又很巧妙,大致过程如下:LOS_TaskCreate -> OS_TASK_SCHED_QUEUE_ENQUEUE -> OsTaskSchedQueueEnqueue -> OS_PROCESS_PRI_QUEUE_ENQUEUE -> OsPriQueueEnqueue
像OS_TASK_SCHED_QUEUE_ENQUEUE是个宏,对应的是OsTaskSchedQueueEnqueue函数,在OsTaskSchedQueueEnqueue函数里调用了OS_PROCESS_PRI_QUEUE_ENQUEUE宏,这个宏对应的正是OsPriQueueEnqueue。
第2节:OsPriQueueEnqueue的实现
先看一下OsPriQueueEnqueue初始时的实现,其实是一种“先进先出”的优先级队列。
注意传入的参数:
priQueueList是一个指向优先级队列数组的指针,每个数组元素都是一个 LOS_DL_LIST
结构,代表一个优先级。
bitMap是一个指向位图的指针,位图用来快速检索非空优先级队列。
priqueueItem是一个指向将要加入队列的项的指针。
priority是一个 UINT32
类型的变量,表示 priqueueItem
将要被插入的优先级。
注意参数类型:UINT32不过多赘述,是一个32位的二进制位串。LOS_DL_LIST是一个双向链表结构体,pstPrev是前驱节点,pstNext是后继节点:
VOID OsPriQueueEnqueue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem, UINT32 priority){
LOS_ASSERT(priqueueItem->pstNext == NULL); //是一个断言,确保 priqueueItem 没有已经加入其他链表。指针不是 NULL,说明 priqueueItem已经在链表中
if (LOS_ListEmpty(&priQueueList[priority])) { //这是一个检查操作,看在 priority 优先级的链表是否为空
*bitMap |= PRIQUEUE_PRIOR0_BIT >> priority; //如果对应优先级的链表为空,则在位图中设置对应位。将这个最高位设置到位图的相应位置上,表示现在这个优先级不再为空。
}
LOS_ListTailInsert(&priQueueList[priority], priqueueItem);//这行代码执行插入操作,将 priqueueItem 插入到对应优先级的链表末尾。说明基于先进先出原则。
}
如果大家还感兴趣可以所调用函数的具体实现:
更新后的函数解释如下,感兴趣的同学可以自行阅读: