(原创)RTOS-RTX分析系列之其它

2017/07/26 RTX

简介

至此RTOS-RTX分析系列主要部分基本介绍完了,还剩些边边角角的就在这里罗列下。

rt_stk_check

RTX的栈溢出检测原理很简单,有两个办法结合使用:

  1. 在边界位置放置一个魔数,一旦这个魔数被改变了,那就任务越界了。
  2. 比较栈指针的位置,看是否超出范围。

单单判断魔数就怕极端情况,刚好有栈的内容等于魔数,而栈又越界了,这就出问题。

单单判断栈指针也有问题,加入栈曾今越界过,但现在又正常了,那判断成没有越界就也有问题了。

因此两个结合起来判断,虽然也会出现错检的情况:加入一个任务的栈曾经越界,现在又正常,然后刚好在魔数的位置曾经改动的值又恰好等于魔数,那就有问题了。

只不过这两种情况同时出现的概率太低了,按概率论来说,太小概率的事件可以认定为不会发生的事件。

#define MAGIC_WORD      0xE25A2EA5

__weak void rt_stk_check (void) {
  /* Check for stack overflow. */
  if ((os_tsk.run->tsk_stack < (U32)os_tsk.run->stack) || 
      (os_tsk.run->stack[0] != MAGIC_WORD)) {
    os_error (OS_ERR_STK_OVF);
  }
}

轮询robin算法

RTX支持同优先级任务的轮询执行,执行的时间片段可以在配置里修改:

//   <o>Round-Robin Timeout [ticks] <1-1000>
//   <i> Defines how long a thread will execute before a thread switch.
//   <i> Default: 5
#ifndef OS_ROBINTOUT
 #define OS_ROBINTOUT   5
#endif

其实这算法非常简单,先来看看它的结构体:

typedef struct OS_ROBIN {         /* Round Robin Control                     */
  P_TCB  task;                    /* Round Robin task  当前robin持有的任务 */
  U16    time;                    /* Round Robin switch time  指定时间来执行切换*/
  U16    tout;                    /* Round Robin timeout  时间片段的值*/
} *P_ROBIN;

rt_init_robin

__weak void rt_init_robin (void) {
  /* Initialize Round Robin variables. */
  os_robin.task = NULL;
  os_robin.tout = (U16)os_rrobin; //初始化下时间片段的值
}

rt_chk_robin

每次系统滴答中检测一次:

__weak void rt_chk_robin (void) {
  /* Check if Round Robin timeout expired and switch to the next ready task.*/
  P_TCB p_new;

  if (os_robin.task != os_rdy.p_lnk) {//结合下面```rt_systick```的代码来看,os_rdy.p_lnk其实就是当前运行的任务,因为robin轮询仅限在同优先级,因此只要任务在不停的切换,robin轮询就得不停的复位初始化。
    /* New task was suspended, reset Round Robin timeout. */
    os_robin.task = os_rdy.p_lnk;//初始化为对新任务的统计
    os_robin.time = (U16)os_time + os_robin.tout - 1;//计算新任务最多执行的时间
  }
  if (os_robin.time == (U16)os_time) {//如果任务一直在执行,而且执行完一个时间片段
    /* Round Robin timeout has expired, swap Robin tasks. */
    os_robin.task = NULL;
    p_new = rt_get_first (&os_rdy);//就得放置到就绪任务中去,如果有同优先级任务的话,就会放置在同优先级任务的后面,这样就会形成一个循环,从而轮询执行。
    rt_put_prio ((P_XCB)&os_rdy, p_new);//当然如果没有同优先级的任务,而且优先级又最高,那么及时放进就绪任务链表,在经过```next = rt_get_first (&os_rdy);```后也会重新取出来。
  }
}

rt_systick

系统的滴答时钟,不应该太快,反则mcu负载太高,一般设置1ms中断一次:

void rt_systick (void) {
  /* Check for system clock update, suspend running task. */
  P_TCB next;

  os_tsk.run->state = READY;
  rt_put_rdy_first (os_tsk.run);//不管三七二十一,先把当前任务扔进就绪任务链表。

  /* Check Round Robin timeout. */
  rt_chk_robin ();//执行以下robin算法,找出应该执行的任务

  /* Update delays. */
  os_time++;
  rt_dec_dly ();//延迟任务链表计数,而能有任务等待超时,就得唤醒,放入就绪任务链表。

  /* Check the user timers. */
#ifdef __CMSIS_RTOS
  sysTimerTick();//timer定时器管理,和在线程中使用osDelay效果差不多,只不过使用定时器不需要再开启单独的线程,因为有一个osTimerThread的线程统一执行所有的定时器,而且可以在配置中修改。
#else
  rt_tmr_tick ();
#endif

  /* Switch back to highest ready task */
  next = rt_get_first (&os_rdy);
  rt_switch_req (next);
}

rt_pop_req

在之前信号量,邮箱中经常有带isr_前缀的函数,解释是说延迟到PendSV_Handler中执行,其实就是临时放进一个循环数组:

void rt_pop_req (void) {
  /* Process an ISR post service requests. */
  struct OS_XCB *p_CB;
  P_TCB next;
  U32  idx;

  os_tsk.run->state = READY;
  rt_put_rdy_first (os_tsk.run);

  idx = os_psq->last;
  while (os_psq->count) {//如果数组里还有执行请求,那就全部执行完
    p_CB = os_psq->q[idx].id;
    if (p_CB->cb_type == TCB) {//如果是对事件的操作
      /* Is of TCB type */
      rt_evt_psh ((P_TCB)p_CB, (U16)os_psq->q[idx].arg);
    }
    else if (p_CB->cb_type == MCB) {//如果是对邮箱操作
      /* Is of MCB type */
      rt_mbx_psh ((P_MCB)p_CB, (void *)os_psq->q[idx].arg);
    }
    else {//如果是对信号量操作
      /* Must be of SCB type */
      rt_sem_psh ((P_SCB)p_CB);
    }
    if (++idx == os_psq->size) idx = 0;
    rt_dec (&os_psq->count);
  }
  os_psq->last = idx;

  next = rt_get_first (&os_rdy);
  rt_switch_req (next);
}

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

站内搜索

    撩我备注-博客

    joinee

    目录结构