μC/OS-Ⅱ源码学习(2)---多任务系统的实现(下)
快速回顾
在之前的文章中讲解了μC/OSⅡ的多任务模型以及初始化的源码:
本文继续探究任务生命周期的其它函数源码。
任务的挂起
任务挂起是一个动作,通过主动使用函数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)!