• 鸿蒙LiteOs读源码教程+向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版本:

    1. repo init -u https://gitee.com/openharmony/manifest.git -b OpenHarmony-3.2-Release
    2. 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的大小,来安排链表结点的顺序,时间短的排在前面,时间长的排在后面;排在前面的结点先执行,排在后面的后执行,从而实现“短作业优先”。

    代码如下,可直接复制粘贴:

    1. VOID OsPriQueueEnqueue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem, UINT32 priority)
    2. {
    3. if (LOS_ListEmpty(&priQueueList[priority])) {
    4. *bitMap |= PRIQUEUE_PRIOR0_BIT >> priority;
    5. LOS_ListTailInsert(&priQueueList[priority], priqueueItem);
    6. }else{
    7. LOS_DL_LIST *currentItem = priQueueList[priority].pstNext;
    8. LOS_DL_LIST *prevItem = &priQueueList[priority];
    9. while(currentItem != &priQueueList[priority]){
    10. UINT32 thisTime = *(UINT32*)(priqueueItem + 1);
    11. UINT32 frontTime = *(UINT32*)(currentItem + 1);
    12. if(thisTime < frontTime){
    13. priqueueItem->pstNext = currentItem;
    14. prevItem->pstNext = priqueueItem;
    15. if(prevItem->pstPrev){
    16. priqueueItem->pstPrev = prevItem;
    17. }
    18. if(currentItem->pstPrev){
    19. currentItem->pstPrev = priqueueItem;
    20. }
    21. return;
    22. }
    23. prevItem = currentItem;
    24. currentItem = currentItem->pstNext;
    25. }
    26. prevItem->pstNext = priqueueItem;
    27. priqueueItem->pstNext = &priQueueList[priority];
    28. priqueueItem->pstPrev = prevItem;
    29. }
    30. }

    第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节)。

    代码如下,可直接复制粘贴:

    1. #include "los_printf.h"
    2. #include "los_task_pri.h"
    3. #include "los_base_pri.h"
    4. #include "los_priqueue_pri.h"
    5. #include "los_sem_pri.h"
    6. #include "los_event_pri.h"
    7. #include "los_mux_pri.h"
    8. #include "los_hw_pri.h"
    9. #include "los_exc.h"
    10. #include "los_memstat_pri.h"
    11. #include "los_mp.h"
    12. #include "los_spinlock.h"
    13. #include "los_percpu_pri.h"
    14. #include "los_process_pri.h"
    15. #ifdef __cplusplus
    16. #if __cplusplus
    17. #endif
    18. #endif
    19. #include "stdio.h"
    20. #include "los_task_pri.h"
    21. UINT32 FIRST;
    22. UINT32 SECOND;
    23. UINT32 THIRD;
    24. #define PRIOR 1
    25. void PRIOR_FIRST_TASK(VOID)
    26. {
    27. PRINTK("Task:One , RunTime:15 s\r\n");
    28. return;
    29. }
    30. void PRIOR_SECOND_TASK(VOID)
    31. {
    32. PRINTK("Task:Two , RunTime:30 s\r\n");
    33. return;
    34. }
    35. void PRIOR_THIRD_TASK(VOID)
    36. {
    37. PRINTK("Task:Three , RunTime:10 s\r\n");
    38. return;
    39. }
    40. void HxSyscall(int num){
    41. UINT32 ret;
    42. TSK_INIT_PARAM_S initParam;
    43. LOS_TaskLock();
    44. PRINTK("\nLOS_TaskLock() Success!\r\n\n");
    45. initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)PRIOR_FIRST_TASK;
    46. initParam.usTaskPrio = PRIOR;
    47. initParam.pcName = "PRIOR_FIRST_TASK";
    48. initParam.uwStackSize = OS_TASK_RESOURCE_STATCI_SIZE;
    49. initParam.RunTime = 15;
    50. initParam.uwResved = LOS_TASK_STATUS_DETACHED;
    51. ret = LOS_TaskCreate(&FIRST,&initParam);
    52. if(ret != LOS_OK)
    53. {
    54. LOS_TaskUnlock();
    55. PRINTK("Failed_1!\r\n");
    56. return;
    57. }
    58. PRINTK("Init_Task1_Success!\r\n");
    59. initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)PRIOR_SECOND_TASK;
    60. initParam.usTaskPrio = PRIOR;
    61. initParam.pcName = "PRIOR_SECOND_TASK";
    62. initParam.uwStackSize = OS_TASK_RESOURCE_STATCI_SIZE;
    63. initParam.RunTime = 30;
    64. initParam.uwResved = LOS_TASK_STATUS_DETACHED;
    65. ret = LOS_TaskCreate(&SECOND,&initParam);
    66. if(ret != LOS_OK)
    67. {
    68. LOS_TaskUnlock();
    69. PRINTK("Failed_2!\r\n");
    70. return;
    71. }
    72. PRINTK("Init_Task2_Success!\r\n");
    73. initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)PRIOR_THIRD_TASK;
    74. initParam.usTaskPrio = PRIOR;
    75. initParam.pcName = "PRIOR_THIRD_TASK";
    76. initParam.uwStackSize = OS_TASK_RESOURCE_STATCI_SIZE;
    77. initParam.RunTime = 10;
    78. initParam.uwResved = LOS_TASK_STATUS_DETACHED;
    79. ret = LOS_TaskCreate(&THIRD,&initParam);
    80. if(ret != LOS_OK)
    81. {
    82. LOS_TaskUnlock();
    83. PRINTK("Failed_3!\r\n");
    84. return;
    85. }
    86. PRINTK("Init_Task3_Success!\r\n\n");
    87. LOS_TaskUnlock();
    88. return;
    89. }

    第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是后继节点:

    1. VOID OsPriQueueEnqueue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem, UINT32 priority){
    2. LOS_ASSERT(priqueueItem->pstNext == NULL); //是一个断言,确保 priqueueItem 没有已经加入其他链表。指针不是 NULL,说明 priqueueItem已经在链表中
    3. if (LOS_ListEmpty(&priQueueList[priority])) { //这是一个检查操作,看在 priority 优先级的链表是否为空
    4. *bitMap |= PRIQUEUE_PRIOR0_BIT >> priority; //如果对应优先级的链表为空,则在位图中设置对应位。将这个最高位设置到位图的相应位置上,表示现在这个优先级不再为空。
    5. }
    6. LOS_ListTailInsert(&priQueueList[priority], priqueueItem);//这行代码执行插入操作,将 priqueueItem 插入到对应优先级的链表末尾。说明基于先进先出原则。
    7. }

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

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

  • 相关阅读:
    测试/开发程序员该不该跳槽?别忘了当初的梦想......
    「译文」Google SRE 二十年的经验教训
    Gateway基本配置
    网页视频F12倍速看
    闭包、IIFE立即执行函数
    系统监控
    第三方软件检测机构有哪些资质,2023年软件测评公司推荐
    Numpy、Pandas使用大全与各参数详解
    Pikachu上的CSRF以及NSSCTF上的[NISACTF 2022]bingdundun~、 [SWPUCTF 2022 新生赛]xff
    【云原生之Docker实战】使用Docker部署excalidraw-cn白板工具
  • 原文地址:https://blog.csdn.net/RuanFun/article/details/134200806