(6)从1开始写一个操作系统

第六章

任务优先级及任务状态

到现在为止我们其实已经实现了一些多任务的功能,而且我们也能从中总结出一些状态,比如正在运行,睡眠,挂起,就绪。这些状态都有他们特殊的逻辑,在之后我们还会设计支持时间片的方式,这时候我们只有就绪表就有些不够用了,我们需要在任务控制块中添加任务状态属性。

在前面讲到的任务切换逻辑时间上是进行了任务ID从大到小的先后顺序进行调度,这就有点像优先级,只不过优先级与任务ID是一个。同样在后面时间片的方式中要对同优先级的任务进行逻辑处理,所以我们需要在任务控制块中添加优先级的属性。

typedef struct os_tcb {
    u8              OSTCBStkPtr;            /* 指向堆栈栈顶的指针                                      */
    u16             OSTCBDly;               /* 任务等待的节拍数                                        */
    u16             OSTCBStatus;            /* 任务状态                                                */
    u8              OSTCBPrio;              /* 任务优先级                                              */
#ifdef STACK_DETECT_MODE
    u8              OSTCBStkSize;           /* 堆栈的大小                                              */
#endif
} OS_TCB;

OSTCBStatus的类型目前有如下这些

/*
*********************************************************************************************************
*                              TASK STATUS (Bit definition for OSTCBStatus)
*********************************************************************************************************
*/
#define  OS_STAT_DEFAULT         0x0000u    /* 初始化的默认值                                          */
#define  OS_STAT_RUNNING         0x0001u    /* 运行中                                                  */
#define  OS_STAT_RDY             0x0002u    /* Ready to run                                            */
#define  OS_STAT_SLEEP           0x0004u    /* Task is sleeping                                        */
#define  OS_STAT_SUSPEND         0x0008u    /* Task is suspended                                       */

在状态属性中有就绪这个标志OS_STAT_RDY我们在需要判断就绪状态时就可以使用这个属性,所以我们把现有代码中就绪表使用状态属性代替,这里改动位置有些多,就不再一一贴代码了,需要的可以下载教程源码查看。

对于优先级我们需要在创建任务时指定,所以修改创建任务函数

u8 os_task_create(void (*task)(void), u8 taskID, u8 task_prio, u8 *pstack, u8 stack_size)
{
    if (taskID >= TASK_SIZE)
        return (OS_TASK_ID_INVALID);
    if (task_prio == OS_IDLE_TASK_PRIO)//不允许与idle相同优先级
        return (OS_PRIO_INVALID);
    if (os_tcb[taskID].OSTCBStkPtr != 0)
        return (OS_TASK_ID_EXIST);
    OS_ENTER_CRITICAL();
#ifdef STACK_DETECT_MODE
{
    u8 i = 0;
    for (; i<stack_size; i++)
        pstack[i] = STACK_MAGIC;
}
#else
    stack_size = stack_size; //消除编译警告
#endif
    *pstack++ = (u16)task; //将函数的地址高位压入堆栈,
    *pstack = (u16)task>>8; //将函数的地址低位压入堆栈,
{
    u8 i = 0;
    for (; i < 13; i++)        //初次被任务调度函数pop时需要清空堆栈内容,避免原有内容导致错误
        *(++pstack) = 0;
}
    os_tcb[taskID].OSTCBStkPtr = (u8)pstack; //将人工堆栈的栈顶,保存到堆栈的数组中
    //os_rdy_tbl |= 0x01<<taskID; //任务就绪表已经准备好
    os_tcb[taskID].OSTCBStatus = OS_STAT_RDY;
    os_tcb[taskID]. OSTCBPrio = task_prio;
    OS_EXIT_CRITICAL();
    return (OS_NO_ERR);
}

我们看到在开始的时候我们判断了优先级是否为idle(0),这样是不被允许的,任何任务不能与idle均分时间片,这样会影响idle中的统计和用户任务。

在最后我们也将OSTCBPrio属性赋值了。

既然有了优先级我们在开始调度任务的时候就可以直接运行优先级最高的任务,而不是傻傻的运行idle,等着idle进行任务调度。修改启动运行函数如下:

void os_start_task(void)
{
    char i = 0;
    u8 highest_prio_id = 0;
    u8 highest_prio = 0;
    tick_timer_start();
    for (; i<TASK_SIZE; i++)
    {
        if ((os_tcb[i].OSTCBStatus == OS_STAT_RDY) && (os_tcb[i].OSTCBPrio > os_tcb[highest_prio_id].OSTCBPrio))
            highest_prio_id = i;
    }
    os_task_running_ID = highest_prio_ id;
    os_tcb[highest_prio_id].OSTCBStatus = OS_STAT_RUNNING;
    SP = os_tcb[highest_prio_id].OSTCBStkPtr - 13;
}

其中是由for循环遍历所有任务控制块,找到最大优先级的任务,并运行它。

这里根据逻辑我们定义的优先级范围是1-255,数值越大优先级越高。

有了以上代码我们会发现如果我们在任务运行起来之后再调用创建任务时并不能是进行任务调度,这样就导致我们在start之后create更高优先级的任务时不能马上进行调度,这就不是一个好的抢占式内核了,所以我们需要做一个start标志,并在create时进行判断是否需要进行任务调度。

u8 os_task_create(void (*task)(void), u8 taskID, u8 task_prio, u8 *pstack, u8 stack_size)
{
    ……
    if (os_core_start) {//如果任务已经启动则进行一次任务调度
        os_tcb[os_task_running_ID].OSTCBStatus = OS_STAT_RDY; //先将自己置位就绪态
        OS_EXIT_CRITICAL();
        OS_TASK_SW();
    }
    return (OS_NO_ERR);
}

现在我们需要考虑让任务能够修改优先级,我们提供修改优先级函数。

// 此功能允许您动态更改任务的优先级。 请注意,新的优先级必须可用。
// 返回:OS_NO_ERR:是成功
// OS_TASK_ID_INVALID:如果试图改变idle任务的优先级或者任务不存在或者任务ID超过最大任务数量
// OS_PRIO_INVALID: 如果试图更改为idle任务的优先级(0)是不允许的
// OS_PRIO_ERR: 其他错误
u8 os_task_change_prio (u8 taskID, u8 new_prio)
{
    if (taskID == OS_IDLE_TASKID)
        return (OS_TASK_ID_INVALID);
    if (new_prio == OS_IDLE_TASK_PRIO)
        return (OS_PRIO_INVALID); 
    if (taskID >= TASK_SIZE && taskID != OS_TASKID_SELF)
        return (OS_TASK_ID_INVALID);
   
    OS_ENTER_CRITICAL();
    if ((taskID < TASK_SIZE) && (os_tcb[taskID].OSTCBStkPtr == 0)) {
            OS_EXIT_CRITICAL();
            return (OS_TASK_ID_INVALID);
    }

    if ((taskID == OS_TASKID_SELF) || (taskID == os_task_running_ID)){
        os_tcb[os_task_running_ID].OSTCBPrio = new_prio;
        OS_EXIT_CRITICAL();
    } else {
        os_tcb[taskID].OSTCBPrio = new_prio;
        OS_EXIT_CRITICAL();
        OS_TASK_SW();
    }

    return (OS_NO_ERR);                                        //return(0)
}

下面我们写一段测试代码测试一下

首先我们准备3个任务

任务1 优先级为20,任务2 优先级为10,任务3初始优先级为15

void app_task_3(void)
{
    while (1) {
        debug_print("app_task_3\r\n");
        os_tick_sleep(100);
    }
}
void app_task_1(void)
{
    u8 cnt = 0;
while (1) {
       if (cnt++ == 3)
            os_task_change_prio(3, 30);
        debug_print("app_task_1\r\n");
        os_tick_sleep(100);
    }
}
void app_task_2(void)
{
    os_task_create(app_task_3, 3, 15, app3_stack, 25);
    while (1) {
        debug_print("app_task_2\r\n");
        os_tick_sleep(100);
    }
}

程序开始时先create任务1和任务2

Start后会先运行任务1,因为任务1优先级最高,然后任务1会让出cpu进入sleep,任务2执行,任务2中会create任务3,因为任务3比任务2优先级高会先运行任务3,然后任务3进入sleep,继续运行任务2,任务会按照132,132,132的顺序运行。当任务1运行到第4次时修改任务3优先级为30,此时任务3最高,会触发调度,但是任务3在sleep所以还是继续运行任务1.

输出如下:

[2019-09-04 23:14:52.037]# RECV ASCII>
STC15F2K60S2 UART1 Test Prgramme!
app_task_1
app_task_3
app_task_2
idle_task_0 in
[2019-09-04 23:14:53.042]# RECV ASCII>
app_task_1
app_task_3
app_task_2
[2019-09-04 23:14:54.043]# RECV ASCII>
app_task_1
app_task_3
app_task_2
[2019-09-04 23:14:55.045]# RECV ASCII>
app_task_3
app_task_1
app_task_2

之后一次非常有趣,顺序从132变为312,只是由于时钟中断计时的sleep要比idle中的任务切换要快,这就导致有一次调度时发生123任务同时进入就绪态,这时就会优先运行优先级高的3,之后是1,最后是2.

这个问题是由于没有中断context中进行过调度,所以sleep时间到了的时候没有及时的进行调度产生的,这个问题我们会在下一章抢占式内核中进行完善。

 

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页