【找到了一种简单易懂的实验实现方式,适合基础较薄弱的同学,见第二部分】
最终效果如下:
依次创建了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库实现,相关代码参考下面博客:
Linux线程调度策略以及优先级实验(图文)
二、实验过程
修改步骤:
第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 插入到对应优先级的链表末尾。说明基于先进先出原则。
}
如果大家还感兴趣可以所调用函数的具体实现:
更新后的函数解释如下,感兴趣的同学可以自行阅读:文章来源:https://www.toymoban.com/news/detail-743194.html
文章来源地址https://www.toymoban.com/news/detail-743194.html
到了这里,关于鸿蒙LiteOs读源码教程+向LiteOS中添加一个简单的基于线程运行时的短作业优先调度策略的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!