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
第四章 中断和时钟
第五章 任务的同步和通信
第七章 动态内存管理
作者:津湾山支蚁