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

本篇文章采用了在内核写死代码的方式,通过TaskId来进行线程属性设置,实现起来虽然较为死板但是比较简便

一、实验目的

1、理解Liteos-a中的任务调度,增加一个短作业优先策略(SJF, Shortest Job First)

二、实验环境

1.物理机:windows操作系统     

2.VMware虚拟机:ubuntu 18.04.6

3.开发板:imx6ull Mini

三、实验内容

0.前置说明

       本次实验会向鸿蒙中增加短作业优先调度策略,但是依旧会受到时钟中断的影响(具体在后续过程说明),并且作用的范围是我们在应用程序中通过pthread创建的三个线程,这三个线程我们会提供一个预测运行时间,之后预测时间最短的线程先完成执行,再依次执行预测时间稍微长一些的线程,因此是会出现饥饿现象

1.增加调度算法

       实验的第一步是先往TCB中增加一个predict_time字段,用于记录预测执行时间,TCB被定义在以下文件中

openharmony\kernel\liteos_a\kernel\base\include\los_task_pri.h

5aaa3ec22bf54a43a8f559f9975cd290.png

       之后我们要修改的就是TCB插入到就绪队列的方式,原先在内核中提供了两种方式,一种是时间片轮转(RR),另一种是先来先到(FIFO),该部分代码位于

openharmony/kernel/liteos_a/kernel/base/core/los_process.c

cc1566d3ded240e7906ed2d2594705ba.png

       这段代码的解释如下:

  1. 如果是时间片轮转策略并且时间片没用完(被抢占或阻塞导致)就放在对应优先级队列的头部,如果时间片用完了就放在最后;
  2. 如果是FIFO策略并且是运行态,即刚从CPU上下来,那应该让它回到队列的头部继续运行,否则放到队尾

        这段代码会在两个情况执行,一个情况是新的TCB被创建的时候会将其放入就绪队列;另一个情况是时钟中断到达,就会先把运行的TCB放回就绪队列,之后再重新选出最高优先级的进行调度

        因此,对于我们要实现的SJF代码,效果是在创建的时候根据它的预测时间将其放到合适的位置;如果是运行完的TCB也会再次根据它的预测时间来重新插入回就绪队列。如果不被发生抢占的情况,当前预测时间最短的会回到头部,因此会重新又一次上CPU,直到其被运行完。

21b543648a6d4dfa839f552634decde9.png

        之后去内核中补充进这个调度策略的宏定义,对于的宏定义位置如下图所示

3285bc0b6e714ecd843eed7cb0511415.png

        接下去实现这个调度策略的具体算法,先在los_task_pri.h文件中添加对应的宏

c0497e8df12449918095c8234459f2f9.png

        在文件openharmony/kernel/liteos_a/kernel/base/include/los_priqueue_pri.h中添加该函数的声明

25b32af12e6845a89e38af4c08f2746a.png

        之后再去文件los_priqueue.c中实现具体的算法,该函数总共是四个参数,第四个参数是UINT32 priority。该算法的思路就是比较优先队列中当前优先级下各个TCB中的预测时间和待插入TCB的预测时间

ef7d2a81efe540a9acb228342d5606db.png

//openharmony/kernel/liteos_a/kernel/base/sched/sched_sq/los_priqueue.c

VOID OsPriQueueEnqueueSJF(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem, UINT32 priority)
{
    LOS_ASSERT(priqueueItem->pstNext == NULL);
    LosTaskCB *newtask=LOS_DL_LIST_ENTRY(priqueueItem, LosTaskCB, pendList);

    if (LOS_ListEmpty(&priQueueList[priority])) {
        *bitMap |= PRIQUEUE_PRIOR0_BIT >> priority;
        LOS_ListTailInsert(&priQueueList[priority], priqueueItem);
        return;
    }
 
    LOS_DL_LIST *item;
    LosTaskCB *taskitem;
    LOS_DL_LIST_FOR_EACH(item,&priQueueList[priority])
    {
        taskitem=LOS_DL_LIST_ENTRY(item, LosTaskCB, pendList);
        if(newtask->predict_time<taskitem->predict_time)
        {
            LOS_ListAdd(item->pstPrev,priqueueItem);
            break;
        }
    }

    if(!(newtask->predict_time<taskitem->predict_time))
    {

        LOS_ListTailInsert(&priQueueList[priority], priqueueItem);
    }

}
  1. 如果该优先级下这个循环双向链表是空的说明这个优先级下暂时还没有TCB,直接插入即可
  2. 如果找到一个TCB(taskitem)的预测时间大于当前待插入TCB(newtask)的预测时间,就需要将待插入TCB放在这个TCB的前面,使用内核中的LOS_ListAdd即可实现,该函数定义如下

f8764bc93ef8425aa2e31cb28822157b.png

       通过分析这个函数可以知道会插入到list节点的下一个位置,因此我们在传入参数的时候需要取item的前一个结点

        3.如果当前循环链表中的预测时间都小于待插入的TCB,则将这个TCB放在最后即可

经过上述修改内核便实现了支持SJF调度策略

2.设置测试线程

       现在还有两个问题没有解决:

  1. 怎么设置一个任务的调度策略为SJF
  2. 怎么设置一个任务的预测时间

这里的话有三种思路,但是实现起来都比较麻烦:

  1. 修改pthread_create函数,但是libc库是被静态编译成静态文件,然后在clang编译时和应用程序进行链接,想要重新编译这个库极其麻烦,具体可以参考另一位同学的博客
  2. 内核中的POSIX接口提供了另一个pthread库,但是我们很难去直接使用这部分代码与应用程序编译链接,使用系统调用执行这部分代码经过证明会出现不少问题
  3. 绕开pthread库,通过系统调用直接调用LOS_TaskCreateOnly和后续的LOS_SetTaskScheduler,这个方法相对比较方便,但是增加系统调用本身也会更改不少文件

        本实验的目的是了解就绪队列的插入机制,从而实现SJF调度,而无论任何形式的pthread_create都是直接或间接调用了内核的LOS_TaskCreateOnly和LOS_SetTaskScheduler,因此如果为了实验目的我们可以直接采用写死在代码里即可,而不陷入到修改pthread库的泥淖中

        具体的实验方案就是经过实验证明可以发现我们如果在开发板通电后不进行其他操作的情况下直接在用户态下创建三个线程,则这三个线程的TaskId是固定不变的,每次分配到的id都是一样的,因此我们直接可以在内核判断这三个线程之后进行相关的赋值操作

        我们先编写一个用于测试的应用程序prj2(直接放在~目录里即可),这里我们没有使用sleep函数,是因为sleep函数会将线程放到阻塞队列,它重新回到就绪队列的时候不会调用我们之前的OsPriQueueEnqueueSJF,因此我们直接使用while循环空转的方式来进行线程的等待

//prj2.c
#include <pthread.h>
#include <stdio.h>
#include <sched.h>

void s()
{

    int T=1e7;

    while(T--);

}

void * func1()
{

    int t=20;
    while(t--){
        printf("pthread1:%s\n",__func__);
        s();

    };
    printf("pthread1  finished\n\n");

}

void * func2()
{
    int t=5;
    while(t--){
        printf("pthread2:%s\n",__func__);
        s();

    }
    printf("pthread2  finished\n\n");

}

void * func3()
{

    int t=12;
    while(t--){
        printf("pthread3:%s\n",__func__);
        s();
    }
    printf("pthread3  finished\n\n");
}

int main()

{

    pthread_t ppid1,ppid2,ppid3;

    pthread_create(&ppid1,NULL,func1,NULL);
    pthread_create(&ppid2,NULL,func2,NULL);
    pthread_create(&ppid3,NULL,func3,NULL);


    while(1);//避免主线程退出

    return 0;

}

        直接运行下这个程序,获取这三个线程的TaskId,使用以下命令完成内核的编译和根文件目录的制作

//编译内核

cd  /home/book/openharmony/kernel/liteos_a

make clean

make -j 16

make rootfs
//交叉编译prj2

cd ~

clang -target arm-liteos --sysroot=/home/book/openharmony/prebuilts/lite/sysroot/ -o prj2 prj2.c

cp  prj2  /home/book/openharmony/kernel/liteos_a/out/imx6ull/rootfs/bin

cd  /home/book/openharmony/kernel/liteos_a/out/imx6ull/

mkfs.jffs2  -s 0x10000 -e 0x10000 -d rootfs -o rootfs.jffs2

        之后将生成的liteos.bin和rootfs.jffs2下载到烧写工具的files目录中,运行开发板后输入./bin/prj2运行应用程序,可以看到这三个线程现在是时间片轮状的方式运行(线程2输出5次,线程3输出12次,线程1输出20次)

b06072da1fa14987a247f1207fd66a55.png

        使用命令task查看这三个线程的id(可以直接在程序prj2运行的时候使用输入task命令),可以看到三个线程的id是12、13、14

e94671ddc9e74976b83173ac756ecf75.png

        之后修改下代码在入队的时候进行特判,再重新编译内核与应用程序,下载到开发板上运行

2ff5911b9a3b4b79918e091d38925633.png

 

四、实验结果

       运行对应的应用程序,可以发现可能会出现两种情形

       第一种情形是先运行一下了1号线程,然后再依次运行2号线程、3号线程和1号线程

9951ba02fd6148e3803398d68f0c8a72.png

       第二种情形是依次运行2号线程、3号线程和1号线程,这个情况概率比较小,第一次做实验运气好出现了,但是后来复现的时候尝试了10几次才出现

ac7df32a2b8246ba9c3c580f86b64386.png

五、实验分析

       上述两种情形都是正确的,第二种情形符合预期,第一种情形是因为在1号线程进入就绪队列后主线程的时间片恰好到了,这个时候2号、3号线程还没创建,因此1号线程直接上CPU运行了,后续2号线程创建出来就会抢占掉1号线程,因此是没有问题的,成功实现短作业优先调度策略。

六、实验总结

       本次实验实现SJF算法本身不算困难,只要对内核的数据结构有一定了解即可实现,但是在纠结修改pthread_create的时候花费了大量时间和精力,不过这个探索过程学到了不少libc库、posix接口和交叉编译的知识,也算是非常有收获。

       另外通过设计实验的方式确实可以对阅读代码起到非常大的帮助,通过动手和阅读结合的方式能更好的理解代码。

七、参考文献

[1] 鸿蒙liteos-a添加一个短作业优先调度算法-CSDN博客

[2] Linux下用pthread实现“生产者---消费者”的同步与互斥-CSDN博客

[3] 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的? | 百篇博客分析HarmonyOS源码 | v7.05_51CTO博客_鸿蒙系统内核分析

 

 

 

 

 

 

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值