(原创)RTOS-RTX分析系列之任务

2017/07/22 RTX

简介

这里就要开始讲解最重要的一节:任务,毕竟这是整个系统的核心所在。

关键结构

看注释就可以了:

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);
}

知识共享许可协议
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

站内搜索

    撩我备注-博客

    joinee

    目录结构