向LiteOS中添加一个简单的基于线程运行时的短作业优先调度策略

【找到了一种简单易懂的实验实现方式,适合基础较薄弱的同学,见第二部分】

最终效果如下:

依次创建了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

el/base/include/los_task_pri.h中。
在pendList下(注意一定要在pendList下添加)添加一条 UINT32          RunTime; 

第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 插入到对应优先级的链表末尾。说明基于先进先出原则。
}

如果大家还感兴趣可以所调用函数的具体实现:

更新后的函数解释如下,感兴趣的同学可以自行阅读:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值