uCOS3实时操作系统(系统初始化和任务启动)
文章目录
- ucos初始化
- 任务创建
- 任务启动
ucos初始化
- 系统运行的过程如下:OSInit -> OSTaskCreate -> OSStart
- ucos初始化主要在
OSInit
中进行,下面列举了该初始化过程中比较重要的几个步骤:OSInit()OSInitHook();OS_CPU_ExceptStkBase //将异常栈的栈底(OS_CPU_ExceptStkBase)按 8 字节对齐OS_KA_BASEPRI_Boundary //初始化全局变量 OS_KA_BASEPRI_Boundary 的值,该全局变量用于设置 BASEPRI 寄存器OSIntNestingCtr = 0u;//中断嵌套计数OSRunning = OS_STATE_OS_STOPPED; //内核运行状态OSSchedLockNestingCtr = 0u;//任务调度器锁定嵌套计数OSTCBCurPtr = (OS_TCB *)0;/* 指向当前任务的任务控制块的指针 */OSTCBHighRdyPtr = (OS_TCB *)0;/* 指向优先级最高的任务的任务控制块的指针 */OSPrioCur = 0u;/* 当前任务的任务优先级 */OSPrioHighRdy = 0u;/* 优先级最高的任务的任务优先级 */p_stk = OSCfg_ISRStkBasePtr; // 任务异常栈空间清零处理,该栈空间是一个全局变量…… //一些其他的必要的初始化(但是对于程序分析不是很重要没写入进来)OS_PrioInit();for (i = 0u; i < OS_PRIO_TBL_SIZE; i++) { //OS_PRIO_TBL_SIZE此变量通过设置的最大优先级数量动态调整OSPrioTbl[i] = 0u; //我们设置32,刚好一个数组只需要一个字即可装下32bit,此处是进行清零操作} //若后续创建了新的任务则对应的bit会置1,bit31对应优先级0OS_RdyListInit();p_rdy_list = &OSRdyList[i]; //这 OS_CFG_PRIO_MAX 个就绪态任务链表被存放在数组 OSRdyList[OS_CFG_PRIO_MAX中p_rdy_list->HeadPtr = (OS_TCB *)0; //数组 OSRdyList 的一个元素就代表一个就绪态任务链表p_rdy_list->TailPtr = (OS_TCB *)0;OS_TaskInit(p_err); // 任务管理的初始化,将任务数量计数器清零,将任务切换次数计数器清零OS_IdleTaskInit(p_err); //创建空闲任务及其相关初始化变量OS_TickInit(p_err);OSTickCtr = 0u;//将系统时钟节拍计数器(OSTickCtr)清零OSTickList.NbrEntries = 0u; //将 Tick 任务链表(OSTickList)中指向下一个 Tick 任务控制块的指针清空OSTickList.NbrUpdated = 0u;OS_StatTaskInit(p_err); //创建统计任务及其相关初始化变量OS_TmrInit(p_err); //创建软件定时器任务及其相关初始化变量OSCfg_Init(); //主要用于保证一些未使用到的变量不会被编译器优化掉,中途可能会用到OSInitialized = OS_TRUE; //µC/OS-III 内核初始化状态
任务创建
- 下面是一个简单的任务创建函数
OSTaskCreate( (OS_TCB *)&StartTask_TCB,(CPU_CHAR *)"start_task",(OS_TASK_PTR )start_task,(void *)0,(OS_PRIO )START_TASK_PRIO,(CPU_STK *)StartTask_STK,(CPU_STK_SIZE )START_STK_SIZE / 10,(CPU_STK_SIZE )START_STK_SIZE,(OS_MSG_QTY )0,(OS_TICK )0,(void *)0,(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),(OS_ERR *)&err);
- 接下面简要说明一下创建具体过程
OSTaskCreate…… //各种可能的错误检查,如栈空间大小检测,中断中非法调用等等OS_TaskInitTCB(p_tcb); //初始化任务控制块中成员变量的值为0#if (CPU_CFG_STK_GROWTH == CPU_STK_GROWTH_HI_TO_LO) //栈的生长方向是与硬件有关的p_stk_limit = p_stk_base + stk_limit; //根据栈的生长方向确定 “水位”限制的大小,因为传入是一个值不是地址#elsep_stk_limit = p_stk_base + (stk_size - 1u) - stk_limit;#endifp_sp = OSTaskStkInit(p_task, //初始化任务栈,详细内容参考后面注释1p_arg,p_stk_base,p_stk_limit,stk_size,opt);p_tcb->StkPtr = p_sp; //将更新后的栈指针更新至任务控制块的栈指针中…… //根据函数传入的参数初始化任务控制块中的成员变量CPU_CRITICAL_ENTER(); //进入临界区OS_PrioInsert(p_tcb->Prio); //更新在init函数中创建的优先级位图表OS_RdyListInsertTail(p_tcb); //任务插入就绪态任务链表,将对应的链表的前后指针都设置为任务控制块地址OSTaskQty++; //任务数量+1CPU_CRITICAL_EXIT(); //退出临界区OSSched(); //任务切换,这个是下一章节的内容,此时OSRunning还是stoppped状态,会直接退出该函数,需要经过OSStart()函数改变此状态才行。
- 注释1 初始化任务栈,任务创建时会在栈的最开始位置创建一个如下图所示的任务栈空间方便后期进行切换,且任务栈创建成功后会将更新后的栈指针更新至任务控制块的栈指针中,便于后期进行出栈操。
对应的函数实现如下图所示:
任务启动
-
开始 µC/OS-III 任务调度是 µC/OS-III 内核开始运行前的最后一步,在此之前需要先调用函数 OSInit()对 µC/OS-III 内核进行初始化,并至少创建一个应用任务。通过调用函数 OSStart()就能够开始 µC/OS-III 的任务调度。
OSStart(&err);if (OSInitialized != OS_TRUE) { //判断用户在此之前是否没有初始化操作系统*p_err = OS_ERR_OS_NOT_INIT;return;}if (OSTaskQty <= kernel_task_cnt) { //判断用户在此之前是否没有创建应用任务*p_err = OS_ERR_OS_NO_APP_TASK;return;}if (OSRunning == OS_STATE_OS_STOPPED) {OSPrioHighRdy = OS_PrioGetHighest(); //就绪态任务的任务优先级以位图的当时记录在数组 OSPrioTbl 中通过计算前导零的方式就能够非常方便地获取就绪态任务中最高的任务优先级OSPrioCur = OSPrioHighRdy;OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;OSTCBCurPtr = OSTCBHighRdyPtr; //获取最高优先级任务控制块的指针OSRunning = OS_STATE_OS_RUNNING; //将状态改为启动OSStartHighRdy(); //此函数是任务开始调度的核心,下面会进一步讲解*p_err = OS_ERR_FATAL_RETURN;}
-
函数 OSStartHighRdy()是在文件 os_cpu_a.asm 中定义的一个标号,注意此处与任务创建函数中的任务栈初始化联系十分紧密,将初始化任务栈中的寄存器全部都出栈到CPU寄存器里面,代码如下:
OSStartHighRdyCPSID I ; Prevent interruption during context switchMOV32 R0, NVIC_SYSPRI14 ; Set the PendSV exception priorityMOV32 R1, NVIC_PENDSV_PRISTRB R1, [R0]MOVS R0, #0 ; Set the PSP to 0 for initial context switch callMSR PSP, R0MOV32 R0, OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBaseLDR R1, [R0]MSR MSP, R1BL OSTaskSwHook ; Call OSTaskSwHook() for FPU Push & PopMOV32 R0, OSPrioCur ; OSPrioCur = OSPrioHighRdy;MOV32 R1, OSPrioHighRdyLDRB R2, [R1]STRB R2, [R0]MOV32 R0, OSTCBCurPtr ; OSTCBCurPtr = OSTCBHighRdyPtr;MOV32 R1, OSTCBHighRdyPtrLDR R2, [R1]STR R2, [R0]LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;MSR PSP, R0 ; Load PSP with new process SPMRS R0, CONTROLORR R0, R0, #2BIC R0, R0, #4 ; Clear FPCA bit to indicate FPU is not in useMSR CONTROL, R0ISB ; Sync instruction streamLDMFD SP!, {R4-R11, LR} ; Restore r4-11, lr from new process stackLDMFD SP!, {R0-R3} ; Restore r0, r3LDMFD SP!, {R12, LR} ; Load R12 and LRLDMFD SP!, {R1, R2} ; Load PC and discard xPSRCPSIE IBX R1
-
需要注意的是,在我们使用裸机进行中断任务切换的时候不管主程序还是中断都是使用的是MSP堆栈指针,而在ucos操作系统中我们在主程序的任务切换上使用的是PSP堆栈指针,在中断系统调度上中我们使用的是MSP堆栈指针,此外,在中断进行时,系统会自动出入栈除R4-R11以外的寄存器。
-
控制寄存器选择当前使用哪个堆栈指针。
-
在上面的代码中用到了入栈出栈相关的指令,具体的描述如下图所示: