(学习日记)2024.03.28:UCOSIII第二十五节:常见任务管理函数

写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。


标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。


点击此处进入学习日记的总目录

三十九、UCOSIII:常见任务管理函数

通过之前章节的学习,我们对任务创建以及任务调度的实现已然掌握了。
下面就补充一些ΜC/OS提供给我们对任务操作的一些常用函数。

1、任务挂起函数OS_TaskSuspend()

挂起指定任务。被挂起的任务绝不会得到CPU的使用权,不管该任务具有什么优先级。

任务可以通过调用vTaskSuspend()函数都可以将处于任何状态的任务挂起。
被挂起的任务得不到CPU的使用权,也不会参与调度, 它相对于调度器而言是不可见的,除非它从挂起态中解除。
任务挂起是我们经常使用的一个函数, 想要使用的就必须将宏定义OS_CFG_TASK_SUSPEND_EN启用,这样在编译的时候才会包含OS_TaskSuspend()这个函数, 源码如下:

#if OS_CFG_TASK_SUSPEND_EN > 0u//如果启用了函数 OSTaskSuspend()
void   OS_TaskSuspend (OS_TCB  *p_tcb,              //(1)//任务控制块指针
                    OS_ERR  *p_err)                 //(2)//返回错误类型
{
    CPU_SR_ALLOC();  //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
    //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
    // SR(临界段关中断只需保存SR),开中断时将该值还原。

    CPU_CRITICAL_ENTER();                         //关中断
    if (p_tcb == (OS_TCB *)0) {         //(3)//如果 p_tcb 为空
        p_tcb = OSTCBCurPtr;    //挂起自身
    }

    if (p_tcb == OSTCBCurPtr) {         //(4)//如果是挂起自身
        if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) {    //如果调度器被锁
            CPU_CRITICAL_EXIT();                            //开中断
            *p_err = OS_ERR_SCHED_LOCKED;          //错误类型为“调度器被锁”
            return;                                         //返回,停止执行
        }
    }

    *p_err = OS_ERR_NONE;                             //错误类型为“无错误”
    switch (p_tcb->TaskState) {      //(5)//根据 p_tcb 的任务状态分类处理
        case OS_TASK_STATE_RDY:       //(6)//如果是就绪状态
        OS_CRITICAL_ENTER_CPU_EXIT();                 //锁调度器,重开中断
        p_tcb->TaskState  =  OS_TASK_STATE_SUSPENDED; //任务状态改为“挂起状态”
        p_tcb->SuspendCtr = (OS_NESTING_CTR)1;           //挂起前套数为1
        OS_RdyListRemove(p_tcb);                  //将任务从就绪列表移除
        OS_CRITICAL_EXIT_NO_SCHED();              //开调度器,不进行调度
        break;                                           //跳出

        case OS_TASK_STATE_DLY:         //(7)//如果是延时状态将改为“延时中被挂起”
        p_tcb->TaskState  = OS_TASK_STATE_DLY_SUSPENDED;
        p_tcb->SuspendCtr = (OS_NESTING_CTR)1;           //挂起前套数为1
        CPU_CRITICAL_EXIT();                             //开中断
        break;                                           //跳出

        case OS_TASK_STATE_PEND: //(8)//如果是无期限等待状态将改为“无期限等待中被挂起”
        p_tcb->TaskState  = OS_TASK_STATE_PEND_SUSPENDED;
        p_tcb->SuspendCtr = (OS_NESTING_CTR)1;           //挂起前套数为1
        CPU_CRITICAL_EXIT();                             //开中断
        break;                                           //跳出

        case OS_TASK_STATE_PEND_TIMEOUT://(9)//如果是有期限等待将改为“有期限等待中被挂起”
        p_tcb->TaskState  = OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED;
        p_tcb->SuspendCtr = (OS_NESTING_CTR)1;           //挂起前套数为1
        CPU_CRITICAL_EXIT();                             //开中断
        break;                                           //跳出

        case OS_TASK_STATE_SUSPENDED:     //(10)              //如果状态中有挂起状态
        case OS_TASK_STATE_DLY_SUSPENDED:
        case OS_TASK_STATE_PEND_SUSPENDED:
        case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
        p_tcb->SuspendCtr++;                             //挂起嵌套数加1
        CPU_CRITICAL_EXIT();                             //开中断
        break;                                           //跳出

        default:                         //(11)//如果任务状态超出预期
        CPU_CRITICAL_EXIT();                             //开中断
        *p_err = OS_ERR_STATE_INVALID;          //错误类型为“状态非法”
        return;                                          //返回,停止执行
    }

    OSSched();                     //(12)//调度任务
}
#endif
  • (1):任务控制块指针,该指针指向要挂起的任务,也可以是任务自身,但是不能是空闲任务,空闲任务永远不允许挂起。
  • (2):用于存放返回错误代码,如果挂起任务失败,则返回对应的错误代码。
  • (3):如果传递进来的任务控制块指针是NULL或者是0, 则表明要挂起的任务是任务自身,将任务控制块的指针指向当前任务。
  • (4):如果的任务是当前任务,也就是挂起任务自身,那么需要判断一下调度器有没有被锁定, 因为挂起任务自身之后,就肯定需要切换任务,而如果调度器被锁定的话,就无法切换任务了,所以会返回错误类型“调度器被锁”,然后退出。
  • (5):根据要挂起的任务状态分类处理,这样处理逻辑简单,更加方便快捷。
  • (6):如果任务处于就绪状态,那么该任务能直接挂起,但是接下来我们要操作进行列表, 时间是不确定的,我们不能将中断关闭太久,这样子会影响系统对中断的响应,此时系统就会打开中断, 但是系统又不想其他任务来影响我们操作就绪列表,所以系统还会锁定调度器,不进行任务切换,这样子就不会有任务打扰我们的操作了, 然后将任务状态变为挂起态,挂起次数为1次,然后调用OS_RdyListRemove()函数将任务从就绪列表移除,再打开调度器, 然后跳出,最后才进行任务的调度。
  • (7):如果任务当前处于延时状态,那么也能被挂起, 任务状态将改为“延时中被挂起”状态,挂起次数为1次,然后打开中断,退出。
  • (8):如果任务当前处于无期限等待状态, 那么也能被挂起,任务状态将改为“无期限等待中被挂起”状态,挂起次数为1次,然后打开中断,退出。
  • (9):如果任务当前处于有期限等待状态, 那么也能被挂起,任务状态将改为“有期限等待中被挂起”状态,挂起次数为1次,然后打开中断,退出。
  • (10):如果要挂起的任务本身就处于挂起态, 那么再次挂起就要记录挂起的次数,将挂起的次数加一,然后打开中断,退出。
  • (11):对于其他的任务状态,返回状态非法错误,然后退出。
  • (12):进行一次任务调度。

注:任务可以调用OS_TaskSuspend()这个函数来挂起任务自身,但是在挂起自身的时候会进行一次任务上下文切换, 需要挂起自身就将任务控制块指针设置为NULL或0传递进来即可。无论任务是什么状态都可以被挂起, 只要调用了OS_TaskSuspend()这个函数就会挂起成功,不论是挂起其他任务还是挂起任务自身。

任务的挂起与恢复函数在很多时候都是很有用的,比如我们想暂停某个任务运行一段时间,但是我们又需要在其恢复的时候继续工作, 那么删除任务是不可能的。
因为删除了任务的话,任务的所有的信息都是不可能恢复的,删除是完完全全删除了, 里面的资源都被系统释放掉。

挂起任务就不会这样子,调用挂起任务函数,仅仅是将任务进入挂起态, 其内部的资源都会保留下来,同时也不会参与系统中任务的调度,当调用恢复函数的时候,整个任务立即从挂起态进入就绪态, 并且参与任务的调度,如果该任务的优先级是当前就绪态优先级最高的任务,那么立即会按照挂起前的任务状态继续执行该任务, 从而达到我们需要的效果。
注意,是继续执行,也就是说,挂起任务之前是什么状态,都会被系统保留下来,在恢复的瞬间, 继续执行。

这个任务函数的使用方法是很简单的,只需把任务句柄传递进来即可,OS_TaskSuspend()会根据任务句柄的信息将对应的任务挂起

/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
staticOS_TCB   AppTaskLed1TCB;/* LED任务句柄 */
static void KEY_Task(void* parameter)
{
    OS_ERR      err;
    while (1) {
        if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {
            /* KEY1 被按下 */
            printf("挂起LED任务!\n");
            OSTaskSuspend (AppTaskLed1TCB, & err );   /* 挂起LED1任务 */
        }
        OSTimeDly ( 20, OS_OPT_TIME_DLY, & err );   /* 延时20个tick */
    }
}

2、任务恢复函数OSTaskResume()

既然有任务的挂起,那么当然一样有恢复,不然任务怎么恢复呢。
任务恢复就是让挂起的任务重新进入就绪状态,恢复的任务会保留挂起前的状态信息, 在恢复的时候根据挂起时的状态继续运行。
如果被恢复任务在所有就绪态任务中,处于最高优先级列表的第一位,那么系统将进行任务上下文的切换。
下面一起看看任务恢复函数OSTaskResume()的源码:

#if OS_CFG_TASK_SUSPEND_EN > 0u//如果启用了函数 OSTaskResume()
void  OSTaskResume (OS_TCB  *p_tcb,     //(1)//任务控制块指针
        OS_ERR  *p_err)     //(2)//返回错误类型
{
    CPU_SR_ALLOC();  //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
    //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
    // SR(临界段关中断只需保存SR),开中断时将该值还原。

#ifdef OS_SAFETY_CRITICAL//如果启用了安全检测
    if (p_err == (OS_ERR *)0) {      //(3)//如果 p_err 为空
        OS_SAFETY_CRITICAL_EXCEPTION();   //执行安全检测异常函数
        return;                           //返回,停止执行
    }
#endif
//如果禁用了中断延迟发布和中断中非法调用检测
#if (OS_CFG_ISR_POST_DEFERRED_EN   == 0u) && \
    (OS_CFG_CALLED_FROM_ISR_CHK_EN >  0u)           //(4)
    if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果在中断中调用该函数
        *p_err = OS_ERR_TASK_RESUME_ISR;        //错误类型为“在中断中恢复任务”
        return;                                //返回,停止执行
    }
#endif


    CPU_CRITICAL_ENTER();                     //关中断
#if OS_CFG_ARG_CHK_EN > 0u//如果启用了参数检测
    if ((p_tcb == (OS_TCB *)0) ||             //如果被恢复任务为空或是自身
        (p_tcb == OSTCBCurPtr)) {   //(5)
        CPU_CRITICAL_EXIT();                  //开中断
        *p_err  = OS_ERR_TASK_RESUME_SELF;     //错误类型为“恢复自身”
        return;                               //返回,停止执行
    }
#endif
    CPU_CRITICAL_EXIT();                      //关中断

#if OS_CFG_ISR_POST_DEFERRED_EN > 0u	//(6)//如果启用了中断延迟发布
    if (OSIntNestingCtr > (OS_NESTING_CTR)0) {  //如果该函数在中断中被调用
        OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_TASK_RESUME,
                    (void      *)p_tcb,
                    (void      *)0,
                    (OS_MSG_SIZE)0,
                    (OS_FLAGS   )0,
                    (OS_OPT     )0,
                    (CPU_TS     )0,
                    (OS_ERR    *)p_err);//把恢复任务命令发布到中断消息队列
        return;       //返回,停止执行
    }
#endif
    /* 如果禁用了中断延迟发布或不是在中断中调用该函数 */
    OS_TaskResume(p_tcb, p_err);         //直接将任务 p_tcb 恢复//(7)
}
#endif
  • (1):任务控制块指针,该指针指向要恢复的任务,与挂起任务不同的是,该指针不允许指向任务自身。
  • (2):用于存放返回错误代码,如果恢复任务失败,则返回对应的错误代码。
  • (3):如果启用了安全检测(OS_SAFETY_CRITICAL)这个宏定义, 那么在编译代码的时候会包含安全检测, 如果p_err指针为空,系统会执行安全检测异常函数OS_SAFETY_CRITICAL_EXCEPTION(),然后退出。
  • (4):如果禁用了中断延迟发布和中断中非法调用检测,那么在中断中恢复任务则是非法的, 会直接返回错误类型为“在中断中恢复任务”,并且退出。而如果启用了中断延迟发布的话呢,就可以在中断中恢复任务, 因为中断延迟发布的真正操作是在中断发布任务中。
  • (5):如果启用了参数检测(OS_CFG_ARG_CHK_EN)这个宏定义,如果被恢复任务为空或是自身, 也是不允许的,会返回错误类型为“恢复自身”,并且退出操作。
  • (6):如果启用了中断延迟发布,并且如果该函数在中断中被调用, 系统就会把恢复任务命令发布到中断消息队列中,唤醒中断发布任务,在任务中恢复指定任务,并且退出。
  • (7):如果禁用了中断延迟发布或不是在中断中调用该函数, 直接调用OS_TaskResume()函数恢复任务

OS_TaskResume()函数源码如下:

#if OS_CFG_TASK_SUSPEND_EN > 0u//如果启用了函数 OSTaskResume()
void  OS_TaskResume (OS_TCB  *p_tcb,      //任务控制块指针
                    OS_ERR  *p_err)      //返回错误类型
{
    CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
    //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
    // SR(临界段关中断只需保存SR),开中断时将该值还原。
    CPU_CRITICAL_ENTER();                 //关中断
    *p_err  = OS_ERR_NONE;                 //错误类型为“无错误”
    switch (p_tcb->TaskState) {      //(1)//根据 p_tcb 的任务状态分类处理
        case OS_TASK_STATE_RDY:               //如果状态中没有挂起状态
        case OS_TASK_STATE_DLY:
        case OS_TASK_STATE_PEND:
        case OS_TASK_STATE_PEND_TIMEOUT:
        CPU_CRITICAL_EXIT();                              //开中断
        *p_err = OS_ERR_TASK_NOT_SUSPENDED;  //(2)//错误类型为“任务未被挂起”
        break;                                            //跳出

        case OS_TASK_STATE_SUSPENDED:           //(3)//如果是“挂起状态”
        OS_CRITICAL_ENTER_CPU_EXIT();                 //锁调度器,重开中断
        p_tcb->SuspendCtr--;              //(4)//任务的挂起嵌套数减1
        if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0) {  //如果挂起前套数为0
            p_tcb->TaskState = OS_TASK_STATE_RDY;    //修改状态为“就绪状态”
            OS_TaskRdy(p_tcb);                  //把 p_tcb 插入就绪列表
        }
        OS_CRITICAL_EXIT_NO_SCHED();              //开调度器,不调度任务
        break;                                            //跳出

        case OS_TASK_STATE_DLY_SUSPENDED:      //(5)//如果是“延时中被挂起”
        p_tcb->SuspendCtr--;                       //任务的挂起嵌套数减1
        if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0) { //如果挂起前套数为0
            p_tcb->TaskState = OS_TASK_STATE_DLY;    //修改状态为“延时状态”
        }
        CPU_CRITICAL_EXIT();                              //开中断
        break;                                            //跳出

        case OS_TASK_STATE_PEND_SUSPENDED:    //(6)//如果是“无期限等待中被挂起”
        p_tcb->SuspendCtr--;                      //任务的挂起嵌套数减1
        if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0) {  //如果挂起前套数为0
            p_tcb->TaskState = OS_TASK_STATE_PEND; //修改状态为“无期限等待状态”
        }
        CPU_CRITICAL_EXIT();                              //开中断
        break;                                            //跳出

        case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:	//(7)//如果是“有期限等待中被挂起”
        p_tcb->SuspendCtr--;                //任务的挂起嵌套数减1
        if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0) { //如果挂起前套数为0
            p_tcb->TaskState = OS_TASK_STATE_PEND_TIMEOUT;
        }
        CPU_CRITICAL_EXIT();                              //开中断
        break;                                            //跳出

        default:                        //(8) //如果 p_tcb 任务状态超出预期
        CPU_CRITICAL_EXIT();                              //开中断
        *p_err = OS_ERR_STATE_INVALID;        //错误类型为“状态非法”
        return;//跳出
    }

    OSSched();                //(9)//调度任务
}
#endif
  • (1):根据要挂起的任务状态分类处理,这样处理逻辑简单,更加方便快捷。
  • (2):如果要恢复的任务状态中没有挂起状态, 那表示任务没有被挂起,根本不需要恢复任务,返回错误类型为“任务未被挂起”,并且退出操作。
  • (3):如果要恢复的任务是单纯的挂起状态,那么可以恢复任务。
  • (4):任务的挂起记录次数减1,如果挂起前次数为0,表示任务已经完全恢复了,那么就可以参与系统的调度, 此时就要把任务添加到就绪列表中,并且将任务的状态变为就绪状态,操作完成之后就跳出switch语句,打开中断但是不进行任务调度, 因为在最后面才会进行任务调度。
  • (5):如果任务在延时的时候被挂起了,也可以进行恢复任务操作,任务的挂起记录次数减1,如果挂起前次数为0, 表示任务已经完全恢复了,那就会恢复挂起前的状态——延时状态,然后退出。
  • (6):同理,如果任务在无期限等待的时候被挂起了,也可以进行恢复任务操作,任务的挂起记录次数减1, 如果挂起前次数为0,表示任务已经完全恢复了,那就会恢复挂起前的状态——无期限等待状态,然后退出。
  • (7):如果任务在有期限等待的时候被挂起了,也可以进行恢复任务操作,任务的挂起记录次数减1, 如果挂起前次数为0,表示任务已经完全恢复了,那就会恢复挂起前的状态——有期限等待状态,然后退出。
  • (8):对于其他的任务状态,返回状态非法错误,然后退出。
  • (9):进行一次任务调度。

OSTaskResume()函数用于恢复挂起的任务。任务在挂起时候调用过多少次的OS_TaskSuspend()函数,那么就需要调用多少次OSTaskResume() 函数才能将任务恢复运行,下面来看看任务恢复函数OSTaskResume()的使用实例

/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
staticOS_TCB   AppTaskLed1TCB;/* LED任务句柄 */

static void KEY_Task(void* parameter)
{OS_ERR      err;
    while (1) {
        if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {
            /* KEY2 被按下 */
            printf("恢复LED任务!\n");
            OSTaskResume ( &AppTaskLed1TCB, & err );  /* 恢复LED任务! */
        }
        OSTimeDly ( 20, OS_OPT_TIME_DLY, & err );   /* 延时20个tick */
    }
}

3、删除任务函数OSTaskDel()

OSTaskDel()用于删除一个任务。
当一个任务删除另外一个任务时,形参为要删除任务创建时返回的任务句柄,如果是删除自身,则形参为 NULL。
要想使用该函数必须在os_cfg.h中把OS_CFG_TASK_DEL_EN宏定义配置为1,删除的任务将从所有就绪,阻塞,挂起和事件列表中删除, 任务删除函数OSTaskDel()源码具体如下

#if OS_CFG_TASK_DEL_EN > 0u//如果启用了函数 OSTaskDel()
void  OSTaskDel (OS_TCB  *p_tcb,                 //目标任务控制块指针
                OS_ERR  *p_err)                 //返回错误类型
{
    CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
    //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
    // SR(临界段关中断只需保存SR),开中断时将该值还原。

#ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0) {                //如果 p_err 为空
        OS_SAFETY_CRITICAL_EXCEPTION();        //执行安全检测异常函数
        return;                                //返回,停止执行
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u  //(1)//如果启用了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数在中断中被调用
        *p_err = OS_ERR_TASK_DEL_ISR;           //错误类型为“在中断中删除任务”
        return;                                //返回,停止执行
    }
#endif

    if (p_tcb == &OSIdleTaskTCB) {      //(2)//如果目标任务是空闲任务
        *p_err = OS_ERR_TASK_DEL_IDLE;          //错误类型为“删除空闲任务”
        return;                                //返回,停止执行
    }

#if OS_CFG_ISR_POST_DEFERRED_EN > 0u    //(3)//如果启用了中断延迟发布
    if (p_tcb == &OSIntQTaskTCB) {          //如果目标任务是中断延迟提交任务
        *p_err = OS_ERR_TASK_DEL_INVALID;       //错误类型为“非法删除任务”
        return;                                //返回,停止执行
    }
#endif

if (p_tcb == (OS_TCB *)0) {        //(4)//如果 p_tcb 为空
        CPU_CRITICAL_ENTER();                  //关中断
        p_tcb  = OSTCBCurPtr;         //目标任务设为自身
        CPU_CRITICAL_EXIT();                   //开中断
    }

    OS_CRITICAL_ENTER();                       //进入临界段
    switch (p_tcb->TaskState) {      //(5)//根据目标任务的任务状态分类处理
    case OS_TASK_STATE_RDY:                //如果是就绪状态
            OS_RdyListRemove(p_tcb);     //(6)//将任务从就绪列表移除
    break;                            //跳出

    case OS_TASK_STATE_SUSPENDED:    //(7)//如果是挂起状态
    break;                            //直接跳出

    case OS_TASK_STATE_DLY:        //(8)//如果包含延时状态
    case OS_TASK_STATE_DLY_SUSPENDED:
            OS_TickListRemove(p_tcb);         //将任务从节拍列表移除
    break;                            //跳出

    case OS_TASK_STATE_PEND:       //(9)//如果包含等待状态
    case OS_TASK_STATE_PEND_SUSPENDED:
    case OS_TASK_STATE_PEND_TIMEOUT:
    case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
            OS_TickListRemove(p_tcb);    //(10)//将任务从节拍列表移除
    switch (p_tcb->PendOn) {   //(11)//根据任务的等待对象分类处理
    case OS_TASK_PEND_ON_NOTHING: //如果没在等待内核对象
    case OS_TASK_PEND_ON_TASK_Q:  //如果等待的是任务消息队列
    case OS_TASK_PEND_ON_TASK_SEM://如果等待的是任务信号量
    break;                   //直接跳出

    case OS_TASK_PEND_ON_FLAG:    //如果等待的是事件
    case OS_TASK_PEND_ON_MULTI:   //如果等待多个内核对象
    case OS_TASK_PEND_ON_MUTEX:   //如果等待的是互斥量
    case OS_TASK_PEND_ON_Q:       //如果等待的是消息队列
    case OS_TASK_PEND_ON_SEM:     //如果等待的是信号量
                OS_PendListRemove(p_tcb);//(12)//将任务从等待列表移除
    break;                   //跳出

    default:                      //如果等待对象超出预期
    break;                   //直接跳出
            }
    break;                            //跳出

    default:                        //(13)//如果目标任务状态超出预期
            OS_CRITICAL_EXIT();                //退出临界段
            *p_err = OS_ERR_STATE_INVALID;      //错误类型为“状态非法”
    return;                            //返回,停止执行
    }

#if OS_CFG_TASK_Q_EN > 0u   //(14)//如果启用了任务消息队列
    (void)OS_MsgQFreeAll(&p_tcb->MsgQ);        //释放任务的所有任务消息
#endif

    OSTaskDelHook(p_tcb);              //(15)//调用用户自定义的钩子函数

#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
    OS_TLS_TaskDel(p_tcb);                                  /* Call TLSk     */
#endif

#if OS_CFG_DBG_EN > 0u  //(16)//如果启用了调试代码和变量
    OS_TaskDbgListRemove(p_tcb);               //将任务从任务调试双向列表移除
#endif
    OSTaskQty--;                      //(17)//任务数目减1

    OS_TaskInitTCB(p_tcb);             //(18)//初始化任务控制块
    p_tcb->TaskState = (OS_STATE)OS_TASK_STATE_DEL;//标定任务已被删除

    OS_CRITICAL_EXIT_NO_SCHED();               //退出临界段(无调度)

    *p_err = OS_ERR_NONE;                       //错误类型为“无错误”

    OSSched();                          //(19)//调度任务
}
#endif
  • (1):如果启用了中断中非法调用检测, 那么在中断中删除任务则是非法的,会直接返回错误类型为“在中断中删除任务”,并且退出。
  • (2):如果要删除的目标任务是空闲任务,这是绝对不允许的,系统中空闲任务的存在是必然的, 绝对不允许删除空闲任务,会返回错误类型为“删除空闲任务”的错误代码,并且退出。
  • (3):如果启用了中断延迟发布,但是要删除的目标任务是中断延迟发布任务,这也是绝对不允许的, 因为启用了中断延迟发布,则代表着系统中必须有一个中断延迟发布任务处理在中断中的发布的事情,所以会返回错误类型为“非法删除任务”的错误代码,并且退出。
  • (4):如果传递进来的任务控制块指针为0, 表示要删除的任务是任务自身,则将任务控制块指针指向当前任务,目标任务设为任务自身。
  • (5):根据目标任务的任务状态分类处理。
  • (6):如果任务是处于就绪态的,就将任务从就绪列表移除。
  • (7):如果任务是处于挂起状态就直接跳出switch语句。
  • (8):如果任务包含延时状态,那么将任务从节拍列表移除。
  • (9):如果任务包含等待状态。
  • (10):系统首先会将任务从节拍列表移除。
  • (11):然后再根据任务的等待对象分类处理, 如果没在等待内核对象或者等待的是任务消息队列或者等待的是任务信号量,那么直接跳出switch语句。
  • (12):而任务如果是在等待内核资源这些,如事件、消息队列、 信号量等,系统会直接将任务从等待列表移除,然后跳出switch语句。
  • (13):如果目标任务状态超出预期,直接返回错误类型为“状态非法”的错误,并且退出删除操作。
  • (14):如果启用了任务消息队列,将释放任务的所有任务消息。
  • (15):在删除任务的时候,系统还会调用用户自定义的钩子函数,用户可以通过该钩子函数进行自定义的操作。
  • (16):如果启用了调试代码和变量,将任务从任务调试双向列表移除。
  • (17):到这里就进行任务的删除操,系统的任务数目减1。
  • (18):初始化对应的任务控制块,将任务状态变为删除态, 退出临界段但不进行调度,返回错误类型为“无错误”的错误代码。
  • (19):进行一次任务调度。

删除任务是说任务将返回并处以删除(休眠)状态,任务的代码不再被μC/OS调用。
删除任务不是删除代码,删除任务和挂起任务有些相似,其实有着本质的区别。
根本来说,最大的不同就是删除任务队任务控制块的操作,我们知道在任务创建的时候,需要给每个任务分配一个任务控制块,这个任务控制块存储有关这个任务重要的信息,对任务间有至关重要的作用,挂起任务根本不会动任务控制块,但删除任务就会把任务控制块进行初始化,这样关于任务的任何信息都被抹去。

注意,删除任务并不会释放任务的栈空间。

删除任务函数的使用实例如下:

/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
staticOS_TCB   AppTaskLed1TCB;/* LED任务句柄 */

static void KEY_Task(void* parameter)
{OS_ERR      err;
while (1) {
    if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {
    /* KEY2 被按下 */
            printf("删除LED任务!\n");
            OSTaskDel( &AppTaskLed1TCB, & err );  /* 删除LED任务! */
        }
        OSTimeDly ( 20, OS_OPT_TIME_DLY, & err );   /* 延时20个tick */
    }
}

4、任务延时函数

1. OSTimeDly()

OSTimeDly()在我们任务中用得非常之多,每个任务都必须是死循环,并且是必须要有阻塞的情况,否则低优先级的任务就无法被运行了。
OSTimeDly()函数常用于停止当前任务进行的运行,延时一段时间后再运行。
OSTimeDly()函数源码具体如下:

void  OSTimeDly (OS_TICK   dly,              //(1)//延时的时钟节拍数
    OS_OPT    opt,                          //(2)//选项
                OS_ERR   *p_err)            //(3)//返回错误类型
{
    CPU_SR_ALLOC();
    //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和定义一个局部变
    //量,用于保存关中断前的 CPU 状态寄存器 SR(临界段关中断只需保存SR)
    //,开中断时将该值还原。

#ifdef OS_SAFETY_CRITICAL   //(4)//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0) {                        //如果错误类型实参为空
        OS_SAFETY_CRITICAL_EXCEPTION();                //执行安全检测异常函数
        return;                                        //返回,不执行延时操作
    }
#endif
                        //(5)
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u//如果启用(默认启用)了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0u){//如果该延时函数是在中断中被调用
        *p_err = OS_ERR_TIME_DLY_ISR;       //错误类型为“在中断函数中延时”
        return;                             //返回,不执行延时操作
    }
#endif
/* 当调度器被锁时任务不能延时 */         //(6)
    if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0u) {  //如果调度器被锁
        *p_err = OS_ERR_SCHED_LOCKED;             //错误类型为“调度器被锁”
        return;                                        //返回,不执行延时操作
    }

    switch (opt) {             //(7)//根据延时选项参数 opt 分类操作
    case OS_OPT_TIME_DLY:               //如果选择相对时间(从现在起延时多长时间)
    case OS_OPT_TIME_TIMEOUT:                      //如果选择超时(实际同上)
    case OS_OPT_TIME_PERIODIC:                     //如果选择周期性延时
    if (dly == (OS_TICK)0u) {    //(8)//如果参数 dly 为0(0意味不延时)
                *p_err = OS_ERR_TIME_ZERO_DLY;         //错误类型为“0延时”
    return;                               //返回,不执行延时操作
            }
    break;

    case OS_OPT_TIME_MATCH:         //(9)
    //如果选择绝对时间(匹配系统开始运行(OSStart())后的时钟节拍数)
    break;




    default:                            //(10)//如果选项超出范围
        *p_err = OS_ERR_OPT_INVALID;               //错误类型为“选项非法”
    return;                                   //返回,不执行延时操作
    }

    OS_CRITICAL_ENTER();                             //进入临界段
    OSTCBCurPtr->TaskState = OS_TASK_STATE_DLY;  //(11)//修改当前任务的任务状态为延时状态
    OS_TickListInsert(OSTCBCurPtr,             //将当前任务插入节拍列表
                    dly,
                    opt,
                    p_err);         //(12)
    if (*p_err != OS_ERR_NONE) {          //如果当前任务插入节拍列表时出现错误
        OS_CRITICAL_EXIT_NO_SCHED();                  //退出临界段(无调度)
        return;                                       //返回,不执行延时操作
    }
    OS_RdyListRemove(OSTCBCurPtr);          //(13)//从就绪列表移除当前任务
    OS_CRITICAL_EXIT_NO_SCHED();                       //退出临界段(无调度)
    OSSched();                              //(14)//任务切换
    *p_err = OS_ERR_NONE;                               //错误类型为“无错误”
}
  • (1):任务延时的时钟节拍数,也就是延时的时间。
  • (2):任务延时的可选选项,在os.h中有定义
#define  OS_OPT_TIME_DLY                     DEF_BIT_NONE   
//OS_OPT_TIME_DLY:dly 为相对时间,就是从现在起延时多长时间, 到时钟节拍总计数OSTickCtr = OSTickCtr当前 + dly 时延时结束。
#define  OS_OPT_TIME_TIMEOUT                ((OS_OPT)DEF_BIT_01) 
//OS_OPT_TIME_TIMEOUT:跟 OS_OPT_TIME_DLY 的作用情况一样。
#define  OS_OPT_TIME_MATCH                  ((OS_OPT)DEF_BIT_02)
//OS_OPT_TIME_MATCH:dly为绝对时间, 就是从系统开始运行(调用 OSStart())时到节拍总计数OSTickCtr = dly 时延时结束。
#define  OS_OPT_TIME_PERIODIC               ((OS_OPT)DEF_BIT_03)
//OS_OPT_TIME_PERIODIC:周期性延时, 跟 OS_OPT_TIME_DLY的作用差不多,如果是长时间延时,该选项更精准一些。
  • (3):用于存放返回错误代码,如果挂起任务失败,则返回对应的错误代码。
  • (4):如果启用(默认禁用)了安全检测, 系统就会执行安全检测的代码,如果错误类型实参为空,就执行安全检测异常函数,然后返回,不执行延时操作。
  • (5):如果启用(默认启用)了中断中非法调用检测 ,如果该延时函数是在中断中被调用,将返回错误类型为“在中断函数中延时”的错误代码,退出,不执行延时操作。
  • (6):如果调度器被锁,则不允许进行延时操作, 返回错误类型为“调度器被锁”的错误代码,并且退出延时操作。因为延时就必须进行任务的切换,所以在延时的时候不能锁定调度器,
  • (7):根据延时选项参数 opt 分类操作。
  • (8):如果选择相对时间(从现在起延时多长时间)或者选择超时时间或者选择周期性延时, 那么这表示延时时间,如果参数 dly 为0(0意味不延时),就会返回错误类型为“0延时”的错误代码,并且退出不执行延时操作。
  • (9):如果选择绝对时间(匹配系统开始运行(OSStart())后的时钟节拍数。
  • (10):如果选项超出范围,则视为非法,返回错误类型为“选项非法”的错误代码,并且退出不执行延时操作。
  • (11):程序能执行到这里,说明能正常进行延时操作,那么系统就会修改当前任务的任务状态为延时状态。
  • (12):调用OS_TickListInsert()函数将当前任务插入节拍列表, 加入节拍列表的任务会按照延时时间进行升序排列
  • (13):调用OS_RdyListRemove()函数从就绪列表移除当前任务,进行延时操作。
  • (14):进行一次任务切换。

OS_TickListInsert()源码具体如下:

void  OS_TickListInsert (OS_TCB   *p_tcb, //任务控制块
                        OS_TICK   time,  //时间
                        OS_OPT    opt,   //选项
                        OS_ERR   *p_err) //返回错误类型
{
    OS_TICK            tick_delta;
    OS_TICK            tick_next;
    OS_TICK_SPOKE     *p_spoke;
    OS_TCB            *p_tcb0;
    OS_TCB            *p_tcb1;
    OS_TICK_SPOKE_IX   spoke;

    if (opt == OS_OPT_TIME_MATCH)
    {      //如果 time 是个绝对时间
        tick_delta = time - OSTickCtr - 1u;  //计算离到期还有多长时间
        if (tick_delta > OS_TICK_TH_RDY)
        {    //如果延时时间超过了门限
            p_tcb->TickCtrMatch = (OS_TICK        )0u;  //将任务的时钟节拍的匹配变量置0
            p_tcb->TickRemain   = (OS_TICK        )0u; //将任务的延时还需时钟节拍数置0
            p_tcb->TickSpokePtr = (OS_TICK_SPOKE *)0; //该任务不插入节拍列表
            *p_err      =  OS_ERR_TIME_ZERO_DLY; //错误类型相当于“0延时”
            return;                         //返回,不将任务插入节拍列表
        }
        p_tcb->TickCtrMatch = time; //任务等待的匹配点为 OSTickCtr = time
        p_tcb->TickRemain   = tick_delta + 1u; //计算任务离到期还有多长时间
    }
    else if (time > (OS_TICK)0u)
    {          //如果 time > 0
        if (opt == OS_OPT_TIME_PERIODIC)
        {    //如果 time 是周期性时间
            tick_next  = p_tcb->TickCtrPrev + time;
            //计算任务接下来要匹配的时钟节拍总计数
            tick_delta = tick_next - OSTickCtr - 1u;  //计算任务离匹配还有个多长时间
            if (tick_delta < time)
            {        //如果 p_tcb->TickCtrPrev<OSTickCtr+1
                p_tcb->TickCtrMatch = tick_next; //将 p_tcb->TickCtrPrev + time设为时钟节拍匹配点
            }
            else
            {         //如果 p_tcb->TickCtrPrev >= OSTickCtr + 1
                 p_tcb->TickCtrMatch = OSTickCtr + time; //将 OSTickCtr + time 设为时钟节拍匹配点
            }
            p_tcb->TickRemain   = p_tcb->TickCtrMatch - OSTickCtr; //计算任务离到期还有多长时间
            p_tcb->TickCtrPrev  = p_tcb->TickCtrMatch; //保存当前匹配值为下一周期延时用

        }
        else
        {                            //如果 time 是相对时间
            p_tcb->TickCtrMatch = OSTickCtr + time; //任务等待的匹配点为 OSTickCtr + time
            p_tcb->TickRemain   = time; //计算任务离到期的时间就是 time
        }

    }
    else
    {                           //如果 time = 0
        p_tcb->TickCtrMatch = (OS_TICK        )0u; //将任务的时钟节拍的匹配变量置0
        p_tcb->TickRemain   = (OS_TICK        )0u; //将任务的延时还需时钟节拍数置0
        p_tcb->TickSpokePtr = (OS_TICK_SPOKE *)0; //该任务不插入节拍列表
        *p_err               =  OS_ERR_TIME_ZERO_DLY; //错误类型为“0延时”
        return;                           //返回,不将任务插入节拍列表
    }


    spoke   = (OS_TICK_SPOKE_IX)(p_tcb->TickCtrMatch % OSCfg_TickWheelSize);
    //使用哈希算法(取余)来决定任务存于数组
    p_spoke = &OSCfg_TickWheel[spoke];
    //OSCfg_TickWheel的哪个元素(组织一个节拍列表),
    //与更新节拍列表相对应,可方便查找到期任务。
    if (p_spoke->NbrEntries == (OS_OBJ_QTY)0u)
    {     //如果当前节拍列表为空
        p_tcb->TickNextPtr   = (OS_TCB   *)0;
        //任务中指向节拍列表中下一个任务的指针置空
        p_tcb->TickPrevPtr   = (OS_TCB   *)0;
        //任务中指向节拍列表中前一个任务的指针置空
        p_spoke->FirstPtr    =  p_tcb;
        //当前任务被列为该节拍列表的第一个任务
        p_spoke->NbrEntries  = (OS_OBJ_QTY)1u;   //节拍列表中的元素数目为1
    }
    else
    {                                     //如果当前节拍列表非空
        p_tcb1     = p_spoke->FirstPtr;          //获取列表中的第一个任务
        while (p_tcb1 != (OS_TCB *)0)
        {          //如果该任务存在
            p_tcb1->TickRemain = p_tcb1->TickCtrMatch   //计算该任务的剩余等待时间
                                - OSTickCtr;
            if (p_tcb->TickRemain > p_tcb1->TickRemain)
            {
                //如果当前任务的剩余等待时间大于该任务的
                if (p_tcb1->TickNextPtr != (OS_TCB *)0)
                {//如果该任务不是列表的最后一个元素
                    p_tcb1               =  p_tcb1->TickNextPtr;
                    //让当前任务继续与该任务的下一个任务作比较
                }
                else
                {         //如果该任务是列表的最后一个元素
                    p_tcb->TickNextPtr   = (OS_TCB *)0; //当前任务为列表的最后一个元素
                    p_tcb->TickPrevPtr   =  p_tcb1;  //该任务是当前任务的前一个元素
                    p_tcb1->TickNextPtr  =  p_tcb; //当前任务是该任务的后一个元素
                    p_tcb1             = (OS_TCB *)0; //插入完成,退出 while 循环
                }
            }
            else
            {                //如果当前任务的剩余等待时间不大于该任务的
                if (p_tcb1->TickPrevPtr == (OS_TCB *)0)
                {//如果该任务是列表的第一个元素
                    p_tcb->TickPrevPtr   = (OS_TCB *)0; //当前任务就作为列表的第一个元素
                    p_tcb->TickNextPtr   =  p_tcb1; //该任务是当前任务的后一个元素
                    p_tcb1->TickPrevPtr  =  p_tcb;  //当前任务是该任务的前一个元素
                    p_spoke->FirstPtr    =  p_tcb;  //当前任务是列表的第一个元素
                }
                else
                {                          //如果该任务也不是是列表的第一个元素
                    p_tcb0  =  p_tcb1->TickPrevPtr; // p_tcb0 暂存该任务的前一个任务
                    p_tcb->TickPrevPtr   =  p_tcb0;
                    //该任务的前一个任务作为当前任务的前一个任务
                    p_tcb->TickNextPtr   =  p_tcb1; //该任务作为当前任务的后一个任务
                    p_tcb0->TickNextPtr  =  p_tcb;  // p_tcb0暂存的任务的下一个任务改为当前任务
                    p_tcb1->TickPrevPtr  =  p_tcb; // 该任务的前一个任务也改为当前任务
                }
                p_tcb1 = (OS_TCB *)0;     //插入完成,退出 while 循环
            }
        }
        p_spoke->NbrEntries++;             //节拍列表中的元素数目加1
    }       //更新节拍列表的元素数目的最大记录
    if (p_spoke->NbrEntriesMax < p_spoke->NbrEntries) {
        p_spoke->NbrEntriesMax = p_spoke->NbrEntries;
    }
    p_tcb->TickSpokePtr = p_spoke;       //记录当前任务存放于哪个节拍列表
    *p_err               = OS_ERR_NONE;//错误类型为“无错误”
}

任务的延时在实际中运用特别多,因为需要暂停一个任务,让任务放弃CPU,延时结束后再继续运行该任务。
如果任务中没有阻塞的话,比该任务优先级低的任务则无法得到CPU的使用权,就无法运行。

延时函数的使用实例具体如下:

void vTaskA( void * pvParameters )
{
    while (1) {
    //  ...
    //  这里为任务主体代码
    //  ...

    /* 调用相对延时函数,阻塞1000个tick */
    OSTimeDly ( 1000, OS_OPT_TIME_DLY, & err );
    }
}

2. OSTimeDlyHMSM()

OSTimeDlyHMSM() 函数与 OSTimeDly() 函数的功能类似,也是用于停止当前任务进行的运行,延时一段时间后再运行。
但是OSTimeDlyHMSM()函数会更加直观,延时多少个小时、分钟、秒、毫秒。

用户若要使用 OSTimeDlyHMSM()函数, 必须将宏OS_CFG_TIME_DLY_HMSM_EN 设为1,该宏定义位于os_cfg.h中。

OSTimeDlyHMSM()函数源码具体如下:

#if OS_CFG_TIME_DLY_HMSM_EN > 0u//如果启用(默认启用)了 OSTimeDlyHMSM() 函数
void  OSTimeDlyHMSM (CPU_INT16U   hours,    //(1)     //延时小时数
                    CPU_INT16U   minutes,  //(2)      //分钟数
                    CPU_INT16U   seconds,  //(3)      //秒数
                    CPU_INT32U   milli,   //(4)       //毫秒数
                    OS_OPT       opt,      //(5)      //选项
                    OS_ERR      *p_err)    //(6)      //返回错误类型
{
#if OS_CFG_ARG_CHK_EN > 0u  //(7)//如果启用(默认启用)了参数检测功能
    CPU_BOOLEAN  opt_invalid;      //声明变量用于参数检测
    CPU_BOOLEAN  opt_non_strict;
#endif
    OS_OPT       opt_time;
    OS_RATE_HZ   tick_rate;
    OS_TICK      ticks;
    CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL   //(8)//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0) {          //如果错误类型实参为空
        OS_SAFETY_CRITICAL_EXCEPTION();  //执行安全检测异常函数
        return;                          //返回,不执行延时操作
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u    //(9)
    //如果启用(默认启用)了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0u){//如果该延时函数是在中断中被调用
        *p_err = OS_ERR_TIME_DLY_ISR;     //错误类型为“在中断函数中延时”
        return;                    //返回,不执行延时操作
    }
#endif
    /* 当调度器被锁时任务不能延时 */
    if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0u) { //(10)//如果调度器被锁
        *p_err = OS_ERR_SCHED_LOCKED;         //错误类型为“调度器被锁”
        return;                         //返回,不执行延时操作
    }

    opt_time = opt & OS_OPT_TIME_MASK; //(11)//检测除选项中与延时时间性质有关的位
    switch (opt_time) {                    //根据延时选项参数 opt 分类操作
    caseOS_OPT_TIME_DLY:               //如果选择相对时间(从现在起延时多长时间)
    case OS_OPT_TIME_TIMEOUT:                //如果选择超时(实际同上)
    case OS_OPT_TIME_PERIODIC:                         //如果选择周期性延时
        if (milli == (CPU_INT32U)0u) {                //如果毫秒数为0
            if (seconds == (CPU_INT16U)0u) {          //如果秒数为0
                if (minutes == (CPU_INT16U)0u) {      //如果分钟数为0
                    if (hours == (CPU_INT16U)0u) {    //如果小时数为0
                            *p_err = OS_ERR_TIME_ZERO_DLY; //错误类型为“0延时”
                    return;             //(12)//返回,不执行延时操作
                    }
                }
            }
        }
break;

    case OS_OPT_TIME_MATCH:                 //(13)
    //如果选择绝对时间(把系统开始运行(OSStart()时做为起点)
    break;



    default:                               //(14)//如果选项超出范围
            *p_err = OS_ERR_OPT_INVALID;                   //错误类型为“选项非法”
    return;                                       //返回,不执行延时操作
    }

#if OS_CFG_ARG_CHK_EN > 0u                  //(15)
//如果启用(默认启用)了参数检测功能
    opt_invalid = DEF_BIT_IS_SET_ANY(opt, ~OS_OPT_TIME_OPTS_MASK);
    //检测除选项位以后其他位是否被置位
    if (opt_invalid == DEF_YES) {           //(16)
    //如果除选项位以后其他位有被置位的
        *p_err = OS_ERR_OPT_INVALID;             //错误类型为“选项非法”
        return;                            //返回,不执行延时操作
    }

    opt_non_strict = DEF_BIT_IS_SET(opt, OS_OPT_TIME_HMSM_NON_STRICT);//(17)
    //检测有关时间参数取值范围的选项位
    if (opt_non_strict != DEF_YES) {//如果选项选择了OS_OPT_TIME_HMSM_STRICT
        if (milli   > (CPU_INT32U)999u) {  //(18)     //如果毫秒数>999
            *p_err = OS_ERR_TIME_INVALID_MILLISECONDS; //错误类型为“毫秒数不可用”
            return;             //返回,不执行延时操作
        }
        if (seconds > (CPU_INT16U)59u) {    //(19)//如果秒数>59
            *p_err = OS_ERR_TIME_INVALID_SECONDS;  //错误类型为“秒数不可用”
            return;                          //返回,不执行延时操作
        }
        if (minutes > (CPU_INT16U)59u) {  //(20)//如果分钟数>59
            *p_err = OS_ERR_TIME_INVALID_MINUTES; //错误类型为“分钟数不可用”
            return;                            //返回,不执行延时操作
        }
        if (hours   > (CPU_INT16U)99u) {    //(21)//如果小时数>99
            *p_err = OS_ERR_TIME_INVALID_HOURS;   //错误类型为“小时数不可用”
            return;                            //返回,不执行延时操作
        }
    } else {          //如果选项选择了 OS_OPT_TIME_HMSM_NON_STRICT

        if (minutes > (CPU_INT16U)9999u) {   //(22)//如果分钟数>9999
            *p_err = OS_ERR_TIME_INVALID_MINUTES; //错误类型为“分钟数不可用”
            return;                                 //返回,不执行延时操作
        }
        if (hours   > (CPU_INT16U)999u) {  //(23)     //如果小时数>999
            *p_err = OS_ERR_TIME_INVALID_HOURS; //错误类型为“小时数不可用”
            return;                         //返回,不执行延时操作
        }
    }
#endif


/*将延时时间转换成时钟节拍数*/
    tick_rate = OSCfg_TickRate_Hz;      //(24)//获取时钟节拍的频率
    ticks     = ((OS_TICK)hours * (OS_TICK)3600u + (OS_TICK)minutes *
                (OS_TICK)60u + (OS_TICK)seconds) * tick_rate
                + (tick_rate * ((OS_TICK)milli + (OS_TICK)500u /
                tick_rate)) / (OS_TICK)1000u;   //(25)//将延时时间转换成时钟节拍数

    if (ticks > (OS_TICK)0u) {             //(26)//如果延时节拍数>0
        OS_CRITICAL_ENTER();                         //进入临界段
        OSTCBCurPtr->TaskState = OS_TASK_STATE_DLY;  //修改当前任务的任务状态为延时状态
        OS_TickListInsert(OSTCBCurPtr,     //将当前任务插入节拍列表
                        ticks,
                        opt_time,
                        p_err);             //(27)
        if(*p_err != OS_ERR_NONE) {  //如果当前任务插入节拍列表时出现错误
            OS_CRITICAL_EXIT_NO_SCHED();            //退出临界段(无调度)
            return;                                 //返回,不执行延时操作
        }
        OS_RdyListRemove(OSTCBCurPtr);     //(28)//从就绪列表移除当前任务
        OS_CRITICAL_EXIT_NO_SCHED();                 //退出临界段(无调度)
        OSSched();                         //(29)//任务切换
        *p_err = OS_ERR_NONE;                         //错误类型为“无错误”
    } else {                                         //如果延时节拍数=0
        *p_err = OS_ERR_TIME_ZERO_DLY;       //错误类型为“0延时”
    }
}
#endif
  • (1):延时时间——小时数。
  • (2):延时时间——分钟数
  • (3):延时时间——秒数
  • (4):延时时间——毫秒数
  • (5):任务延时的可选选项,在os.h中有定义,具体如下:
#define  OS_OPT_TIME_DLY                      DEF_BIT_NONE  
//OS_OPT_TIME_DLY:dly 为相对时间,就是从现在起延时多长时间, 到时钟节拍总计数OSTickCtr = OSTickCtr 当前 + dly 时延时结束。
#define  OS_OPT_TIME_TIMEOUT                ((OS_OPT)DEF_BIT_01)
//OS_OPT_TIME_TIMEOUT:跟 OS_OPT_TIME_DLY 的作用情况一样。
#define  OS_OPT_TIME_MATCH                  ((OS_OPT)DEF_BIT_02)
//OS_OPT_TIME_MATCH:dly为绝对时间, 就是从系统开始运行(调用 OSStart())时到节拍总计数OSTickCtr = dly 时延时结束。
#define  OS_OPT_TIME_PERIODIC               ((OS_OPT)DEF_BIT_03)
//OS_OPT_TIME_PERIODIC:周期性延时, 跟 OS_OPT_TIME_DLY的作用差不多,如果是长时间延时,该选项更精准一些。

#define  OS_OPT_TIME_HMSM_STRICT            ((OS_OPT)DEF_BIT_NONE)
/*延时时间取值比较严格:
小时数hours: (0-99)
分钟数minutes: (0-59)
秒数seconds: (0-59)
毫秒数milliseconds: (0-999)
*/
#define  OS_OPT_TIME_HMSM_NON_STRICT        ((OS_OPT)DEF_BIT_04)
/*延时时间取值比较宽松。
小时数hours: (0-999)
分钟数minutes: (0-9999)
秒数seconds: (0-65535)
毫秒数milliseconds: (0-4294967295)
*/
  • (6):用于存放返回错误代码,如果挂起任务失败,则返回对应的错误代码。
  • (7):如果启用(默认启用)了参数检测功能,则定义一些变量用于参数检测。
  • (8):如果启用(默认禁用)了安全检测,就会包含安全检测的代码, 如果错误类型实参为空,执行安全检测异常函数,然后返回,不执行延时操作。
  • (9):如果启用(默认启用)了中断中非法调用检测, 并且如果该延时函数是在中断中被调用,则被视为非法,返回错误类型为“在中断函数中延时”的错误,然后返回,不执行延时操作。
  • (10):当调度器被锁时任务不能延时,任务延时后会进行任务调度,如果调度器被锁, 就会返回错误类型为“调度器被锁”的错误,然后返回,不执行延时操作。
  • (11):检测除选项中与延时时间性质有关的位,并且根据延时选项参数 opt 分类操作。
  • (12):如果选择相对延时(从现在起延时多长时间)、 超时延时、周期性延时等延时类型,就会检测一下延时的时间是多 少,如果是0,则是不允许的,返回错误类型为“0延时”的错误,不进行延时操作。
  • (13):如果选择绝对时间,会把系统开始运行OSStart()时做为起点。
  • (14):如果选项超出范围,返回错误类型为“选项非法”的错误,然后退出,不进行延时操作。
  • (15):如果启用(默认启用)了参数检测功能,就会检测除选项位以外其他位是否被置位。
  • (16):如果除选项位外其他位有被置位的,则返回错误类型为“选项非法”的错误,然后退出,不执行延时操作。
  • (17):检测有关时间参数取值范围的选项位, 如果选项选择了 OS_OPT_TIME_HMSM_STRICT,就是比较严格的参数范围。
  • (18):如果毫秒数大于999,返回错误类型为“毫秒数不可用”的错误,然后退出,不执行延时操作。
  • (19):如果如果秒数大于59,返回错误类型为“秒数不可用”的错误,然后退出,不执行延时操作。
  • (20):如果分钟数大于59,返回错误类型为“分钟数不可用”的错误,然后退出,不执行延时操作。
  • (21):如果小时数大于99,返回错误类型为“小时数不可用”的错误,然后退出,不执行延时操作。
  • (22):而如果选项选择了 OS_OPT_TIME_HMSM_ NON_STRICT, 就是比较宽松的延时操作, 如果分钟数大于9999,返回错误类型为“分钟数不可用”的错误,然后退出,不执行延时操作。
  • (23):如果小时数大于999,返回错误类型为“小时数不可用”的错误,然后退出,不执行延时操作。
  • (24):因为我们延时的时间是时、分、秒、毫秒,但是系统的时间单位是时钟节拍, 所以需要将延时时间转换成时钟节拍数,首先获取时钟节拍的频率。
  • (25):然后根据我们延时的时间进行计算转换,将延时时间转换成时钟节拍数tick。
  • (26):如果延时节拍数大于0,表示可以延时,修改当前任务的任务状态为延时状态。
  • (27):调用OS_TickListInsert()函数将当前任务插入节拍列表。
  • (28):调用OS_RdyListRemove()函数从就绪列表移除当前任务。
  • (29):进行一次任务切换。

任务延时函数OSTimeDlyHMSM()的使用实例具体如下:

void vTaskA( void * pvParameters )
{
    while (1) {
    //  ...
    //  这里为任务主体代码
    //  ...

    /* 调用延时函数,延时1s */
    OSTimeDlyHMSM(0,0,1,0, OS_OPT_TIME_DLY, & err );
    }
}

最近更新

  1. TCP协议是安全的吗?

    2024-04-03 20:54:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-03 20:54:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-03 20:54:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-03 20:54:02       20 阅读

热门阅读

  1. 分布式IO模块PLC扩展模拟量模块

    2024-04-03 20:54:02       13 阅读
  2. 为什么重写 equals 时,必须重写 hashCode?

    2024-04-03 20:54:02       13 阅读
  3. js绑定点击事件的方法

    2024-04-03 20:54:02       11 阅读
  4. 面试算法-128-单词拆分 II

    2024-04-03 20:54:02       11 阅读
  5. RabbitMQ

    2024-04-03 20:54:02       9 阅读