这是μ/C OS III
任务调度的第一篇文章:调度过程和调度点。基于Cortex-M
系列的处理器,从最简单的创建任务开始,分析μC/OS III
的任务调度过程。包括上下文切换的详细过程、任务的栈分配详情、引起调度的调度点等内容。
一. 从哪开始
先来看一段简单的代码:
#define TASK_PRIO (10)
#define TASK_STACK_ZIZE (128)
static CPU_STK TaskStk1[TASK_STACK_ZIZE];
static OS_TCB TaskTCB1;
static CPU_STK TaskStk2[TASK_STACK_ZIZE];
static OS_TCB TaskTCB2;
static void TaskStart1(void *p_arg)
{
OS_ERR err;
while(1)
{
LED_ON();
OSTimeDly((OS_TICK)10, OS_OPT_TIME_DLY, &err);//延时10个tick
LED_OFF();
OSTimeDly((OS_TICK)10, OS_OPT_TIME_DLY, &err);
}
}
static void TaskStart2(void *p_arg)
{
OS_ERR err;
while(1)
{
printf("TaskStart2 looping! \r\n ");
OSTimeDly((OS_TICK)10, OS_OPT_TIME_DLY, &err);//延时10个tick
}
}
int main()
{
OS_ERR err;
CPU_Init();
BSP_Init();
SysTick_Init();
OSInit(&err);
OSTaskCreate((OS_TCB *) &TaskTCB1, //TCB
(CPU_CHAR *) "Task1", //任务的名字
(OS_TASK_PTR ) TaskStart1, //任务处理函数
(void *) 0, //传参
(OS_PRIO ) TASK_PRIO, //任务优先级
(CPU_STK *) &TaskStk1[0], //任务的栈的底部
(CPU_STK_SIZE) TASK_STACK_ZIZE / 10, //栈的watermark
(CPU_STK_SIZE) TASK_STACK_ZIZE, //栈的总大小
(OS_MSG_QTY ) 0u, //这个任务可以接收的消息的最大个数
(OS_TICK ) 0u, //在round robin中有用,时间片的大小
(void *) 0, //TCB扩展内存,用于用户自定义的数据
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),//选项
(OS_ERR *) &err);
OSTaskCreate((OS_TCB *) &TaskTCB2,
(CPU_CHAR *) "Task2",
(OS_TASK_PTR ) TaskStart2,
(void *) 0,
(OS_PRIO ) TASK_PRIO,
(CPU_STK *) &TaskStk2[0],
(CPU_STK_SIZE) TASK_STACK_ZIZE / 10,
(CPU_STK_SIZE) TASK_STACK_ZIZE,
(OS_MSG_QTY ) 0u,
(OS_TICK ) 0u,
(void *) 0,
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *) &err);
OSStart(&err);
return 0;
}
上面的任务创建之后,会执行任务处理函数TaskStart1
和TaskStart2
,TaskStart1
不停的亮灭LED,TaskStart2
则不停的打印信息。
二. OSTaskCreate
函数做了什么?
[os_task.c OSTaskCreate函数 片段1]
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)
{
...
CPU_STK *p_sp;
CPU_STK *p_stk_limit;
CPU_SR_ALLOC();
...
OS_TaskInitTCB(p_tcb);
*p_err = OS_ERR_NONE;
/* -------------- CLEAR THE TASK'S STACK -------------- */
if (((opt & OS_OPT_TASK_STK_CHK) != 0u) ||
((opt & OS_OPT_TASK_STK_CLR) != 0u)) {
if ((opt & OS_OPT_TASK_STK_CLR) != 0u) {
p_sp = p_stk_base;
for (i = 0u; i < stk_size; i++) {
*p_sp = 0u;
p_sp++;
}
}
}
在OS_TaskInitTCB
之前是一些入参检查。OS_TaskInitTCB
函数是初始化TCB
里面的各个成员,基本上都设为0
或者DEF_NULL
。把TCB
的优先级设置为OS_CFG_PRIO_MAX
。随后如果opt
中的OS_OPT_TASK_STK_CHK
比特位和OS_OPT_TASK_STK_CLR
比特位设为1,那么就把stack
全部清为0。
[os_task.c OSTaskCreate函数 片段2]
#if (CPU_CFG_STK_GROWTH == CPU_STK_GROWTH_HI_TO_LO)
p_stk_limit = p_stk_base + stk_limit;
#else
p_stk_limit = p_stk_base + (stk_size - 1u) - stk_limit;
#endif
根据栈的不同的增长方向来计算p_stk_limit
,ARM
对两种增长方式都支持,但是由于编译器的原因一般都是选择CPU_STK_GROWTH_HI_TO_LO
,从高地址向低地址增长。
[os_task.c OSTaskCreate函数 片段3]
p_sp = OSTaskStkInit(p_task, p_arg, p_stk_base, p_stk_limit, stk_size, opt);
#if (CPU_CFG_STK_GROWTH == CPU_STK_GROWTH_HI_TO_LO)
if (p_sp < p_stk_base) {
*p_err = OS_ERR_STAT_STK_SIZE_INVALID;
return;
}
#else
if (p_sp > p_stk_base + stk_size) {
*p_err = OS_ERR_STAT_STK_SIZE_INVALID;
return;
}
#endif
再来看OSTaskStkInit
这个函数:
[os_cpu_c.c OSTaskStkInit函数]
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK *p_stk_limit,
CPU_STK_SIZE stk_size,
OS_OPT opt)
{
CPU_STK *p_stk;
/* 'opt' is not used, prevent warning */
(void)opt;
/* Load stack pointer */
p_stk = &p_stk_base[stk_size];
/* Align the stack to 8-bytes. */
p_stk = (CPU_STK *)((CPU_STK)(p_stk) & 0xFFFFFFF8u);
/* Registers stacked as if auto-saved on exception */
/* FPU auto-saved registers. */
#if (OS_CPU_ARM_FP_EN > 0u)
--p_stk;
*(--p_stk) = (CPU_STK)0x02000000u; /* FPSCR */
/* Initialize S0-S15 floating point registers */
*(--p_stk) = (CPU_STK)0x41700000u; /* S15 */
*(--p_stk) = (CPU_STK)0x41600000u; /* S14 */
*(--p_stk) = (CPU_STK)0x41500000u; /* S13 */
*(--p_stk) = (CPU_STK)0x41400000u; /* S12 */
*(--p_stk) = (CPU_STK)0x41300000u; /* S11 */
*(--p_stk) = (CPU_STK)0x41200000u; /* S10 */
*(--p_stk) = (CPU_STK)0x41100000u; /* S9 */
*(--p_stk) = (CPU_STK)0x41000000u; /* S8 */
*(--p_stk) = (CPU_STK)0x40E00000u; /* S7 */
*(--p_stk) = (CPU_STK)0x40C00000u; /* S6 */
*(--p_stk) = (CPU_STK)0x40A00000u; /* S5 */
*(--p_stk) = (CPU_STK)0x40800000u; /* S4 */
*(--p_stk) = (CPU_STK)0x40400000u; /* S3 */
*(--p_stk) = (CPU_STK)0x40000000u; /* S2 */
*(--p_stk) = (CPU_STK)0x3F800000u; /* S1 */
*(--p_stk) = (CPU_STK)0x00000000u; /* S0 */
#endif
*(--p_stk) = (CPU_STK)0x01000000u; /* xPSR */
*(--p_stk) = (CPU_STK)p_task; /* Entry Point */
*(--p_stk) = (CPU_STK)OS_TaskReturn; /* R14 (LR) */
*(--p_stk) = (CPU_STK)0x12121212u; /* R12 */
*(--p_stk) = (CPU_STK)0x03030303u; /* R3 */
*(--p_stk) = (CPU_STK)0x02020202u; /* R2 */
*(--p_stk) = (CPU_STK)p_stk_limit; /* R1 */
*(--p_stk) = (CPU_STK)p_arg; /* R0 : argument */
#if (OS_CPU_ARM_FP_EN > 0u)
/* R14: EXEC_RETURN; See Note 5 */
*(--p_stk) = (CPU_STK)0xFFFFFFEDuL;
#else
/* R14: EXEC_RETURN; See Note 5 */
*(--p_stk) = (CPU_STK)0xFFFFFFFDuL;
#endif
/* Remaining registers saved on process stack */
*(--p_stk) = (CPU_STK)0x11111111uL; /* R11 */
*(--p_stk) = (CPU_STK)0x10101010uL; /* R10 */
*(--p_stk) = (CPU_STK)0x09090909uL; /* R9 */
*(--p_stk) = (CPU_STK)0x08080808uL; /* R8 */
*(--p_stk) = (CPU_STK)0x07070707uL; /* R7 */
*(--p_stk) = (CPU_STK)0x06060606uL; /* R6 */
*(--p_stk) = (CPU_STK)0x05050505uL; /* R5 */
*(--p_stk) = (CPU_STK)0x04040404uL; /* R4 */
#if (OS_CPU_ARM_FP_EN > 0u)
/* Initialize S16-S31 floating point registers */
*(--p_stk) = (CPU_STK)0x41F80000u; /* S31 */
*(--p_stk) = (CPU_STK)0x41F00000u; /* S30 */
*(--p_stk) = (CPU_STK)0x41E80000u; /* S29 */
*(--p_stk) = (CPU_STK)0x41E00000u; /* S28 */
*(--p_stk) = (CPU_STK)0x41D80000u; /* S27 */
*(--p_stk) = (CPU_STK)0x41D00000u; /* S26 */
*(--p_stk) = (CPU_STK)0x41C80000u; /* S25 */
*(--p_stk) = (CPU_STK)0x41C00000u; /* S24 */
*(--p_stk) = (CPU_STK)0x41B80000u; /* S23 */
*(--p_stk) = (CPU_STK)0x41B00000u; /* S22 */
*(--p_stk) = (CPU_STK)0x41A80000u; /* S21 */
*(--p_stk) = (CPU_STK)0x41A00000u; /* S20 */
*(--p_stk) = (CPU_STK)0x41980000u; /* S19 */
*(--p_stk) = (CPU_STK)0x41900000u; /* S18 */
*(--p_stk) = (CPU_STK)0x41880000u; /* S17 */
*(--p_stk) = (CPU_STK)0x41800000u; /* S16 */
#endif
return (p_stk);
}
其中pTask
是线程的入口函数,一般来说pTask
应该是一个死循环,永远不会退出。
OS_TaskReturn
是一个为了防止pTask
这个函数最终的退出而导致异常:
[os_task.c OS_TaskReturn函数]
void OS_TaskReturn (void)
{
OS_ERR err;
/* Call hook to let user decide on what to do */
OSTaskReturnHook(OSTCBCurPtr);
#if OS_CFG_TASK_DEL_EN > 0u
/* Delete task if it accidentally returns! */
OSTaskDel((OS_TCB *)0, (OS_ERR *)&err);
#else
for (;;) {
OSTimeDly((OS_TICK )OSCfg_TickRate_Hz,
(OS_OPT )OS_OPT_TIME_DLY,
(OS_ERR *)&err);
}
#endif
}
OSTaskStkInit
函数之后,先将栈进行8-bytes
对齐,在线程的栈p_stk_base
数组中,结构如下(假设开启了FPU
):
|===================================================|
| position DWORD | value | Descriptions |
|===================================================|
| Top - 0 | 0x02000000u | FPSCR |
| Top - 1 | 0x41700000u | S15 |
| Top - 2 | 0x41600000u | S14 |
| Top - 3 | 0x41500000u | S12 |
| Top - 4 | 0x41400000u | S11 |
| Top - 5 | 0x41300000u | S10 |
| Top - 6 | 0x41200000u | S9 |
| Top - 7 | 0x41100000u | S8 |
| Top - 8 | 0x41000000u | S7 |
| Top - 9 | 0x40E00000u | S6 |
| Top - 10 | 0x40C00000u | S5 |
| Top - 11 | 0x40A00000u | S4 |
| Top - 12 | 0x40800000u | S3 |
| Top - 13 | 0x40400000u | S2 |
| Top - 14 | 0x3F800000u | S1 |
| Top - 15 | 0x00000000u | S0 |
|---------------------------------------------------|
| Top - 16 | 0x01000000u | xPSR |
| Top - 17 | p_task | Entry Pointer |
| Top - 18 | OS_TaskReturn | R14 (LR) |
| Top - 19 | 0x12121212u | R12 |
| Top - 20 | 0x03030303u | R3 |
| Top - 21 | 0x02020202u | R2 |
| Top - 22 | p_stk_limit | R1 |
| Top - 23 | p_arg | R0 : argument |
| Top - 24 | 0xFFFFFFEDu | R14: EXEC_RETURN |
| Top - 25 | 0x11111111u | R11 |
| Top - 26 | 0x10101010u | R10 |
| Top - 27 | 0x09090909u | R9 |
| Top - 28 | 0x08080808u | R8 |
| Top - 29 | 0x07070707u | R7 |
| Top - 30 | 0x06060606u | R6 |
| Top - 31 | 0x05050505u | R5 |
| Top - 32 | 0x04040404u | R4 |
|---------------------------------------------------|
| Top - 34 | 0x41F80000u | S31 |
| Top - 35 | 0x41F00000u | S30 |
| Top - 36 | 0x41E80000u | S29 |
| Top - 37 | 0x41E00000u | S28 |
| Top - 38 | 0x41D80000u | S27 |
| Top - 39 | 0x41D00000u | S26