简介
至此RTOS-RTX分析系列主要部分基本介绍完了,还剩些边边角角的就在这里罗列下。
rt_stk_check
RTX的栈溢出检测原理很简单,有两个办法结合使用:
- 在边界位置放置一个魔数,一旦这个魔数被改变了,那就任务越界了。
- 比较栈指针的位置,看是否超出范围。
单单判断魔数就怕极端情况,刚好有栈的内容等于魔数,而栈又越界了,这就出问题。
单单判断栈指针也有问题,加入栈曾今越界过,但现在又正常了,那判断成没有越界就也有问题了。
因此两个结合起来判断,虽然也会出现错检的情况:加入一个任务的栈曾经越界,现在又正常,然后刚好在魔数的位置曾经改动的值又恰好等于魔数,那就有问题了。
只不过这两种情况同时出现的概率太低了,按概率论来说,太小概率的事件可以认定为不会发生的事件。
#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);
}