文章目录
序言
任务管理,也叫做进程管理,是任何一个操作系统都必须面对的核心问题之一,UCOSIII同样不例外,在UCOSIII中与任务管理相关的API在os_task.c
这个文件中实现。下面我们将会从四个大方向介绍UCOSIII的任务管理:相关文件概览、部分API实际应用,API详解,相关数据结构介绍。
相关文件概览
os_task.c文件概览
看了一下这个文件有2550行,包含如下的函数
- 改变任务优先级:OSTaskChangePrio
- 创建一个任务:OSTaskCreate
- 删除一个任务:OSTaskDel
- 消息队列清除:OSTaskQFlush
- 等待消息:OSTaskQPend
- 取消等待:OSTaskQPendAbort
- 向任务发送消息:OSTaskQPost
- 获取一个任务寄存器的当前值:OSTaskRegGet
- 获取下一个可获得的任务寄存器的ID:OSTaskRegGetID
- 设置一个任务寄存器的当前值:OSTaskRegSet
- 恢复一个悬挂的任务:OSTaskResume
- 等待一个任务信号:OSTaskSemPend
- 取消等待一个任务信号:OSTaskSemPendAbort
- 向一个任务发送信号:OSTaskSemPost
- 设置任务的信号计数器:OSTaskSemSet
- 堆栈检查:OSTaskStkChk
- 挂起一个任务:OSTaskSuspend
- 改变一个任务的时间片:OSTaskTimeQuantaSet
- 添加任务到调试列表:OS_TaskDbgListAdd
- 任务管理初始化:OS_TaskInit
- 初始化任务控制块:OS_TaskInitTCB
- 发送消息到一个任务:OS_TaskQPost
- 恢复一个挂起的任务:OS_TaskResume
- 抓住偶然的任务返回:OS_TaskReturn
- 向一个任务发送一个信号:OS_TaskSemPost
- 挂起一个任务:OS_TaskSuspend
os_core.c文件概览
这个文件有2642行,包含如下函数
- 内核初始化:OSInit
- 通知UCOSIII任务将要进入中断:OSIntEnter
- 通知UCOSIII任务将要退出中断:OSIntExit
- 任务调度:OSSched
- 任务调度器上锁:OSSchedLock
- 任务调度器解锁:OSSchedUnlock
- 改变时间片轮转调度属性:OSSchedRoundRobinCfg
- 提前放弃时间片:OSSchedRoundRobinYield
- 开始多任务处理:OSStart
- 获取UCOSIII版本号:OSVersion
- 空闲任务:OS_IdleTask
- 空闲任务初始化:OS_IdleTaskInit
- 将一个任务锁在某个事件上:OS_Pend
- 退出等待某个事件:OS_PendAbort
- 初始化一个等待列表:OS_PendListInit
- 向一个任务发送消息:OS_Post
- 就绪列表初始化:OS_RdyListInit
部分API实际应用
任务创建和删除
在使用UCOSIII的时候,我们需要按照一定的顺序来初始化并打开UCOSIII,可以按照如下步骤来:
- 调用OS_init()初始化UCOSIII
- 使用OS_CRITICAL_ENTER()进入临界区
- 使用OSTaskCreate创建任务,一般在main()函数里面创建一个AppTaskStart任务,其它的任务由它创建
- 使用OS_CRITICAL_ENTER()退出临界区
- 调用OSStart开启UCOS
如下所示(看不懂的部分先忽略,以后的东西以后再学
),仔细看注释,另外两个任务的函数定义我没写出来,写出来太长了。
//首先要定义任务控制块和任务堆栈以及任务函数
/*
/************************************************************
* LOCAL GLOBAL VARIABLES
*************************************************************
*/
static OS_TCB AppTaskStartTCB;
static CPU_STK AppTaskStartTCBStk[APP_CFG_TASK_START_STK_SIZE];
#define TASK1_TASK_PRIO 4
#define TASK1_STK_SIZE 128
static OS_TCB Task1_TaskTCB;
static CPU_STK TASK1_TASK_STK[TASK1_STK_SIZE];
#define TASK2_TASK_PRIO 5
#define TASK2_STK_SIZE 128
static OS_TCB Task2_TaskTCB;
static CPU_STK TASK2_TASK_STK[TASK2_STK_SIZE];
/*
/************************************************************
* FUNCTION PROTOTYPES
*************************************************************
*/
static void AppTaskStart(void *p_arg);
static void task1_task(void *p_arg);
static void task2_task(void *p_arg);
int main()
{
OS_ERR err;
CPU_SR_ALLOC();
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
LED_Init();
OSInit(&err);//1. 调用OS_init()初始化UCOSIII
if(err != OS_ERR_NONE){
/* Do something */
}
OS_CRITICAL_ENTER();//2. 使用OS_CRITICAL_ENTER()进入临界区
OSTaskCreate ((OS_TCB * )&AppTaskStartTCB,//3. 使用OSTaskCreate创建任务
(CPU_CHAR * )"start task",
(OS_TASK_PTR )AppTaskStart,
(void * )0,
(OS_PRIO )APP_CFG_TASK_START_PRIO,
(CPU_STK * )&AppTaskStartTCBStk[0],
(CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE/10,
(CPU_STK_SIZE )APP_CFG_TASK_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);
if(err != OS_ERR_NONE){
/* Do something */
}
OS_CRITICAL_EXIT();//4. 使用OS_CRITICAL_ENTER()退出临界区
OSStart(&err);//5. 调用OSStart开启UCOS
if(err != OS_ERR_NONE){
/* Do something */
}
}
//编写任务函数
//AppTaskStart任务函数
static void AppTaskStart(void *p_arg)
{
OS_ERR err;
CPU_SR_ALLOC();
p_arg = p_arg;
CPU_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err); //统计任务
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了测量中断关闭时间
CPU_IntDisMeasMaxCurReset();
#endif
#if OS_CFG_SCHED_ROUND_ROBIN_EN //当使用时间片轮转的时候
//使能时间片轮转调度功能,时间片长度为1个系统时钟节拍,既1*5=5ms
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);
#endif
OS_CRITICAL_ENTER(); //进入临界区
//创建TASK1任务
OSTaskCreate((OS_TCB * )&Task1_TaskTCB,
(CPU_CHAR * )"Task1 task",
(OS_TASK_PTR )task1_task,
(void * )0,
(OS_PRIO )TASK1_TASK_PRIO,
(CPU_STK * )&TASK1_TASK_STK[0],
(CPU_STK_SIZE)TASK1_STK_SIZE/10,
(CPU_STK_SIZE)TASK1_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )2,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
//创建TASK2任务
OSTaskCreate((OS_TCB * )&Task2_TaskTCB,
(CPU_CHAR * )"task2 task",
(OS_TASK_PTR )task2_task,
(void * )0,
(OS_PRIO )TASK2_TASK_PRIO,
(CPU_STK * )&TASK2_TASK_STK[0],
(CPU_STK_SIZE)TASK2_STK_SIZE/10,
(CPU_STK_SIZE)TASK2_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);
OS_CRITICAL_EXIT(); //退出临界区
OSTaskDel((OS_TCB*)0,&err); //删除start_task任务自身
}
我们用到了两个在os_task.c文件中定义的函数,一个是OSTaskCreate,另一个是OSTaskDel函数,前者用来创建任务,后者用来删除任务,这里之所以用到了删除,是因为开始任务(AppTaskStart)
的终极目标就是创建其它的任务,一旦其它任务创建完成,它的使命也就结束了,所以自然要被删除掉。
任务挂起和恢复
这里我们就用到了上面没写出来的两个任务函数
void task1_task(void *p_arg)
{
u8 task1_num=0;
OS_ERR err;
CPU_SR_ALLOC();
p_arg = p_arg;
POINT_COLOR = BLACK;
OS_CRITICAL_ENTER();
LCD_DrawRectangle(5,110,115,314); //画一个矩形
LCD_DrawLine(5,130,115,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(6,111,110,16,16,"Task1 Run:000");
OS_CRITICAL_EXIT();
while(1)
{
task1_num++; //任务1执行次数加1 注意task1_num1加到255的时候会清零!!
LED0= ~LED0;
printf("任务1已经执行:%d次\r\n",task1_num);
if(task1_num==5)
{
OSTaskSuspend((OS_TCB*)&Task2_TaskTCB,&err);//任务1执行5次后挂起任务2
printf("任务1挂起了任务2!\r\n");
}
if(task1_num==10)
{
OSTaskResume((OS_TCB*)&Task2_TaskTCB,&err); //任务1运行10次后恢复任务2
printf("任务1恢复了任务2!\r\n");
}
LCD_Fill(6,131,114,313,lcd_discolor[task1_num%14]); //填充区域
LCD_ShowxNum(86,111,task1_num,3,16,0x80); //显示任务执行次数
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_HMSM_STRICT,&err); //延时1s
}
}
在这个任务中,我们有一个局部变量task1_num,当task1_num为5的时候,我们会利用OSTaskSuspend函数将任务2挂起(任务2没有写出来
),当task1_num为10的时候,我们会利用OSTaskResume恢复任务2。
API详解
我们在上面遇到了四个重要的任务相关的API
OSTaskCreate
这个函数是用来让UCOSIII能够管理任务的执行的,任务要么在多任务管理开始前被创建,要么被正在运行的任务创建,不可以通过外部中断来创建任务。
void OSTaskCreate (OS_TCB *p_tcb,
CPU_CHAR *p_name,
OS_TASK_PTR p_task,
void *p_arg,
OS_PRIO prio,
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_limit,
CPU_STK_SIZE stk_size,
OS_MSG_QTY q_size,
OS_TICK time_quanta,
void *p_ext,
OS_OPT opt,
OS_ERR *p_err)
上面是它的函数原型,下面我们来一个一个介绍这些参数
- *p_tcb:一个TCB指针
- *p_name:一个字符串指针,用来给任务提供一个名字
- p_task:一个任务函数指针
- *p_arg:传递给任务函数的参数
- prio:任务优先级
- *p_stk_base:堆栈基址
- *stk_limit:任务堆栈深度限位
- *stk_size:任务堆栈大小
- q_size:可以被发送给这个任务的消息的最大数量
- time_quanta:时间片轮转调度的时间片数量
- *p_ext:用户补充的存储区
- opt:包含这个任务一些额外的信息
- *p_err:存放该函数错误时的返回值
OSTaskDel
这个函数允许你删除一个任务。主调任务可以通过给p_tcb赋予一个NULL指针来删除自身。被删除的任务回到休眠态,可以被创建函数再次被激活。
void OSTaskDel (OS_TCB *p_tcb,
OS_ERR *p_err)
- *p_tcb:要被删除的任务的控制块
- *p_err:一个指向错误码的指针
OSTaskSuspend
这个函数用来挂起一个任务,如果p_tcb参数是NULL,函数将挂起主调任务自身,如果不是,则挂起p_tcb对应的任务
void OSTaskSuspend (OS_TCB *p_tcb,
OS_ERR *p_err)
- *p_tcb:将要被挂起的任务的TCB的指针
- *p_err:一个指向错误码的指针
OSTaskResume
这个函数被用来恢复一个曾经被挂起的任务,这是唯一的可以移除精确函数挂起的调用
void OSTaskResume (OS_TCB *p_tcb,
OS_ERR *p_err)
- *p_tcb:将要被恢复的任务的TCB的指针
- *p_err:一个指向错误码的指针
任务状态
我们上面学习了四个任务调度函数:创建任务,删除任务,挂起任务,恢复任务。这些任务在这些状态之间切换,那么到底是哪些状态呢,我们在这里单独介绍一下任务状态。
任务状态 | 描述 |
---|---|
休眠态 | 任务只是以任务函数的形式存在,只是存储区的一段代码 |
就绪态 | 任务在就续表中已经登记,等待获取CPU |
运行态 | 正在运行的任务处于运行态 |
等待态 | 正在运行的任务需要等待某一个事件,比如信号量、消息、事件标志组等,暂时会让出CPU |
中断服务态 | 一个正在运行的任务被中断打断,CPU转而执行中断服务程序,这时这个任务就会被挂起,进入中断服务状态 |
任务的状态可以在这四种状态之间切换,至于具体的切换机制,我们在后面的学习中会慢慢了解到
相关数据结构介绍
在上面的API使用中,我们用到了几种数据结构,它们分别是任务控制块TCB,任务堆栈CPU_STK,还有我们没有直接用到的任务就绪表,下面我们一一介绍这些数据结构
任务控制块OS_TCB
这是一个非常长的数据结构,如下所示
/*
------------------------------------------------------------------------------------------------------------------------
* TASK CONTROL BLOCK
------------------------------------------------------------------------------------------------------------------------
*/
struct os_tcb {
CPU_STK *StkPtr; /* Pointer to current top of stack */
void *ExtPtr; /* Pointer to user definable data for TCB extension */
CPU_STK *StkLimitPtr; /* Pointer used to set stack 'watermark' limit */
OS_TCB *NextPtr; /* Pointer to next TCB in the TCB list */
OS_TCB *PrevPtr; /* Pointer to previous TCB in the TCB list */
OS_TCB *TickNextPtr;
OS_TCB *TickPrevPtr;
OS_TICK_SPOKE *TickSpokePtr; /* Pointer to tick spoke if task is in the tick list */
CPU_CHAR *NamePtr; /* Pointer to task name */
CPU_STK *StkBasePtr; /* Pointer to base address of stack */
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS TLS_Tbl[OS_CFG_TLS_TBL_SIZE];
#endif
OS_TASK_PTR TaskEntryAddr; /* Pointer to task entry point address */
void *TaskEntryArg; /* Argument passed to task when it was created */
OS_PEND_DATA *PendDataTblPtr; /* Pointer to list containing objects pended on */
OS_STATE PendOn; /* Indicates what task is pending on */
OS_STATUS PendStatus; /* Pend status */
OS_STATE TaskState; /* See OS_TASK_STATE_xxx */
OS_PRIO Prio; /* Task priority (0 == highest) */
CPU_STK_SIZE StkSize; /* Size of task stack (in number of stack elements) */
OS_OPT Opt; /* Task options as passed by OSTaskCreate() */
OS_OBJ_QTY PendDataTblEntries; /* Size of array of objects to pend on */
CPU_TS TS; /* Timestamp */
OS_SEM_CTR SemCtr; /* Task specific semaphore counter */
/* DELAY / TIMEOUT */
OS_TICK TickCtrPrev; /* Previous time when task was ready */
OS_TICK TickCtrMatch; /* Absolute time when task is going to be ready */
OS_TICK TickRemain; /* Number of ticks remaining for a match (updated at ... */
/* ... run-time by OS_StatTask() */
OS_TICK TimeQuanta;
OS_TICK TimeQuantaCtr;
#if OS_MSG_EN > 0u
void *MsgPtr; /* Message received */
OS_MSG_SIZE MsgSize;
#endif
#if OS_CFG_TASK_Q_EN > 0u
OS_MSG_Q MsgQ; /* Message queue associated with task */
#if OS_CFG_TASK_PROFILE_EN > 0u
CPU_TS MsgQPendTime; /* Time it took for signal to be received */
CPU_TS MsgQPendTimeMax; /* Max amount of time it took for signal to be received */
#endif
#endif
#if OS_CFG_TASK_REG_TBL_SIZE > 0u
OS_REG RegTbl[OS_CFG_TASK_REG_TBL_SIZE]; /* Task specific registers */
#endif
#if OS_CFG_FLAG_EN > 0u
OS_FLAGS FlagsPend; /* Event flag(s) to wait on */
OS_FLAGS FlagsRdy; /* Event flags that made task ready to run */
OS_OPT FlagsOpt; /* Options (See OS_OPT_FLAG_xxx) */
#endif
#if OS_CFG_TASK_SUSPEND_EN > 0u
OS_NESTING_CTR SuspendCtr; /* Nesting counter for OSTaskSuspend() */
#endif
#if OS_CFG_TASK_PROFILE_EN > 0u
OS_CPU_USAGE CPUUsage; /* CPU Usage of task (0.00-100.00%) */
OS_CPU_USAGE CPUUsageMax; /* CPU Usage of task (0.00-100.00%) - Peak */
OS_CTX_SW_CTR CtxSwCtr; /* Number of time the task was switched in */
CPU_TS CyclesDelta; /* value of OS_TS_GET() - .CyclesStart */
CPU_TS CyclesStart; /* Snapshot of cycle counter at start of task resumption */
OS_CYCLES CyclesTotal; /* Total number of # of cycles the task has been running */
OS_CYCLES CyclesTotalPrev; /* Snapshot of previous # of cycles */
CPU_TS SemPendTime; /* Time it took for signal to be received */
CPU_TS SemPendTimeMax; /* Max amount of time it took for signal to be received */
#endif
#if OS_CFG_STAT_TASK_STK_CHK_EN > 0u
CPU_STK_SIZE StkUsed; /* Number of stack elements used from the stack */
CPU_STK_SIZE StkFree; /* Number of stack elements free on the stack */
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN
CPU_TS IntDisTimeMax; /* Maximum interrupt disable time */
#endif
#if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u
CPU_TS SchedLockTimeMax; /* Maximum scheduler lock time */
#endif
#if OS_CFG_DBG_EN > 0u
OS_TCB *DbgPrevPtr;
OS_TCB *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
};
具体的每一个意思在注释中都有,如果看不懂英文,可以一边看中文一边看英文,注意一定要把每一个成员的含义看一下,能看懂多少看多少。
任务堆栈CPU_STK
任务堆栈用来在切换任务和调用其它函数的时候保存现场的,因此每个任务都要有自己的堆栈。
从cpu.h中我们可以知道,CPU_STK其实就是CPU_INT32U,所以一个堆栈其实就是一个无符号整形数组
CPU_STK TASK_STK[64]
这句话的含义就是定义一个包含有64个无符号整数的数组
任务就绪表
UCOSIII中就绪的任务是放在任务就续表里的,任务就续表分为两个部分:优先级位映射表OSPrioTbl[]和就绪任务列表OSRdyList[]。
位优先级映射表
我们有一个数组OSPrioTbl[],它的第一个元素为OSPrioTbl[0],这个元素的最高位为最高优先级,最低位为最低优先级,它的第二个元素为OSPrioTbl[1],这个元素的最高位所代表的优先级正好在上一个元素的下面,依此类推下去。
综上所述:用一个数组OSPrioTbl[]来表示任务优先级状态,其中的一个位代表一个优先级。
就绪任务列表
OSRdyList[]是用来记录每一个优先级下所有就绪的任务,它在os.h中定义,数组元素的类型位OS_RDY_LIST,这是一个结构体,如下所示
struct os_rdy_list{
OS_TCB *HeadPtr;
OS_TCB *TailPtr;
OS_OBJ_QTY NbrEntries;
}
UCOSIII支持时间片轮转调度,所以一个优先级下面可以有多个任务,我们需要对这些任务进行管理,OSRdyList[]中的每一个元素管理一个优先级的所有任务,这个元素是一个结构体,用来维护一个双向链表,这个链表里面是这个优先级下面的所有任务的控制块。
与任务就绪列表相关的函数在os_core.c中定义,如下是一些常用函数
函数 | 描述 |
---|---|
OS_RdyListInit() | 由OSInit()调用用来初始化并清空任务就绪列表 |
OS_RdyListInsertHead() | 向某一优先级下的任务双向链表头部添加一个任务控制块TCB |
OS_RdyListInsertTail() | 向某一优先级下的任务双向链表尾部添加一个任务控制块TCB |
OS_RdyListRemove() | 将任务控制块TCB从任务就绪列表中删除 |
OS_RdyListInsertTail() | 将一个任务控制块TCB从双向链表的头部移到尾部 |
OS_RdyListInsert() | 在就续表中添加一个任务控制块TCB |
总结
重点掌握启用UCOSIII的基本步骤