简介
这里就要开始讲解最重要的一节:任务,毕竟这是整个系统的核心所在。
关键结构
看注释就可以了:
typedef struct OS_TCB {
/* General part: identical for all implementations. */
U8 cb_type; /* Control Block Type 控制块类型,有TCB MCB SCB MUCB HCB五种*/
U8 state; /* Task state 任务状态,有INACTIVE READY RUNNING WAIT_DLY WAIT_ITV WAIT_OR WAIT_AND WAIT_SEM WAIT_MBX WAIT_MUT 十种*/
U8 prio; /* Execution priority 任务优先级 */
U8 task_id; /* Task ID value for optimized TCB access 任务ID号 */
struct OS_TCB *p_lnk; /* Link pointer for ready/sem. wait list 就绪任务链表 */
struct OS_TCB *p_rlnk; /* Link pointer for sem./mbx lst backwards 等待信号量,邮箱任务链表*/
struct OS_TCB *p_dlnk; /* Link pointer for delay list 延时任务链表 */
struct OS_TCB *p_blnk; /* Link pointer for delay list backwards 任务延时反向链表 */
U16 delta_time; /* Time until time out 超时时间 */
U16 interval_time; /* Time interval for periodic waits 周期时间 */
U16 events; /* Event flags 当前置位的标志位 */
U16 waits; /* Wait flags 必须等待的标志位 */
void **msg; /* Direct message passing when task waits 传递给任务的消息*/
struct OS_MUCB *p_mlnk; /* Link pointer for mutex owner list 互斥量等待链表 */
U8 prio_base; /* Base priority 原来的优先级 */
/* Hardware dependant part: specific for CM processor */
U8 stack_frame; /* Stack frame: 0=Basic, 1=Extended, */
/* (2=VFP/D16 stacked, 4=NEON/D32 stacked) */
U16 priv_stack; /* Private stack size, 0= system assigned 私有栈大小 如果为0就让系统分配*/
U32 tsk_stack; /* Current task Stack pointer (R13) 栈指针 */
U32 *stack; /* Pointer to Task Stack memory block 栈空间 */
/* Task entry point used for uVision debugger */
FUNCP ptask; /* Task entry address 任务执行入口 */
} *P_TCB;
关键实现
rt_get_TID
为了节省资源定义了一个按需大小的P_TCB数组,来分配每个任务的ID号,注意该函数是static,表示内部使用,另外任务ID是从1开始的:
void *os_active_TCB[OS_TASK_CNT];
static OS_TID rt_get_TID (void) {
U32 tid;
for (tid = 1; tid <= os_maxtaskrun; tid++) {
if (os_active_TCB[tid-1] == NULL) { //获取空闲的id号
return ((OS_TID)tid);
}
}
return (0);
}
rt_init_context
在RTOS-RTX分析系列之移植部分
一节已经介绍过rt_init_stack
,因此,这个函数就非常简单了:
static void rt_init_context (P_TCB p_TCB, U8 priority, FUNCP task_body) {
/* Initialize general part of the Task Control Block. */
p_TCB->cb_type = TCB;
p_TCB->state = READY;
p_TCB->prio = priority; //设置的优先级
p_TCB->prio_base = priority; //优先级翻转时需要使用
p_TCB->p_lnk = NULL;
p_TCB->p_rlnk = NULL;
p_TCB->p_dlnk = NULL;
p_TCB->p_blnk = NULL;
p_TCB->p_mlnk = NULL;
p_TCB->delta_time = 0;
p_TCB->interval_time = 0;
p_TCB->events = 0;
p_TCB->waits = 0;
p_TCB->stack_frame = 0;
if (p_TCB->priv_stack == 0) {
/* Allocate the memory space for the stack. */
p_TCB->stack = rt_alloc_box (mp_stk);
}
rt_init_stack (p_TCB, task_body);
}
rt_switch_req
根据RTOS-RTX分析系列之移植部分
一节解释任务切换的详解,这个函数就是切换任务的标志:
void rt_switch_req (P_TCB p_new) {
/* Switch to next task (identified by "p_new"). */
os_tsk.new = p_new; //需要切换执行的任务
p_new->state = RUNNING;
DBG_TASK_SWITCH(p_new->task_id);
}
rt_dispatch
这个函数是主动申请一次任务调度,当然,必须是高优先级的任务才会立马执行,低优先级的还是继续等待:
void rt_dispatch (P_TCB next_TCB) {
/* Dispatch next task if any identified or dispatch highest ready task */
/* "next_TCB" identifies a task to run or has value NULL (=no next task) */
if (next_TCB == NULL) {//如果不指定任务,那就从就绪任务里拿出最先执行的任务
/* Running task was blocked: continue with highest ready task */
next_TCB = rt_get_first (&os_rdy);
rt_switch_req (next_TCB);//切换任务
}
else {
/* Check which task continues */
if (next_TCB->prio > os_tsk.run->prio) {//如果指定的任务优先级比现在运行的还高,那就立马执行
/* preempt running task */
rt_put_rdy_first (os_tsk.run);//把当前运行的任务放入就绪任务链表的头部
os_tsk.run->state = READY;
rt_switch_req (next_TCB);//切换任务
}
else {
/* put next task into ready list, no task switch takes place */
next_TCB->state = READY;
rt_put_prio (&os_rdy, next_TCB);//优先级比当前运行的任务低,只能按优先级在就绪任务里等待
}
}
}
rt_block
任务可以自己暂停自己,这函数就是这个功能:
void rt_block (U16 timeout, U8 block_state) {
/* Block running task and choose next ready task. */
/* "timeout" sets a time-out value or is 0xffff (=no time-out). */
/* "block_state" defines the appropriate task state */
P_TCB next_TCB;
if (timeout) {
if (timeout < 0xffff) {//如果需要延时一段时间执行,那这里必须要小于0xffff,不然不放入延时任务链表。
rt_put_dly (os_tsk.run, timeout);
}
os_tsk.run->state = block_state;
next_TCB = rt_get_first (&os_rdy);//取出最高优先级任务来执行
rt_switch_req (next_TCB);
}
}
有人会问,万一时间是0xffff,那就不加入延时链表,有从就绪任务链表里取出新任务,那当前任务不就丢掉了吗(就绪任务链表和延时任务链表里都没有这个任务了)?
其实还有一个数据结构os_active_TCB
,这个数组里放置了每个任务的P_TCB,凭借任务ID就可以随时获取到任务。
rt_tsk_pass
因为RTX支持同级任务轮询处理,因此假如A,B任务同级,假定时间片是10ms,并且A任务在第5ms就执行完成了,那就可以提前结束自己的时间片轮,提高CPU使用率,这个函数就是如此:
void rt_tsk_pass (void) {
/* Allow tasks of same priority level to run cooperatively.*/
P_TCB p_new;
p_new = rt_get_same_rdy_prio();//获取同级的任务,假如没有同级的任务,那就不起作用了,因为你虽然想切换,但你任务又没有退出,优先级又高,想让CPU都让不了。
if (p_new != NULL) {
rt_put_prio ((P_XCB)&os_rdy, os_tsk.run);
os_tsk.run->state = READY;
rt_switch_req (p_new);
}
}
rt_tsk_self
获取当前任务的id:
OS_TID rt_tsk_self (void) {
/* Return own task identifier value. */
if (os_tsk.run == NULL) {
return (0);
}
return (os_tsk.run->task_id);
}
rt_tsk_prio
任务在创建时会指定一个优先级,在运行时也可以改变,最明显的场景就是在互斥量翻转时使用:
OS_RESULT rt_tsk_prio (OS_TID task_id, U8 new_prio) {
/* Change execution priority of a task to "new_prio". */
P_TCB p_task;
if (task_id == 0) {//因为任务ID都是从1开始的,因此0表示没有指定任何任务,那就把当前运行任务来处理。
/* Change execution priority of calling task. */
os_tsk.run->prio = new_prio;//新优先级
os_tsk.run->prio_base = new_prio;//新优先级
run:if (rt_rdy_prio() > new_prio) { //#define rt_rdy_prio(void) (os_rdy.p_lnk->prio) 更改了当前运行任务的优先级后,就得立马和就绪任务里优先级最高的做比较
rt_put_prio (&os_rdy, os_tsk.run);//当前任务的优先级比就绪里最高的任务更低了,当然得切换任务了
os_tsk.run->state = READY;
rt_dispatch (NULL);
}
return (OS_R_OK);
}
/* Find the task in the "os_active_TCB" array. */
if (task_id > os_maxtaskrun || os_active_TCB[task_id-1] == NULL) {//非法ID 直接返回OS_R_NOK
/* Task with "task_id" not found or not started. */
return (OS_R_NOK);
}
p_task = os_active_TCB[task_id-1];//有效的任务ID,获取任务的T_PCB控制块
p_task->prio = new_prio;
p_task->prio_base = new_prio;
if (p_task == os_tsk.run) {//如果指定的任务恰好是正在运行的任务,那么直接跳转到上面的run标签执行。
goto run;
}
rt_resort_prio (p_task);//如果不是正在执行的任务,那就要对就绪任务链表重新排序了,毕竟优先级改变了嘛
if (p_task->state == READY) {//如果指定的任务已经处在就绪状态,那就得立马从就绪任务链表里找出最高优先级任务来执行,因为万一指定的任务优先级最高呢!
/* Task enqueued in a ready list. */
p_task = rt_get_first (&os_rdy);
rt_dispatch (p_task);
}
return (OS_R_OK);
}
rt_tsk_create
接下来就是任务的创建函数了,有了上面的分析,也很简单了。
但这里有个优先级范围的定义,需要说明下,RTX最多支持0-255个优先级, 其中优先级0用于空闲任务,如果用户将这个参数设置为0的话,RTX系统会将其更改为1。优先级255被保留用于最重要的任务。 因此用户可以使用的优先级范围为1-254。
OS_TID rt_tsk_create (FUNCP task, U32 prio_stksz, void *stk, void *argv) {
/* Start a new task declared with "task". */
P_TCB task_context;
U32 i;
/* Priority 0 is reserved for idle task! */
if ((prio_stksz & 0xFF) == 0) {//如果优先级为0,变为1
prio_stksz += 1;
}
task_context = rt_alloc_box (mp_tcb);//根据```RTOS-RTX分析系列之格子内存```,这里动态分配一个任务的控制块。
if (task_context == NULL) {//如果空间不够,创建任务失败
return (0);
}
/* If "size != 0" use a private user provided stack. */
task_context->stack = stk;//用户自己分配栈空间的地址
task_context->priv_stack = prio_stksz >> 8;//prio_stksz的低8位位优先级,高24位位用户自定义栈的空间大小
/* Pass parameter 'argv' to 'rt_init_context' */
task_context->msg = argv;//传递给任务的参数
/* For 'size == 0' system allocates the user stack from the memory pool. */
rt_init_context (task_context, prio_stksz & 0xFF, task);//初始化一下P_TCB结构体
/* Find a free entry in 'os_active_TCB' table. */
i = rt_get_TID ();//获取一个有效的ID号。
os_active_TCB[i-1] = task_context;//在os_active_TCB数组中标记一下
task_context->task_id = i;//赋值ID号
DBG_TASK_NOTIFY(task_context, __TRUE);
rt_dispatch (task_context);//手动申请一次调度
return ((OS_TID)i);
}
rt_tsk_delete
包括删除当前任务和删除指定任务两个分支,这里注释了删除当前任务,另一个类似不作解释。
OS_RESULT rt_tsk_delete (OS_TID task_id) {
/* Terminate the task identified with "task_id". */
P_TCB task_context;
P_TCB p_TCB;
P_MUCB p_MCB, p_MCB0;
if (task_id == 0 || task_id == os_tsk.run->task_id) {//因为用户任务是从1开始,为0就是没指定任何任务,那就是当前任务了
/* Terminate itself. */
os_tsk.run->state = INACTIVE;//先把状态设置成非活动
os_tsk.run->tsk_stack = rt_get_PSP ();//获取下栈指针
rt_stk_check ();//检查下栈是否溢出
p_MCB = os_tsk.run->p_mlnk;
while (p_MCB) {//如果该任务持有互斥量,那就得恢复所有被这些互斥量阻塞的任务了。
/* Release mutexes owned by this task */
if (p_MCB->p_lnk) {//判断一互斥量所阻塞的任务链表是否存在
/* A task is waiting for mutex. */
p_TCB = rt_get_first ((P_XCB)p_MCB);//获取被互斥量阻塞的优先级最高的任务
#ifdef __CMSIS_RTOS
rt_ret_val(p_TCB, 0/*osOK*/);//设置被恢复的任务的返回值为0
#else
rt_ret_val(p_TCB, OS_R_MUT);
#endif
rt_rmv_dly (p_TCB);//从阻塞链表移除被恢复的任务
p_TCB->state = READY;//被恢复的任务修改状态
rt_put_prio (&os_rdy, p_TCB);//被恢复的任务加入就绪任务链表
/* A waiting task becomes the owner of this mutex. */
p_MCB0 = p_MCB;
p_MCB->level = 1;
p_MCB->owner = p_TCB;
p_MCB->p_mlnk = p_TCB->p_mlnk;
p_TCB->p_mlnk = p_MCB; //将互斥量的锁交给之前被阻塞优先级最高的任务,类似赛跑的接力棒。
p_MCB = p_MCB0->p_mlnk;
}
else {
p_MCB = p_MCB->p_mlnk;//因为一个任务允许设置多个互斥量,每个互斥量又可以阻塞多个任务,因此有必要循环遍历一下。
}
}
os_active_TCB[os_tsk.run->task_id-1] = NULL;//os_active_TCB素组中清除标记
rt_free_box (mp_stk, os_tsk.run->stack);//释放栈空间
os_tsk.run->stack = NULL;
DBG_TASK_NOTIFY(os_tsk.run, __FALSE);
rt_free_box (mp_tcb, os_tsk.run);//释放P_TCB控制块
os_tsk.run = NULL;
rt_dispatch (NULL);//申请调度
/* The program should never come to this point. */
}
else {//如果指定了一个任务
/* Find the task in the "os_active_TCB" array. */
if (task_id > os_maxtaskrun || os_active_TCB[task_id-1] == NULL) {//判断是否非法的ID
/* Task with "task_id" not found or not started. */
return (OS_R_NOK);
}
task_context = os_active_TCB[task_id-1];//获取指定的任务控制块
rt_rmv_list (task_context);//从就绪任务链表移除该任务
rt_rmv_dly (task_context);//从延时任务链表移除该任务
p_MCB = task_context->p_mlnk;
while (p_MCB) {
/* Release mutexes owned by this task */
if (p_MCB->p_lnk) {
/* A task is waiting for mutex. */
p_TCB = rt_get_first ((P_XCB)p_MCB);
#ifdef __CMSIS_RTOS
rt_ret_val(p_TCB, 0/*osOK*/);
#else
rt_ret_val(p_TCB, OS_R_MUT);
#endif
rt_rmv_dly (p_TCB);
p_TCB->state = READY;
rt_put_prio (&os_rdy, p_TCB);
/* A waiting task becomes the owner of this mutex. */
p_MCB0 = p_MCB;
p_MCB->level = 1;
p_MCB->owner = p_TCB;
p_MCB->p_mlnk = p_TCB->p_mlnk;
p_TCB->p_mlnk = p_MCB;
p_MCB = p_MCB0->p_mlnk;
}
else {
p_MCB = p_MCB->p_mlnk;
}
}
os_active_TCB[task_id-1] = NULL;
rt_free_box (mp_stk, task_context->stack);
task_context->stack = NULL;
DBG_TASK_NOTIFY(task_context, __FALSE);
rt_free_box (mp_tcb, task_context);
if (rt_rdy_prio() > os_tsk.run->prio) {
/* Ready task has higher priority than running task. */
os_tsk.run->state = READY;
rt_put_prio (&os_rdy, os_tsk.run);
rt_dispatch (NULL);
}
}
return (OS_R_OK);
}