uCOS-II实时操作系统任务详解

uCOS-II实时操作系统:任务

  • 第三章 任务
  • 任务的基础知识
  • 任务的调度
  • 任务的创建
  • 任务的挂起和恢复和删除
  • 第四章 中断和时钟
  • 第五章 任务的同步和通信
  • 第七章 动态内存管理

  • 本文的主要目的是学习uCOS II操作系统,俗话说好记性不如烂笔头,这里主要学习“任务”“中断和时钟”“任务的同步和通信”“动态内存管理”我认为比较重要的几块内容,这块学习内容会持续更新,直到完成~

    2024-07-31

    2024-08-01

    2024-08-03

    2024-08-05

    2024-08-07

    2024-08-09

    2024-08-11

    2024-08-13

    已完成

    进行中

    计划中

    现有任务

    Adding GANTT diagram functionality to mermaid

    第三章 任务

    任务的基础知识

    操作系统调度的最小资源就是任务,可以看到一个任务的核心是任务控制块,其相当于人的身份证,身份证包含人的个人信息,则任务控制块也包含任务的所有信息,任务的组成如下所示:

    上面是一个任务,操作系统要对多个任务进行管理,则登记造册得到任务链表:

    注意,这里和任务代码一样,任务堆栈也需要进行申请(任务堆栈的创建):

    typedef  unsigned int  OS_STK  //4字节无符号整型
    
    #define  TASK_STK_SIZE  32
    OS_STK  TaskStk[TASK_STK_SIZE  ]
    

    以上给出任务组成和任务链表,下面给出实际的任务控制块结构体。另外,为了提高任务控制块的申请效率,实际上是系统给出了两条任务控制块链表:一条是提前申请好的空白任务控制块链表,还有一条是任务控制块链表。

    任务控制块结构
    typedef struct os_tcb {
        OS_STK          *OSTCBStkPtr;           /* 定义一个无符号整型指向stk的栈顶指针                        */
    #if OS_TASK_CREATE_EXT_EN > 0u  //如果延申创建标志位=1,那句创建一些延申参数
        void            *OSTCBExtPtr;           /* 用户自定义的数据指针                                     */
        OS_STK          *OSTCBStkBottom;        /* 指向栈底的站在这                                         */
        INT32U           OSTCBStkSize;          /* 栈大小   */
        INT16U           OSTCBOpt;              /*任务选项  */
        INT16U           OSTCBId;               /* 任务ID  */
    #endif
    
        struct os_tcb   *OSTCBNext;             /* 结构体指针OSTCBNext:相对于OS_TCB{OSTCBNext{...}},结构体里的结构体,构成链表                 */
        struct os_tcb   *OSTCBPrev;             /*OS_TCB{OSTCBPrev{...}},结构体里的结构体,构成链表                      */
    
    #if (OS_EVENT_EN)
        OS_EVENT        *OSTCBEventPtr;         /* 指向事件控制块的指针                */
    #endif
    ......
    #if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
        void            *OSTCBMsg;              /* Message received from OSMboxPost() or OSQPost()         */
    #endif
    ......
        INT32U           OSTCBDly;              /* 任务延时时间片   */
        INT8U            OSTCBStat;             /* 任务状态                                             */
        INT8U            OSTCBStatPend;         /* Task PEND status                                        */
        INT8U            OSTCBPrio;             /* 任务的优先级                           */
    
        INT8U            OSTCBX;                /* 优先级位图法用到的结构    */
        INT8U            OSTCBY;                /* 优先级位图法用到的结构    */
        OS_PRIO          OSTCBBitX;             /* 优先级位图法用到的结构          */
        OS_PRIO          OSTCBBitY;             /* 优先级位图法用到的结构          */
    
    #if OS_TASK_DEL_EN > 0u
        INT8U            OSTCBDelReq;           /*删除任务请求,是否需要删除自己       */
    #endif
    
    ......//具体全部可以查看系统源码
    } OS_TCB;
    
    

    系统初始化时创建一个空任务控制块链表


    这里注意,为了方便快速访问任务,系统创建了一个数组OSTCBPrioTb1[],下标对应任务的优先级(为任务的唯一标识ID),数组内存放指向各个任务控制块的指针;其中,系统定义了一个变量OSTCBCur存放当前任务控制块指针

    任务的调度

    任务的调度是操作系统的核心。调度器需要完成两项任务,一是判断哪些任务处于就绪状态,这部分对应就绪表的知识;二是进行实际的任务调度。
    就绪表就是一个位图,表的下标对应任务优先级,每一个任务占据表中的一位,这里系统给出了一个类型为INT8U的OSRdyTb1[]数组。
    为了便于对就绪表进行查找,系统还定义了一个数据类型为INT8U的变量OSRdyGrp,其中每一位对应OSRdyTb1[]的一个元素(即一个INT8U的元素)。

    注意对于就绪表的操作主要有三个,即为登记、注销以及得到就绪的最高优先级

    下面即为实际的任务调度,主要是获得就绪的优先级任务的任务控制块指针(操作就绪表得到)和当前任务的任务控制块指针(在OSTCBCur变量中)。下面给出任务级调度器OSSched的源码:

    void  OS_Sched (void)
    {
    #if OS_CRITICAL_METHOD == 3u                           /* Allocate storage for CPU status register     */
        OS_CPU_SR  cpu_sr = 0u;
    #endif
    
        OS_ENTER_CRITICAL();
        if (OSIntNesting == 0u) {                          /* Schedule only if all ISRs done and ...       */
            if (OSLockNesting == 0u) {                     /* ... scheduler is not locked                  */
                OS_SchedNew();
                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];//得到任务控制块指针
                if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy     */
    #if OS_TASK_PROFILE_EN > 0u
                    OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task      */
    #endif
                    OSCtxSwCtr++;                          /* 统计切换次数的计数器加1             */
                    OS_TASK_SW();                          /* Perform a context switch                     */
                }
            }
        }
        OS_EXIT_CRITICAL();
    }
    
    #define  OS_TASK_SW()         OSCtxSw()
    

    任务的切换工作是靠OSCtxSw()实现的。这给出任务切换的示意图和具体代码:

    ①和②的部分为:把被终止任务的断点指针(这里理解为PC指针)保存到任务堆栈中;把通用寄存器中的数据保存到任务堆栈中;还需要把被终止任务堆栈指针保存到任务的任务控制块的OSTCBStkPtr指针中。
    ③和④的部分为:获得待运行任务控制块,CPU通过该任务控制块获得待运行任务的任务堆栈指针;将任务堆栈中的通用寄存器内容恢复到处理器中;CPU获得待运行任务的断点指针

    ;OSCtxSw()具体代码实现:
    OSCtxSw
    		PUSH    {R4, R5}
            LDR     R4, =NVIC_INT_CTRL  	;触发PendSV异常 (causes context switch)
            LDR     R5, =NVIC_PENDSVSET
            STR     R5, [R4]
    		POP     {R4, R5}
            BX      LR
    

    任务的创建

    创建任务的实质就是创建任务控制块,并通过任务控制块将任务代码和任务堆栈关联起来,形成一个任务。应用程序通过调用函数OSTaskCreate()来创建一个任务,下面给出其具体代码:

    INT8U  OSTaskCreate (void   (*task)(void *p_arg), //指向任务的指针
                         void    *p_arg,  //传递给任务的参数
                         OS_STK  *ptos,  //指向任务堆栈栈顶的指针
                         INT8U    prio)  //任务的优先级
    {
        OS_STK    *psp;
        INT8U      err;
    #if OS_CRITICAL_METHOD == 3u                 /* Allocate storage for CPU status register               */
        OS_CPU_SR  cpu_sr = 0u;
    #endif
    
    
    
    #ifdef OS_SAFETY_CRITICAL_IEC61508
        if (OSSafetyCriticalStartFlag == OS_TRUE) {
            OS_SAFETY_CRITICAL_EXCEPTION();
        }
    #endif
    
    #if OS_ARG_CHK_EN > 0u
        if (prio > OS_LOWEST_PRIO) {             /* 检测任务的优先级是否合法           */
            return (OS_ERR_PRIO_INVALID);
        }
    #endif
        OS_ENTER_CRITICAL();
        if (OSIntNesting > 0u) {                 /* 操作系统不允许在终端服务函数中创建任务  */
            OS_EXIT_CRITICAL();
            return (OS_ERR_TASK_CREATE_ISR);
        }
        if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority  */
            OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ...  */
                                                 /* ... the same thing until task is created.              */
            OS_EXIT_CRITICAL();
            psp = OSTaskStkInit(task, p_arg, ptos, 0u);             /* 初始化任务堆栈         */
            err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);  /* 获得并初始化任务控制块*/
            if (err == OS_ERR_NONE) {
                if (OSRunning == OS_TRUE) {      /* Find highest priority task if multitasking has started */
                    OS_Sched();  //任务调度
                }
            } else {
                OS_ENTER_CRITICAL();
                OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others                 */
                OS_EXIT_CRITICAL();
            }
            return (err);
        }
        OS_EXIT_CRITICAL();
        return (OS_ERR_PRIO_EXIST);
    }
    

    以上就是操作系统任务的比较核心的内容,下面是对应的任务的挂起、恢复和删除的内容。

    任务的挂起和恢复和删除

    任务的挂起和恢复都是针对自身和除空闲任务之外的其它任务。 函数OSTaskSuspend()用于挂起任务,使其进入等待状态,然后只能在其他任务中调用恢复函数OSTaskResume()恢复其为就绪状态。其函数原型如下:

    挂起任务函数原型:
    INT8U OSTaskSuspend(INT8U prio)
    
    恢复任务函数原型
    INT8U OSTaskResume(INT8U prio)
    

    这里prio是任务的优先级别,如果任务要挂起自身,则函数参数必须为常量OS_PRIO_SELF,该常量定义在uCOS_II.H中

    任务的删除就是将任务控制块从任务控制块链表中删除,归还到空任务控制块链表,同时将任务就绪表置零。函数OSTaskDel()用来删除自身或除空闲任务之外的其它任务。

    删除任务函数原型
    INT8U OSTaskDel(INT8U prio)
    

    务必注意,考虑到任务会动态分配内存和信号量等资源,直接删除任务而不释放这部分内存,可能会造成内存泄漏,因此应当谨慎删除一个占用资源的任务,比较可行办法是,提出删除任务请求的任务只负责提出删除任务请求(使用INT8U OSTaskDelReq(INT8U prio)函数提出请求),而删除工作则由被删除任务自己(1)释放资源(2)调用OSTaskDel()删除自己。

    参考:https://blog.csdn.net/weixin_43491077/article/details/115802619

    第四章 中断和时钟

    第五章 任务的同步和通信

    第七章 动态内存管理

    作者:津湾山支蚁

    物联沃分享整理
    物联沃-IOTWORD物联网 » uCOS-II实时操作系统任务详解

    发表回复