自学内容网 自学内容网

μC/OS-Ⅱ源码学习(2)---多任务系统的实现(下)

快速回顾

        在之前的文章中讲解了μC/OSⅡ的多任务模型以及初始化的源码:

μC/OS-Ⅱ中的多任务

μC/OS-Ⅱ源码学习(1)---多任务系统的实现

        本文继续探究任务生命周期的其它函数源码。

任务的挂起

        任务挂起是一个动作,通过主动使用函数OSTaskSuspend(prio)可以使对应优先级的任务处于挂起状态,暂时无法被调度器调度:

//os_task.c
INT8U  OSTaskSuspend (INT8U prio)
{
    BOOLEAN    self;
    OS_TCB    *ptcb;
    INT8U      y;
#if OS_CRITICAL_METHOD == 3u      /* 初始化临界区变量 */
    OS_CPU_SR  cpu_sr = 0u;
#endif



#if OS_ARG_CHK_EN > 0u
    if (prio == OS_TASK_IDLE_PRIO) {      /* 不允许挂起空闲任务 */
        return (OS_ERR_TASK_SUSPEND_IDLE);
    }
    if (prio >= OS_LOWEST_PRIO) {      /* 要挂起的优先级不允许超出设定的最低优先级 */
        if (prio != OS_PRIO_SELF) {
            return (OS_ERR_PRIO_INVALID);
        }
    }
#endif
    OS_ENTER_CRITICAL();           //进入临界区,关闭其它中断,只保留NMI和Hardfault中断
    if (prio == OS_PRIO_SELF) {         /* 检查是否要挂起自己 */
        prio = OSTCBCur->OSTCBPrio;
        self = OS_TRUE;
    } else if (prio == OSTCBCur->OSTCBPrio) {      /* 是否等于当前运行任务的优先级,和上面的判断是一样的 */
        self = OS_TRUE;
    } else {
        self = OS_FALSE;      /* self为TRUE表示要挂起自己,否则为挂起其它任务 */
    }
    ptcb = OSTCBPrioTbl[prio];
    if (ptcb == (OS_TCB *)0) {        /* 任务不能是空,必须存在 */
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_SUSPEND_PRIO);
    }
    if (ptcb == OS_TCB_RESERVED) {       /* 任务控制块不能是OS_TCB_RESERVED状态 */
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_NOT_EXIST);
    }
    y            = ptcb->OSTCBY;        //获取要挂起任务的优先级组下标
    OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX;       /* 将任务的组内优先级就绪状态清空 */
    if (OSRdyTbl[y] == 0u) {      //如果挂起该任务后,整个组内没有就绪任务,则将该组的就绪状态清空
        OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
    }
    ptcb->OSTCBStat |= OS_STAT_SUSPEND;      /* 将任务状态切换为挂起状态 */
    OS_EXIT_CRITICAL();
    if (self == OS_TRUE) {       /* 如果要挂起的任务就是自身,则进行上下文切换(停止运行当前任务) */
        OS_Sched();       /* 切换任务 */
    }
    return (OS_ERR_NONE);
}

       可以看到,挂起任务并不会将任务控制块丢弃或清空,只是将任务状态转换为SUSPEND而已,在这之后,任务将不会永远不会被加入到就绪队列中(除非被恢复)。

        这里面有一些宏定义要额外提一下:

OS_PRIO_SELF:值为0xFF,表示OSTCBCur(当前运行任务的TCB)对应的任务优先级。是一个特殊优先级。

OS_TCB_RESERVED:值为1,是一种临时占用的状态。一种情况是任务刚创建但还没有进行TCB填装时,会先用这个值临时占用,后面再正式填装。另一种使用场景是创建互斥信号量时(后面再说)。

任务的恢复

        当任务被挂起后,只能通过任务恢复将任务重新处于就绪状态,具体函数为OSTaskResume(prio)

//os_task.c
INT8U  OSTaskResume (INT8U prio)
{
    OS_TCB    *ptcb;
#if OS_CRITICAL_METHOD == 3u         /* 初始化临界区变量 */
    OS_CPU_SR  cpu_sr = 0u;
#endif



#if OS_ARG_CHK_EN > 0u
    if (prio >= OS_LOWEST_PRIO) {           /* 不能超出最低优先级 */
        return (OS_ERR_PRIO_INVALID);
    }
#endif
    OS_ENTER_CRITICAL();
    ptcb = OSTCBPrioTbl[prio];
    if (ptcb == (OS_TCB *)0) {           /* 任务TCB不存在 */
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_RESUME_PRIO);
    }
    if (ptcb == OS_TCB_RESERVED) {          /* 任务TCB为OS_TCB_RESERVED状态 */
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_NOT_EXIST);
    }
    if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) != OS_STAT_RDY) {    /* 任务当前不是挂起状态 */
        ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_SUSPEND;    /* 取消挂起状态 */
        if (ptcb->OSTCBStat == OS_STAT_RDY) {                 /* 检查是否为就绪状态 */
            if (ptcb->OSTCBDly == 0u) {      //确保不在延时等待
                OSRdyGrp               |= ptcb->OSTCBBitY;    /* 任务的优先级组对应位置1 */
                OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;  //组内优先级对应位置1
                OS_EXIT_CRITICAL();
                if (OSRunning == OS_TRUE) {
                    OS_Sched();                  /* 进行上下文切换 */
                }
            } else {
                OS_EXIT_CRITICAL();
            }
        } else {               /* 不在就绪态,则可能在Pending某些事件,此时直接退出 */
            OS_EXIT_CRITICAL();
        }
        return (OS_ERR_NONE);
    }
    OS_EXIT_CRITICAL();
    return (OS_ERR_TASK_NOT_SUSPENDED);   //不在挂起状态,返回对应错误
}

       可以看到,恢复任务就是把任务TCB状态的挂起标志清除,然后再根据任务是否就绪将优先级就绪标志置位,最后进行上下文切换。比较后发现,任务恢复和任务挂起做的核心事情正好相反。

        这里还有个有意思的点,要进行OSRunning的判定才进行上下文切换,在创建任务函数里也有同样的判定,而在挂起的函数里就没有这一条,是为什么呢?

任务删除

        删除任务实际是让它休眠,有两个用于实现任务删除的函数,分别是OSTaskDel(prio)OSTaskDelReq(prio)。其中前者是强制删除的,并立即执行;后者只是发送一个删除请求,收到请求的任务可以进行资源清理和回收,再执行OSTaskDel(OS_PRIO_SELF)自我删除,可以理解为”留时间准备后事的缓刑“。

        先看前者源码:

//os_task.c
INT8U  OSTaskDel (INT8U prio)
{
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
    OS_FLAG_NODE *pnode;
#endif
    OS_TCB       *ptcb;
#if OS_CRITICAL_METHOD == 3u         /* 初始化临界区变量 */
    OS_CPU_SR     cpu_sr = 0u;
#endif

    if (OSIntNesting > 0u) {           /* 不允许在中断中删除任务 */
        return (OS_ERR_TASK_DEL_ISR);
    }
    if (prio == OS_TASK_IDLE_PRIO) {        /* 不允许删除空闲任务 */
        return (OS_ERR_TASK_DEL_IDLE);
    }
#if OS_ARG_CHK_EN > 0u
    if (prio >= OS_LOWEST_PRIO) {       /* 不允许超出最低任务优先级 */
        if (prio != OS_PRIO_SELF) {
            return (OS_ERR_PRIO_INVALID);
        }
    }
#endif

    OS_ENTER_CRITICAL();
    if (prio == OS_PRIO_SELF) {         /* 检查是否要删除自身任务 */
        prio = OSTCBCur->OSTCBPrio;
    }
    ptcb = OSTCBPrioTbl[prio];
    if (ptcb == (OS_TCB *)0) {         /* 要删除的任务TCB不能为空 */
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_NOT_EXIST);
    }
    if (ptcb == OS_TCB_RESERVED) {      /* 要删除的任务不能是OS_TCB_RESERVED状态 */
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_DEL);
    }

    OSRdyTbl[ptcb->OSTCBY] &= (OS_PRIO)~ptcb->OSTCBBitX;   //清除就绪组内优先级对应位
    if (OSRdyTbl[ptcb->OSTCBY] == 0u) {      /* 如果组内没有就绪任务,则清除优先级组对应位 */
        OSRdyGrp           &= (OS_PRIO)~ptcb->OSTCBBitY;
    }

#if (OS_EVENT_EN)
    if (ptcb->OSTCBEventPtr != (OS_EVENT *)0) {
        OS_EventTaskRemove(ptcb, ptcb->OSTCBEventPtr);   /* 从事件等待变量中清除该任务优先级对应位 */
    }
#if (OS_EVENT_MULTI_EN > 0u)
    if (ptcb->OSTCBEventMultiPtr != (OS_EVENT **)0) {    /* 从多重事件等待列表中清除该任务优先级对应位 */
        OS_EventTaskRemoveMulti(ptcb, ptcb->OSTCBEventMultiPtr);
    }
#endif
#endif

#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
    pnode = ptcb->OSTCBFlagNode;
    if (pnode != (OS_FLAG_NODE *)0) {
        OS_FlagUnlink(pnode);           /* 如果该任务有等待的Flag,则将该Flag从Flags链表中移除*/
    }
#endif

    ptcb->OSTCBDly      = 0u;              /* 阻止OSTimeTick()更新该任务 */
    ptcb->OSTCBStat     = OS_STAT_RDY;     /* 设置为就绪态,阻值意外恢复 */
    ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
    if (OSLockNesting < 255u) {         /* 临时调度器锁+1,防止上下文切换 */
        OSLockNesting++;
    }
    OS_EXIT_CRITICAL();         /* 退出临界区,使能各种中断 */
    OS_Dummy();          /* Dummy函数是一个空执行,是为了确保能成功再次进入下面的临界区,这是CPU特性决定 */
    OS_ENTER_CRITICAL();          
    if (OSLockNesting > 0u) {        /* 调度器锁-1,恢复之前状态 */
        OSLockNesting--;
    }
    OSTaskDelHook(ptcb);          /* 用户定义回调函数 */
    OSTaskCtr--;              /* 系统的管理任务数-1 */
    OSTCBPrioTbl[prio] = (OS_TCB *)0;       /* 清空TCB优先级数组对应位的指针 */
    if (ptcb->OSTCBPrev == (OS_TCB *)0) {         /* 从任务TCB链表中移除该任务的TCB */
        ptcb->OSTCBNext->OSTCBPrev = (OS_TCB *)0;
        OSTCBList                  = ptcb->OSTCBNext;
    } else {
        ptcb->OSTCBPrev->OSTCBNext = ptcb->OSTCBNext;
        ptcb->OSTCBNext->OSTCBPrev = ptcb->OSTCBPrev;
    }
    ptcb->OSTCBNext     = OSTCBFreeList;       /* 将该TCB归还给空白TCB链表,并放在首位                */
    OSTCBFreeList       = ptcb;
#if OS_TASK_NAME_EN > 0u
    ptcb->OSTCBTaskName = (INT8U *)(void *)"?";
#endif
    OS_EXIT_CRITICAL();
    if (OSRunning == OS_TRUE) {
        OS_Sched();          /* 执行上下文切换 */
    }
    return (OS_ERR_NONE);
}

        可以看到,该函数会将清除就绪表中对应的任务优先级,然后解除各种事件和标志的链接,最后再将任务TCB从OSTCBList链表中归还给OSTCBFreeList链表。

        接着看另一个函数OSTaskDelReq(prio)

//os_task.c
INT8U  OSTaskDelReq (INT8U prio)
{
    INT8U      stat;
    OS_TCB    *ptcb;
#if OS_CRITICAL_METHOD == 3u           /* 初始化临界区变量 */
    OS_CPU_SR  cpu_sr = 0u;
#endif

    if (prio == OS_TASK_IDLE_PRIO) {         /* 不允许删除空闲任务 */
        return (OS_ERR_TASK_DEL_IDLE);
    }
#if OS_ARG_CHK_EN > 0u
    if (prio >= OS_LOWEST_PRIO) {          /* 不允许任务优先级超过最低优先级 */
        if (prio != OS_PRIO_SELF) {
            return (OS_ERR_PRIO_INVALID);
        }
    }
#endif
    if (prio == OS_PRIO_SELF) {         /* 是否在检查自身的删除请求 */
        OS_ENTER_CRITICAL();
        stat = OSTCBCur->OSTCBDelReq;        /* 获取自身的删除请求 */
        OS_EXIT_CRITICAL();
        return (stat);
    }
    OS_ENTER_CRITICAL();
    ptcb = OSTCBPrioTbl[prio];
    if (ptcb == (OS_TCB *)0) {        /* 被发送删除请求的任务必须存在 */
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_NOT_EXIST);
    }
    if (ptcb == OS_TCB_RESERVED) {        /* 任务TCB不能是OS_TCB_RESERVED状态 */
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_DEL);
    }
    ptcb->OSTCBDelReq = OS_ERR_TASK_DEL_REQ;        /* 目标任务的删除请求置位 */
    OS_EXIT_CRITICAL();
    return (OS_ERR_NONE);
}

        该函数本质上就是个传话和接话的,并没有执行任何删除操作。即发送方通过该函数发送任务删除请求,被请求删除的函数也通过执行该函数,并传入自身优先级OS_PRIO_SELF,接收到该请求。在官方的说明里,推荐被请求删除的任务执行以下代码,来接收删除请求并执行清理操作:

//os_task.c
void Task(void *p_arg)
{
    ...
    ...
    while (1) {
        OSTimeDly(1);
        if (OSTaskDelReq(OS_PRIO_SELF) == OS_ERR_TASK_DEL_REQ) {
            /* Release any owned resources; */
            /* De-allocate any dynamic memory; */
            OSTaskDel(OS_PRIO_SELF);
        }
    }
}

其它任务源码

任务名称

        为任务取个名,实际上只是个花哨的功能,用于调试(有更高级的使用场景欢迎分享),相关函数为OSTaskNameSet(prio, pname, perr)OSTaskNameGet(prio, pname, perr)

任务寄存器

        任务寄存器就像一个存储数据的容器,在任务周期产生的数据都可以填装进去,该容器是个每单元32位的数组(模拟32位机真实寄存器)并通过用户定义的id来获取不同单元。由于用户创建的任意任务都可以访问其它任务的任务寄存器,因此可以使用任务寄存器进行通信,包含两个主要函数:

①获取任务寄存器函数OSTaskRegGet(prio, id, perr)

②设置任务寄存器函数OSTaskRegSet(prio, id, value, perr)

任务返回

        任务返回函数是一个异常的处理函数,对任何一个μC/OSⅡ任务来说,其内部都应是一个无限循环,或以任务删除而告终。如果在任务运行过程中,不小心使用了return语句返回,将会触发OS_TaskReturn()函数:

//os_tsak.c
void  OS_TaskReturn (void)
{
    OSTaskReturnHook(OSTCBCur);          /* 用户定义的回调函数 */

#if OS_TASK_DEL_EN > 0u
    (void)OSTaskDel(OS_PRIO_SELF);          /* 如果有定义任务删除功能,则执行任务操作操作 */
#else
    for (;;) {
        OSTimeDly(OS_TICKS_PER_SEC);       //如果没有删除功能,则无限循环系统延时函数,相当于一个空闲任务,等待系统调度处理
    }
#endif
}

任务访问

        拷贝对应任务的任务寄存器,对应函数为OSTaskQuery(prio, p_task_data)

任务堆栈

        包括任务堆栈清除OS_TaskStkClr(pbos, size, opt)和任务堆栈统计OSTaskStkChk(prio, p_stk_data)两个函数。前者很简单,具体来说堆栈统计函数:

//os_task.c
INT8U  OSTaskStkChk (INT8U         prio,
                     OS_STK_DATA  *p_stk_data)
{
    OS_TCB    *ptcb;
    OS_STK    *pchk;
    INT32U     nfree;
    INT32U     size;
#if OS_CRITICAL_METHOD == 3u        /* 初始化临界区变量 */
    OS_CPU_SR  cpu_sr = 0u;
#endif



#if OS_ARG_CHK_EN > 0u
    if (prio > OS_LOWEST_PRIO) {          /* 不要超出最低优先级 */
        if (prio != OS_PRIO_SELF) {
            return (OS_ERR_PRIO_INVALID);
        }
    }
    if (p_stk_data == (OS_STK_DATA *)0) {         /* 传入指针不能为空 */
        return (OS_ERR_PDATA_NULL);
    }
#endif
    p_stk_data->OSFree = 0u;        /* 清空指针数据,防止返回错误时得到意外的指针数据 */
    p_stk_data->OSUsed = 0u;
    OS_ENTER_CRITICAL();
    if (prio == OS_PRIO_SELF) {         /* 检查要统计的是否是当前运行任务自身 */
        prio = OSTCBCur->OSTCBPrio;
    }
    ptcb = OSTCBPrioTbl[prio];
    if (ptcb == (OS_TCB *)0) {         /* 任务TCB不能为空 */
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_NOT_EXIST);
    }
    if (ptcb == OS_TCB_RESERVED) {       //任务TCB不能是OS_TCB_RESERVED状态
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_NOT_EXIST);
    }
    if ((ptcb->OSTCBOpt & OS_TASK_OPT_STK_CHK) == 0u) {      /* 检查堆栈选项是否含有OS_TASK_OPT_STK_CHK,这是创建任务时传入的选项 */
        OS_EXIT_CRITICAL();
        return (OS_ERR_TASK_OPT);
    }
    nfree = 0u;
    size  = ptcb->OSTCBStkSize;      //堆栈大小
    pchk  = ptcb->OSTCBStkBottom;    //堆栈底部
    OS_EXIT_CRITICAL();
#if OS_STK_GROWTH == 1u        //根据堆栈增长方式决定之后的测算方法
    while (*pchk++ == (OS_STK)0) {      /* 检查内容为0空间,遇到不为0的地址就退出 */
        nfree++;
    }
#else
    while (*pchk-- == (OS_STK)0) {
        nfree++;
    }
#endif
    p_stk_data->OSFree = nfree * sizeof(OS_STK);          /* 计算空字节数 */
    p_stk_data->OSUsed = (size - nfree) * sizeof(OS_STK); /* 计算使用的字节数 */
    return (OS_ERR_NONE);
}


原文地址:https://blog.csdn.net/sherlock_cp/article/details/144330117

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!