这是μ/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 |
| Top - 40 | 0x41C80000u | S25 |
| Top - 41 | 0x41C00000u | S24 |
| Top - 42 | 0x41B80000u | S23 |
| Top - 43 | 0x41B00000u | S22 |
| Top - 44 | 0x41A80000u | S21 |
| Top - 45 | 0x41A00000u | S20 |
| Top - 46 | 0x41980000u | S19 |
| Top - 47 | 0x41900000u | S18 |
| Top - 48 | 0x41880000u | S17 |
| Top - 49 | 0x41800000u | S16 |
|---------------------------------------------------| <----- p_task->StkPtr
| ... | ... | ... |
继续看OSTaskCreate
函数 :
[os_task.c OSTaskCreate函数 片段4]
/* -------------- INITIALIZE THE TCB FIELDS ------------- */
#if OS_CFG_DBG_EN > 0u
p_tcb->TaskEntryAddr = p_task; /* Save task entry point address */
p_tcb->TaskEntryArg = p_arg; /* Save task entry argument */
#endif
#if OS_CFG_DBG_EN > 0u
p_tcb->NamePtr = p_name; /* Save task name */
#else
(void)&p_name;
#endif
p_tcb->Prio = prio; /* Save the task's priority */
#if OS_CFG_MUTEX_EN > 0u
p_tcb->BasePrio = prio; /* Set the base priority */
#endif
p_tcb->StkPtr = p_sp; /* Save the new top-of-stack pointer */
#if ((OS_CFG_DBG_EN > 0u) || (OS_CFG_STAT_TASK_STK_CHK_EN > 0u))
p_tcb->StkLimitPtr = p_stk_limit; /* Save the stack limit pointer */
#endif
上面的代码片段,如果有需要就保存线程的入口函数和名字,然后Prio
和BasePrio
保存线程的优先级,StkPtr
保存新的栈顶。StkLimitPtr
字段保存栈限制指针。
[os_task.c OSTaskCreate函数 片段5]
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
/* Save the #ticks for time slice (0 means not sliced) */
p_tcb->TimeQuanta = time_quanta;
if (time_quanta == (OS_TICK)0) {
p_tcb->TimeQuantaCtr = OSSchedRoundRobinDfltTimeQuanta;
} else {
p_tcb->TimeQuantaCtr = time_quanta;
}
#else
(void)&time_quanta;
#endif
上面代码片段是初始化Round-Robin
调度算法的内容,如果OS_CFG_SCHED_ROUND_ROBIN_EN
宏打开的话。
[os_task.c OSTaskCreate函数 片段6]
/* Save pointer to TCB extension */
p_tcb->ExtPtr = p_ext;
#if ((OS_CFG_DBG_EN > 0u) || (OS_CFG_STAT_TASK_STK_CHK_EN > 0u))
/* Save pointer to the base address of the stack */
p_tcb->StkBasePtr = p_stk_base;
/* Save the stack size (in number of CPU_STK elements) */
p_tcb->StkSize = stk_size;
#endif
/* Save task options */
p_tcb->Opt = opt;
将ExtPtr
字段保存为p_ext
,StkBasePrt
字段填写为栈的起始位置。StkSize
字段填写为栈的大小。Opt
字段填写为opt
。
[os_task.c OSTaskCreate函数 片段7]
OS_CRITICAL_ENTER();
OS_PrioInsert(p_tcb->Prio);
OS_RdyListInsertTail(p_tcb);
OS_PrioInsert(p_tcb->Prio)
是将优先级插入OSPrioTbl
数组中:
[os_prio.c OS_PrioInsert函数]
void OS_PrioInsert (OS_PRIO prio)
{
CPU_DATA bit;
CPU_DATA bit_nbr;
OS_PRIO ix;
#if (OS_CFG_PRIO_MAX > DEF_INT_CPU_NBR_BITS)
ix = prio / (OS_PRIO)DEF_INT_CPU_NBR_BITS;
bit_nbr = (CPU_DATA)prio & (DEF_INT_CPU_NBR_BITS - 1u);
#else
ix = 0u;
bit_nbr = prio;
#endif
bit = 1u;
bit <<= (DEF_INT_CPU_NBR_BITS - 1u) - bit_nbr;
OSPrioTbl[ix] |= bit;
}
其中OSPrioTbl
是存放各个优先级的位图数据结构,OS_PrioInsert
这个函数就是把OSPrioTbl
数组里面prio
相对应的比特位置位。
再来看OS_RdyListInsertTail(p_tcb)
:
[os_core.c OS_RdyListInsertTail函数]
void OS_RdyListInsertTail (OS_TCB *p_tcb)
{
OS_RDY_LIST *p_rdy_list;
OS_TCB *p_tcb2;
p_rdy_list = &OSRdyList[p_tcb->Prio];
/* CASE 0: Insert when there are no entries */
if (p_rdy_list->NbrEntries == (OS_OBJ_QTY)0) {
/* This is the first entry */
p_rdy_list->NbrEntries = (OS_OBJ_QTY)1;
/* No other OS_TCBs in the list */
p_tcb->NextPtr = (OS_TCB *)0;
/* Both list pointers point to this OS_TCB */
p_tcb->PrevPtr = (OS_TCB *)0;
p_rdy_list->HeadPtr = p_tcb;
p_rdy_list->TailPtr = p_tcb;
/* CASE 1: Insert AFTER the current tail of list */
} else {
/* One more OS_TCB in the list */
p_rdy_list->NbrEntries++;
/* Adjust new OS_TCBs links */
p_tcb->NextPtr = (OS_TCB *)0;
p_tcb2 = p_rdy_list->TailPtr;
p_tcb->PrevPtr = p_tcb2;
/* Adjust old tail of list's links */
p_tcb2->NextPtr = p_tcb;
p_rdy_list->TailPtr = p_tcb;
}
}
其中OS_RDY_LIST
结构体和OSRdyList
的定义如下:
[os.h]
struct os_rdy_list {
/* Pointer to task that will run at selected priority */
OS_TCB *HeadPtr;
/* Pointer to last task at selected priority */
OS_TCB *TailPtr;
/* Number of entries at selected priority */
OS_OBJ_QTY NbrEntries;
};
typedef struct os_rdy_list OS_RDY_LIST;
OS_EXT OS_RDY_LIST OSRdyList[OS_CFG_PRIO_MAX];
OSRdyList
为每一个优先级保存一个OS_RDY_LIST
结构体。每次执行OS_RdyListInsertTail
这个函数的时候,就把p_tcb
添加到指定优先级的OS_RDY_LIST
的尾部,并增加这个OS_RDY_LIST
的NbrEntries
。
继续OSTaskCreate
函数:
[os_task.c OSTaskCreate函数 片段8]
#if OS_CFG_DBG_EN > 0u
OS_TaskDbgListAdd(p_tcb);
#endif
/* Increment the #tasks counter */
OSTaskQty++;
/* Return if multitasking has not started */
if (OSRunning != OS_STATE_OS_RUNNING) {
OS_CRITICAL_EXIT();
return;
}
OS_CRITICAL_EXIT_NO_SCHED();
OSSched();
上面的代码总结为:
OS_TaskDbgListAdd(p_tcb)
调试相关的代码,暂不深入。OSTaskQty++
记录当前任务的个数。- 如果
OS
不处于OS_STATE_OS_RUNNING
运行状态,则调用OS_CRITICAL_EXIT
并退出函数。 - 如果
OS
已经处于OS_STATE_OS_RUNNING
状态,则继续向下调用OSSched
函数。OSSched
函数会引发一次调度,如果OS
已经跑起来的话,每增加一个新的任务都会引起一次调度,这个后续会详解。
从上面的分析可以直到,OSTaskStkInit
会初始化TCB
的栈,更新全局的优先级位图数据结构,以及位图对应的任务链表,以及其他相关数据。
三. OSStart
函数做了什么?
void OSStart (OS_ERR *p_err)
{
OS_OBJ_QTY kernel_task_cnt;
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
if (OSInitialized != DEF_TRUE) {
*p_err = OS_ERR_OS_NOT_INIT;
return;
}
/* Calculate the number of kernel tasks */
kernel_task_cnt = 0u;
#if (OS_CFG_STAT_TASK_EN == DEF_ENABLED)
kernel_task_cnt++;
#endif
#if (OS_CFG_TASK_TICK_EN == DEF_ENABLED)
kernel_task_cnt++;
#endif
#if (OS_CFG_TMR_EN == DEF_ENABLED)
kernel_task_cnt++;
#endif
#if (OS_CFG_TASK_IDLE_EN == DEF_ENABLED)
kernel_task_cnt++;
#endif
/* No application task created */
if (OSTaskQty <= kernel_task_cnt) {
*p_err = OS_ERR_OS_NO_APP_TASK;
return;
}
if (OSRunning == OS_STATE_OS_STOPPED) {
/* Find the highest priority */
OSPrioHighRdy = OS_PrioGetHighest();
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
OSTCBCurPtr = OSTCBHighRdyPtr;
OSRunning = OS_STATE_OS_RUNNING;
/* Execute target specific code to start task */
OSStartHighRdy();
/* OSStart() is not supposed to return */
*p_err = OS_ERR_FATAL_RETURN;
} else {
/* OS is already running */
*p_err = OS_ERR_OS_RUNNING;
}
}
OSPrioHighRdy = OS_PrioGetHighest();
这条语句是找到OSPrioTbl
位图里面的最高优先级是多少,放到全局变量OSPrioHighRdy
里。OSPrioCur = OSPrioHighRdy;
把OSPrioHighRdy
放入OSPrioCur
(现在执行正在执行的任务的优先级)。OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
找到最高优先级的第一个任务实体,放到全局变量OSTCBHighRdyPtr
里。OSTCBCurPtr
表示当前执行的任务实体指针,设置为OSTCBHighRdyPtr
。OSRunning = OS_STATE_OS_RUNNING
把任务设置为运行状态。- 随后开始执行
OSStartHighRdy
这个函数。
OSStartHighRdy
函数是开始运行优先级最高的任务,其代码如下:
[os_cpu_a.S OSStartHighRdy函数]
.thumb_func
OSStartHighRdy:
CPSID I @ Prevent interruption during context switch
MOVW R0, #:lower16:NVIC_SYSPRI14 @ Set the PendSV exception priority
MOVT R0, #:upper16:NVIC_SYSPRI14
MOVW R1, #:lower16:NVIC_PENDSV_PRI
MOVT R1, #:upper16:NVIC_PENDSV_PRI
STRB R1, [R0]
MOVS R0, #0 @ Set the PSP to 0 for initial context switch call
MSR PSP, R0
MOVW R0, #:lower16:OS_CPU_ExceptStkBase @ Initialize the MSP to the OS_CPU_ExceptStkBase
MOVT R0, #:upper16:OS_CPU_ExceptStkBase
LDR R1, [R0]
MSR MSP, R1
BL OSTaskSwHook @ Call OSTaskSwHook() for FPU Push & Pop
MOVW R0, #:lower16:OSPrioCur @ OSPrioCur = OSPrioHighRdy;
MOVT R0, #:upper16:OSPrioCur
MOVW R1, #:lower16:OSPrioHighRdy
MOVT R1, #:upper16:OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
MOVW R0, #:lower16:OSTCBCurPtr @ OSTCBCurPtr = OSTCBHighRdyPtr;
MOVT R0, #:upper16:OSTCBCurPtr
MOVW R1, #:lower16:OSTCBHighRdyPtr
MOVT R1, #:upper16:OSTCBHighRdyPtr
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2] @ R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
MSR PSP, R0 @ Load PSP with new process SP
MRS R0, CONTROL
ORR R0, R0, #2
MSR CONTROL, R0
ISB @ Sync instruction stream
LDMFD SP!, {R4-R11, LR} @ Restore r4-11 from new process stack
LDMFD SP!, {R0-R3} @ Restore r0, r3
LDMFD SP!, {R12, LR} @ Load R12 and LR
LDMFD SP!, {R1, R2} @ Load PC and discard xPSR
CPSIE I
BX R1
需要注意,struct os_tcb
的第一个字段就是StkPtr
,所以一个TCB
的地址就是这个TCB
中StkPtr
字段的地址。
OSStartHighRdy
这个函数是在的流程可以总结如下:
- 关中断。
- 把
NVIC_SYSPRI14
寄存器的值设为NVIC_PENDSV_PRI
(0xFF
),即把PendSV
的中断优先级设置为最低的0xFF
。 - 把主栈指针
MSP
寄存器设置为OS_CPU_ExceptStkBase
。 跳转到OSTaskSwHook
函数:
[os_cpu_c.c OSTaskSwHook函数]
void OSTaskSwHook (void)
{
...
#if (OS_CPU_ARM_FP_EN > 0u)
/* Push the FP registers of the current task. */
OS_CPU_FP_Reg_Push(OSTCBCurPtr->StkPtr);
#endif
...
#if (OS_CPU_ARM_FP_EN > 0u)
/* Pop the FP registers of the highest ready task. */
OS_CPU_FP_Reg_Pop(OSTCBHighRdyPtr->StkPtr);
#endif
}
[os_cpu_a.S OS_CPU_FP_Reg_Push函数]
.thumb_func
OS_CPU_FP_Reg_Push:
MRS R1, PSP @ PSP is process stack pointer
CBZ R1, OS_CPU_FP_nosave @ Skip FP register save the first time
VSTMDB R0!, {S16-S31}
LDR R1, =OSTCBCurPtr
LDR R2, [R1]
STR R0, [R2]
OS_CPU_FP_nosave:
BX LR
[os_cpu_a.S OS_CPU_FP_Reg_Pop函数]
OS_CPU_FP_Reg_Pop:
VLDMIA R0!, {S16-S31}
LDR R1, =OSTCBHighRdyPtr
LDR R2, [R1]
STR R0, [R2]
BX LR
OS_CPU_FP_Reg_Push
这个函数就是把S16
~ S31
压入OSTCBCurPtr->StkPtr
(当前正在执行的任务的栈)中,但是处理器复位的时候,PSP
会被赋值为0,且线程模式使用的是MSP
,所以在没有设置使用PSP
的时候,PSP
一直是0,所以在这里都会跳过这个压栈过程,直到设置了PSP
为止。OS_CPU_FP_Reg_Pop
这个函数就是把S16
~ S31
从OSTCBHighRdyPtr->StkPtr
(当前最高优先级的任务的栈)中恢复出来。这之后,结合上面的OSTaskStkInit
函数,OSTCBHighRdyPtr
的栈空间分布如下:
|===================================================|
| 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 |
|---------------------------------------------------| <----- OSTCBHighRdyPtr->StkPtr
| ... | ... | ... |
把OSPrioHighRdy
(所有任务中最高的任务优先级)的值赋值给OSPrioCur
(当前执行的任务的优先级)。
- 把
OSTCBHighRdyPtr
(最高优先级的任务实体的指针)的值赋值给OSTCBCurPtr
(当前执行的任务指针)。 - 把
OSTCBHighRdyPtr->StkPtr
的值赋值给线程堆栈指针寄存器PSP
。 - 设置
CONTROL
寄存器的第二个比特位置为1,第二个比特位为SPSEL
,为1是线程模式使用PSP
作为栈指针。并加上一个ISB
使设置对战指针生效。下面的指令都是把PSP
当成栈指针在使用,即OSTCBHighRdyPtr->StkPtr
。 之后,把OSTCBHighRdyPtr->StkPtr
里面的数据依次递减读出来,按照下面的顺序放到下面的寄存器里:R4
~ R11
、LR
、R0
~ R3
、R12
、LR
、R1
、R2
。恢复到寄存器就是按照出栈的方式进行的,经过这样处理之后,几个有用的寄存器被设置,而剩下的值无关紧要,不去关心。
| register | Descriptions |
|===========================|
| R1 | EntryPointer | <----- 线程执行函数入口
|---------------------------|
| R0 | OS_TaskReturn |
|---------------------------|
| LR | p_arg | <----- 线程执行函数的入参
|---------------------------|
这之后OSTCBHighRdyPtr
的栈的内存分布如下:
|===================================================|
| 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 |
|---------------------------------------------------| <----- OSTCBHighRdyPtr->StkPtr
| ... | ... | ... |
打开中断,跳转到R1
寄存器所指的位置,即线程函数入口处,开始执行第一个任务。
四. 任务调度的流程和时机之SysTick
和PendSV
在CPU
相关的.S
文件中,会找到类似如下的代码,把SysTick
和PendSV
的中断处理函数定向到ucos
自带的处理函数中去。
DCD OS_CPU_PendSVHandler ; PendSV Handler
DCD OS_CPU_SysTickHandler ; SysTick Handler
假设SysTick
设置的触发时间是100ms,那么在每100ms就会触发一次中断进入OS_CPU_SysTickHandler
:
void OS_CPU_SysTickHandler (void)
{
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
/* Tell uC/OS-III that we are starting an ISR */
OSIntEnter();
CPU_CRITICAL_EXIT();
/* Call uC/OS-III's OSTimeTick() */
OSTimeTick();
/* Tell uC/OS-III that we are leaving the ISR */
OSIntExit();
}
先调用OSIntEnter
函数,这个函数主要是增加OSIntNestingCtr
的值:
void OSIntEnter (void)
{
OS_TRACE_ISR_ENTER();
/* Is OS running? */
if (OSRunning != OS_STATE_OS_RUNNING) {
return; /* No */
}
/* Have we nested past 250 levels? */
if (OSIntNestingCtr >= 250u) {
return; /* Yes */
}
/* Increment ISR nesting level */
OSIntNestingCtr++;
}
再调用OSTimeTick
,这个函数主要是对Tick Task
做一次Post
操作,这里不做深入讨论。
随后调用了OSIntExit
函数:
void OSIntExit (void)
{
CPU_SR_ALLOC();
/* Has the OS started? */
if (OSRunning != OS_STATE_OS_RUNNING) {
return;
}
CPU_INT_DIS();
/* Prevent OSIntNestingCtr from wrapping */
if (OSIntNestingCtr == (OS_NESTING_CTR)0) {
CPU_INT_EN();
return;
}
OSIntNestingCtr--;
/* ISRs still nested? */
if (OSIntNestingCtr > (OS_NESTING_CTR)0) {
CPU_INT_EN(); /* Yes */
return;
}
/* Scheduler still locked? */
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) {
CPU_INT_EN(); /* Yes */
return;
}
/* Verify ISR Stack */
#if (OS_CFG_ISR_STK_SIZE > 0u)
#if (OS_CFG_TASK_STK_REDZONE_EN == DEF_ENABLED)
stk_status = OS_TaskStkRedzoneChk(OSCfg_ISRStkBasePtr, OSCfg_ISRStkSize);
if (stk_status != DEF_OK) {
OSRedzoneHitHook(DEF_NULL);
}
#endif
#endif
/* Find highest priority */
OSPrioHighRdy = OS_PrioGetHighest();
#if (OS_CFG_TASK_IDLE_EN == DEF_ENABLED)
/* Get highest priority task ready-to-run */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
/* Current task still the highest priority? */
if (OSTCBHighRdyPtr == OSTCBCurPtr) {
#if (OS_CFG_TASK_STK_REDZONE_EN == DEF_ENABLED)
stk_status = OSTaskStkRedzoneChk(DEF_NULL);
if (stk_status != DEF_OK) {
OSRedzoneHitHook(OSTCBCurPtr);
}
#endif
OS_TRACE_ISR_EXIT();
CPU_INT_EN();
return;
}
#else
/* Are we returning to idle? */
if (OSPrioHighRdy != (OS_CFG_PRIO_MAX - 1u)) {
/* No...get highest priority task ready-to-run */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
/* Current task still the highest priority? */
if (OSTCBHighRdyPtr == OSTCBCurPtr) {
OS_TRACE_ISR_EXIT();
CPU_INT_EN();
return;
}
}
#endif
#if (OS_CFG_TASK_PROFILE_EN == DEF_ENABLED)
/* Inc. # of context switches for this new task */
OSTCBHighRdyPtr->CtxSwCtr++;
#endif
#if ((OS_CFG_TASK_PROFILE_EN == DEF_ENABLED) || (OS_CFG_DBG_EN == DEF_ENABLED))
/* Keep track of the total number of ctx switches */
OSTaskCtxSwCtr++;
#endif
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS_TaskSw();
#endif
OS_TRACE_ISR_EXIT_TO_SCHEDULER();
/* Perform interrupt level ctx switch */
OSIntCtxSw();
CPU_INT_EN();
}
OSIntExit
函数可以总结为如下步骤:
- 判断
OS
是否处于OS_STATE_OS_RUNNING
状态,如果不是,退出。 CPU_INT_DIS
关闭中断。- 判断是否处于中断状态,通过
OSIntNestingCtr
,如果不是,退出。 - 判断是否处于中断嵌套状态,如果是,退出。
- 判断是否锁住了调度,如果是,退出。
- 寻找到任务的最高优先级,放入
OSPrioHighRdy
,寻找最高优先级任务的实体,放入到OSTCBHighRdyPtr
。 - 如果最高优先级的任务就是当前的任务,退出。
- 增加当前最高优先级的调度计数器
CtxSwCtr
,增加全局的调度计数器OSTaskCtxSwCtr
。 调用OSIntCtxSw
函数:
[os_cpu_a.S OSIntCtxSw函数]
OSIntCtxSw:
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
这个函数是触发PENDSV
中断。
CPU_INT_EN
开打中断。因为这时候CPU
本身处于处理模式(SysTick ISR
函数中),打开中断之后由于末尾连锁的机制,即使没有加ISB
指令,CPU
也不会向下执行两条指令,而直接触发PendSV ISR
。
在这之后,触发了PendSV
,加上末尾连锁的机制,不会再返回线程模式而直接进入了PendSV
的ISR
。
进入PendSV ISR
的流程可能有如下的几种:
- 线程模式 →
SysTick ISR
→ PendSV
。 - 线程模式 → 其他更高优先级的
ISR
→ SysTick ISR
→ PendSV
。 - 线程模式 → … →
SysTick ISR
→ … → PendSV
。
不管是那种流程,最开始的都是从线程模式开始,跳到处理模式,然后最后到PendSV
。每一个任务从线程模式跳转到处理模式之前,他们的栈内存分布如下:
|===================================|
| position DWORD | Descriptions |
|===================================|
| Top - 0 | FPSCR |
| Top - 1 | S15 |
| Top - 2 | S14 |
| Top - 3 | S12 |
| Top - 4 | S11 |
| Top - 5 | S10 |
| Top - 6 | S9 |
| Top - 7 | S8 |
| Top - 8 | S7 |
| Top - 9 | S6 |
| Top - 10 | S5 |
| Top - 11 | S4 |
| Top - 12 | S3 |
| Top - 13 | S2 |
| Top - 14 | S1 |
| Top - 15 | S0 |
|-----------------------------------| <----- OSTCBCurPtr->StkPtr
| ... | ... | <----- 每一个任务都会有一些c语言栈变量,
| ... | ... | 以及函数调用的压栈,占据了一些空间。
| ... | ... |
|-----------------------------------| <----- PSP寄存器指向这个位置
| Top - n | ... |
从线程模式跳入到处理模式的时候,Cortex-M
处理器会把一些寄存器用PSP
进行压栈,如果开启了Lazy Stacking
(惰性压着)特性,那么压栈的顺序是xPSR
、PC
、LR
(R14
)、R12
、R3
、R2
、R1
、R0
。此后当前任务的栈内存分布如下:
|===================================|
| position DWORD | Descriptions |
|===================================|
| Top - 0 | FPSCR |
| Top - 1 | S15 |
| Top - 2 | S14 |
| Top - 3 | S12 |
| Top - 4 | S11 |
| Top - 5 | S10 |
| Top - 6 | S9 |
| Top - 7 | S8 |
| Top - 8 | S7 |
| Top - 9 | S6 |
| Top - 10 | S5 |
| Top - 11 | S4 |
| Top - 12 | S3 |
| Top - 13 | S2 |
| Top - 14 | S1 |
| Top - 15 | S0 |
|-----------------------------------| <----- OSTCBCurPtr->StkPtr
| ... | ... | <----- 每一个任务都会有一些c语言栈变量,
| ... | ... | 以及函数调用的压栈,占据了一些空间。
| ... | ... |
|-----------------------------------|
| Top - n | xPSR |
| Top - n - 1 | PC |
| Top - n - 2 | R14 (LR) |
| Top - n - 3 | R12 |
| Top - n - 4 | R3 |
| Top - n - 5 | R2 |
| Top - n - 6 | R1 |
| Top - n - 7 | R0 |
|-----------------------------------| <----- PSP寄存器指向这个位置
| ... | ... |
此时,还有一些寄存器没有压栈:R4
~ R11
。
再来看OS_CPU_PendSVHandler
函数:
[os_cpu_a.S OS_CPU_PendSVHandler函数]
.thumb_func
OS_CPU_PendSVHandler:
CPSID I @ Prevent interruption during context switch
MRS R0, PSP @ PSP is process stack pointer
STMFD R0!, {R4-R11, R14} @ Save remaining regs r4-11, R14 on process stack
MOVW R5, #:lower16:OSTCBCurPtr @ OSTCBCurPtr->StkPtr = SP;
MOVT R5, #:upper16:OSTCBCurPtr
LDR R1, [R5]
STR R0, [R1] @ R0 is SP of process being switched out
@ At this point, entire context of process has been saved
MOV R4, LR @ Save LR exc_return value
BL OSTaskSwHook @ OSTaskSwHook();
MOVW R0, #:lower16:OSPrioCur @ OSPrioCur = OSPrioHighRdy;
MOVT R0, #:upper16:OSPrioCur
MOVW R1, #:lower16:OSPrioHighRdy
MOVT R1, #:upper16:OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
MOVW R1, #:lower16:OSTCBHighRdyPtr @ OSTCBCurPtr = OSTCBHighRdyPtr;
MOVT R1, #:upper16:OSTCBHighRdyPt
LDR R2, [R1]
STR R2, [R5]
ORR LR, R4, #0x04 @ Ensure exception return uses process stack
LDR R0, [R2] @ R0 is new process SP;
@ SP = OSTCBHighRdyPtr->StkPtr;
LDMFD R0!, {R4-R11, R14} @ Restore r4-11, R14 from new process stack
MSR PSP, R0 @ Load PSP with new process SP
CPSIE I
BX LR @ Exception return will restore remaining context
OS_CPU_PendSVHandler
可以总结为以下步骤:
把PSP
里的值给R0
,然后把把R14
(LR
里面放的是EXC_RETURN
)、R11
~ R4
、压入到到R0
指向的栈中,再更新OSTCBCurPtr->StkPtr
的值,即OSTCBCurPtr->StkPtr = R0
。此时正在运行的任务的栈中内存分布为:
|===================================|
| position DWORD | Descriptions |
|===================================|
| Top - 0 | FPSCR |
| Top - 1 | S15 |
| Top - 2 | S14 |
| Top - 3 | S12 |
| Top - 4 | S11 |
| Top - 5 | S10 |
| Top - 6 | S9 |
| Top - 7 | S8 |
| Top - 8 | S7 |
| Top - 9 | S6 |
| Top - 10 | S5 |
| Top - 11 | S4 |
| Top - 12 | S3 |
| Top - 13 | S2 |
| Top - 14 | S1 |
| Top - 15 | S0 |
|-----------------------------------|
| ... | ... | <----- 每一个任务都会有一些c语言栈变量,
| ... | ... | 以及函数调用的压栈,占据了一些空间。
| ... | ... |
|-----------------------------------|
| Top - n | xPSR |
| Top - n - 1 | PC |
| Top - n - 2 | R14 (LR) |
| Top - n - 3 | R12 |
| Top - n - 4 | R3 |
| Top - n - 5 | R2 |
| Top - n - 6 | R1 |
| Top - n - 7 | R0 |
|-----------------------------------|
| Top - n - 8 | LR(EXC_RETURN) |
| Top - n - 9 | R11 |
| Top - n - 10 | R10 |
| Top - n - 11 | R9 |
| Top - n - 12 | R8 |
| Top - n - 13 | R7 |
| Top - n - 14 | R6 |
| Top - n - 15 | R5 |
| Top - n - 16 | R4 |
|-----------------------------------| <----- R0寄存器指向这个位置,OSTCBCurPtr->StkPtr
| ... | ... |
把LR
(EXC_RETURN
)的值给R4
,然后跳入到C函数OSTaskSwHook
,需要注意的是跳入到OS_CPU_PendSVHandler
之后,处理器使用的是MSP
,所以在ISR
中直接调用任何的C代码都不会改变PSP
所指向的栈的内存分布。OSTaskSwHook
在之前提到过,就是把S31
~ S16
压入到OSTCBCurPtr->StkPtr
(当前任务)的栈里,再把OSTCBHighRdyPtr->StkPtr
(最高优先级,即下一个任务)的栈中数据恢复到S31
~ S16
中,这样过后,两个任务的栈的内存分布如下:
|===================================| |===================================|
| OSTCBCurPtr | | OSTCBHighRdyPtr |
|===================================| |===================================|
| position DWORD | Descriptions | | position DWORD | Descriptions |
|===================================| |===================================|
| Top - 0 | FPSCR | | Top - 0 | FPSCR |
| Top - 1 | S15 | | Top - 1 | S15 |
| Top - 2 | S14 | | Top - 2 | S14 |
| Top - 3 | S12 | | Top - 3 | S12 |
| Top - 4 | S11 | | Top - 4 | S11 |
| Top - 5 | S10 | | Top - 5 | S10 |
| Top - 6 | S9 | | Top - 6 | S9 |
| Top - 7 | S8 | | Top - 7 | S8 |
| Top - 8 | S7 | | Top - 8 | S7 |
| Top - 9 | S6 | | Top - 9 | S6 |
| Top - 10 | S5 | | Top - 10 | S5 |
| Top - 11 | S4 | | Top - 11 | S4 |
| Top - 12 | S3 | | Top - 12 | S3 |
| Top - 13 | S2 | | Top - 13 | S2 |
| Top - 14 | S1 | | Top - 14 | S1 |
| Top - 15 | S0 | | Top - 15 | S0 |
|-----------------------------------| |-----------------------------------|
| ... | ... | | ... | ... |
| ... | ... | | ... | ... |
| ... | ... | | ... | ... |
|-----------------------------------| |-----------------------------------|
| Top - n | xPSR | | Top - m | xPSR |
| Top - n - 1 | PC | | Top - m - 1 | PC |
| Top - n - 2 | R14 (LR) | | Top - m - 2 | R14 (LR) |
| Top - n - 3 | R12 | | Top - m - 3 | R12 |
| Top - n - 4 | R3 | | Top - m - 4 | R3 |
| Top - n - 5 | R2 | | Top - m - 5 | R2 |
| Top - n - 6 | R1 | | Top - m - 6 | R1 |
| Top - n - 7 | R0 | | Top - m - 7 | R0 |
|-----------------------------------| |-----------------------------------|
| Top - n - 8 | LR(EXC_RETURN) | | Top - m - 8 | LR(EXC_RETURN) |
| Top - n - 9 | R11 | | Top - m - 9 | R11 |
| Top - n - 10 | R10 | | Top - m - 10 | R10 |
| Top - n - 11 | R9 | | Top - m - 11 | R9 |
| Top - n - 12 | R8 | | Top - m - 12 | R8 |
| Top - n - 13 | R7 | | Top - m - 13 | R7 |
| Top - n - 14 | R6 | | Top - m - 14 | R6 |
| Top - n - 15 | R5 | | Top - m - 15 | R5 |
| Top - n - 16 | R4 | | Top - m - 16 | R4 |
|-----------------------------------| |-----------------------------------| <--|
| Top - n - 17 | S31 | | ... | ... | |
| Top - n - 18 | S30 | |
| Top - n - 19 | S29 | OSTCBHighRdyPtr->StkPtr -------|
| Top - n - 20 | S28 |
| Top - n - 21 | S27 |
| Top - n - 22 | S26 |
| Top - n - 23 | S25 |
| Top - n - 24 | S24 |
| Top - n - 25 | S23 |
| Top - n - 26 | S22 |
| Top - n - 27 | S21 |
| Top - n - 28 | S20 |
| Top - n - 29 | S19 |
| Top - n - 30 | S18 |
| Top - n - 31 | S17 |
| Top - n - 32 | S16 |
|-----------------------------------| <----- OSTCBCurPtr->StkPtr
| ... | ... |
OSPrioCur = OSPrioHighRdy
、OSTCBCurPtr = OSTCBHighRdyPtr
。
把LR
(EXC_RETURN
)寄存器里的BIT3
置位。EXC_RETURN
的值决定了异常返回的行为,可以决定异常返回之后的模式、使用哪个对战寄存器、出栈栈帧的长度:
|============================================|
| no FP extension |
|============================================|
| EXC_RETURN | Return to | Return stack |
|============================================|
| 0xFFFFFFF1 | Handler mode | Main |
| 0xFFFFFFF9 | Thread mode | Main |
| 0xFFFFFFFD | Thread mode | Process |
|--------------------------------------------|
|=========================================================|
| with FP extension |
|=========================================================|
| EXC_RETURN | Return to | Return stack | Frame type |
|=========================================================|
| 0xFFFFFFE1 | Handler mode | Main | Extended |
| 0xFFFFFFE9 | Thread mode | Main | Extended |
| 0xFFFFFFED | Thread mode | Process | Extended |
| 0xFFFFFFF1 | Handler mode | Main | Basic |
| 0xFFFFFFF9 | Thread mode | Main | Basic |
| 0xFFFFFFFD | Thread mode | Process | Basic |
|---------------------------------------------------------|
这一步有一些疑问,会再下一步一并讨论。
把OSTCBHighRdyPtr->StkPtr
赋值给R0
,再用R0
把OSTCBHighRdyPtr->StkPtr
栈内的值恢复到R4
~ R11
、LR
(R14
/EXC_RETURN
)寄存器。再把恢复后的R0
的值给PSP
。在第2步,可以看到每次调用OS_CPU_PendSVHandler
时,会把EXC_RETURN
压入栈中,而在这一步会把LR
寄存器从栈中恢复出来,所以,第4步对LR
的操作会被这一步覆盖。在OSTaskStkInit
初始化的是时候会把0xFFFFFFEDu
压入栈中EXC_RETURN
位置(没有FPU
的话值是0xFFFFFFFD
)。所以第4步应该是没用才对(ORR LR, R4, #0x04
这条指令)。这时OSTCBCurPtr
的值已经是新的任务了(OSTCBHighRdyPtr
),新任务的栈内存分布为:
|===================================|
| OSTCBCurPtr |
|===================================|
| position DWORD | Descriptions |
|===================================|
| Top - 0 | FPSCR |
| Top - 1 | S15 |
| Top - 2 | S14 |
| Top - 3 | S12 |
| Top - 4 | S11 |
| Top - 5 | S10 |
| Top - 6 | S9 |
| Top - 7 | S8 |
| Top - 8 | S7 |
| Top - 9 | S6 |
| Top - 10 | S5 |
| Top - 11 | S4 |
| Top - 12 | S3 |
| Top - 13 | S2 |
| Top - 14 | S1 |
| Top - 15 | S0 |
|-----------------------------------|
| ... | ... |
| ... | ... |
| ... | ... |
|-----------------------------------|
| Top - m | xPSR |
| Top - m - 1 | PC |
| Top - m - 2 | R14 (LR) |
| Top - m - 3 | R12 |
| Top - m - 4 | R3 |
| Top - m - 5 | R2 |
| Top - m - 6 | R1 |
| Top - m - 7 | R0 |
|-----------------------------------| <----- PSP寄存器指向的位置
这时,触发异常退出流程(也可能有新的中断进来,进入末尾连锁机制,但是最终都会触发异常退出流程),将PSP
中的数据恢复到R0
~ R3
、R12
、R14
(LR
)、PC
、xPSR
。PC
指针被更新,执行PC
处的代码。此时栈的内存分布如下:
|===================================|
| OSTCBCurPtr |
|===================================|
| position DWORD | Descriptions |
|===================================|
| Top - 0 | FPSCR |
| Top - 1 | S15 |
| Top - 2 | S14 |
| Top - 3 | S12 |
| Top - 4 | S11 |
| Top - 5 | S10 |
| Top - 6 | S9 |
| Top - 7 | S8 |
| Top - 8 | S7 |
| Top - 9 | S6 |
| Top - 10 | S5 |
| Top - 11 | S4 |
| Top - 12 | S3 |
| Top - 13 | S2 |
| Top - 14 | S1 |
| Top - 15 | S0 |
|-----------------------------------|
| ... | ... |
| ... | ... |
| ... | ... |
|-----------------------------------| <----- PSP寄存器指向的位置
| ... | ... |
跟之前的程序运行状态一样。这样子就完成了上下文切换的动作,切换到的新的任务。
但是需要注意的是,每一个任务的stack
最上面都会预留FPSCR
、S15
~ S0
。但是每次代码里却没有主动的去PUSH
和POP
这个区域,原因是因为Cortex-M4
的Lazy Stacking
,详细的说明可以参考Cortex-M4(F) Lazy Stacking and Context Switching Application Note 298
。在这个版本里的uCos
是强制打开Lazy Stacking
的:
[os_cpu.h]
#ifdef __ARMVFP__
#define OS_CPU_ARM_FP_EN 1u
#else
#define OS_CPU_ARM_FP_EN 0u
#endif
如果定义了__ARMVFP__
则打开OS_CPU_ARM_FP_EN
这个宏,并且在OSInitHook
这个函数里,会判断Lazy Stacking
有没有打开,如果没有打开则死循环:
[os_cpu_c.c]
void OSInitHook (void)
{
#if (OS_CPU_ARM_FP_EN > 0u)
CPU_INT32U reg_val;
#endif
/* 8-byte align the ISR stack. */
OS_CPU_ExceptStkBase = (CPU_STK *)(OSCfg_ISRStkBasePtr + OSCfg_ISRStkSize);
OS_CPU_ExceptStkBase = (CPU_STK *)((CPU_STK)(OS_CPU_ExceptStkBase) & 0xFFFFFFF8);
#if (OS_CPU_ARM_FP_EN > 0u)
/* Check the floating point mode. */
reg_val = CPU_REG_FP_FPCCR;
if ((reg_val & CPU_REG_FPCCR_LAZY_STK) != CPU_REG_FPCCR_LAZY_STK) {
while (1u) {
;
}
}
#endif
}
到此,便完成了由SysTick
引发的上下文切换的所有动作。
五. OSIntExit
函数引发的调度
可以引发PendSV
中断的函数有两个,一个是OSCtxSw
,一个是OSIntCtxSw
。
[os_cpu_a.asm]
OSCtxSw
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
OSIntCtxSw
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
OSIntCtxSw
这个函数只有在OSIntExit
函数中调用,OSIntExit
这个函数在之前分析过,在SysTick
中断里也会调用他。
一般来说,OSIntExit
这个函数在中断退出的时候才会调用他,配合OSIntEnter
一起使用。
OSIntEnter
函数只是记录一下中断的嵌套层数,放到全局变量OSIntNestingCtr
里:
void OSIntEnter (void)
{
OS_TRACE_ISR_ENTER();
if (OSRunning != OS_STATE_OS_RUNNING) {
return;
}
if (OSIntNestingCtr >= 250u) {
return;
}
OSIntNestingCtr++;
}
而OSIntExit
把OSIntNestingCtr
进行自减操作,再触发PendSV
进行一次任务调度。
OSIntExit
触发调度的原因就是因为再某些任务中会等待一些事件或者信号量,而这些事件或者信号量会在中断中被触发或者增加,如果想让这些任务在这些等待的条件满足时,可以快速的触发调度而调度到正在等待的任务,降低等待的时间。
六. 调用OSSched
函数引发的调度,及其调度点总结
上面说到,可以引发PendSV
中断的函数有两个,一个是OSCtxSw
,一个是OSIntCtxSw
。
其中OSCtxSw
又被宏定义为OS_TASK_SW
:
[os_cpu.h]
#define OS_TASK_SW() OSCtxSw()
而OS_TASK_SW
函数只有在OSSched
中被调用:
void OSSched (void)
{
CPU_SR_ALLOC();
/* ISRs still nested? */
if (OSIntNestingCtr > (OS_NESTING_CTR)0) {
/* Yes ... only schedule when no nested ISRs */
return;
}
/* Scheduler locked? */
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) {
/* Yes */
return;
}
CPU_INT_DIS();
/* Find the highest priority ready */
OSPrioHighRdy = OS_PrioGetHighest();
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
/* Current task is still highest priority task? */
if (OSTCBHighRdyPtr == OSTCBCurPtr) {
/* Yes ... no need to context switch */
CPU_INT_EN();
return;
}
#if OS_CFG_TASK_PROFILE_EN > 0u
/* Inc. # of context switches to this task */
OSTCBHighRdyPtr->CtxSwCtr++;
#endif
/* Increment context switch counter */
OSTaskCtxSwCtr++;
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS_TaskSw();
#endif
/* Perform a task level context switch */
OS_TASK_SW();
CPU_INT_EN();
#ifdef OS_TASK_SW_SYNC
OS_TASK_SW_SYNC();
#endif
}
OSPrioHighRdy = OS_PrioGetHighest();
这条语句是找到OSPrioTbl
位图里面的最高优先级是多少,放到全局变量OSPrioHighRdy
里。
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
找到最高优先级的第一个任务实体,放到全局变量OSTCBHighRdyPtr
里。
随后调用OS_TASK_SW
引发调度。这里CPU_INT_EN
是打开中断,OS_TASK_SW_SYNC
是一个内存屏障ISB
指令。
下面是通过调用OSSched
而引发调度的调度点总结:
序号 | 调度点 | 说明 |
---|
1 | 调用 OS...Post 函数时 | 任务调用提交服务函数OS...Post() ,发送信号量或消息给其它任务时调度发生。调度在OS...Post() 函数的结束时发生。注意在有些情况下, 调度是不会发生的(见 OS_OPT_POST_NO_SCHED 的可选参数)。 这些Post 函数都会调用OSSched 函数。 |
2 | 调用 OSTimeDly 或者 OSTimeDlyHMSM 延时函数时 | 在调用延时函数的时候, 调用者的本意就是放弃cpu 的使用权,等到延时结束之后再继续。 所以, 调用延时函数之后, ucos 会先调用OS_RdyListRemove(OSTCBCurPtr) 把当前任务从就绪列表中移除, 再主动的去调用OSSched 引发一次调度。 |
3 | 调用 OS...Pend 函数时 | 在调用Pend 函数之后, 如果资源还没有准备就绪, 就会调用一次 OSSched 来引发一次调度。 资源就绪时接收到该事件的任务 或者超时的任务就会被移出等待队列。 然后调度器选择就绪列表中优先级最高的任务执行。 移出等待队列的任务不一定就是就绪状态, 因为它还可能在停止队列中, 效果是可以叠加的。 |
4 | 调用 Os...PendAbort 取消挂起状态时 | 一个任务可以被取消挂起, 若另一个任务调用 OS...PendAbort 。 当任务被移出等待列表中时调度发生。 |
5 | 任务被创建时 | 创建任务的函数是OSTaskCreate , 这个函数最后会调用OSSched 引发一次调度。 |
6 | 任务被删除时 | 删除任务的函数是OSTaskDel , 这个函数最后也会调用一次OSSched 引发一次调度。 |
7 | 内核对象被删除时 | 内核对象被删除的函数都是OS...Del , 删除的对象可以是事件标志组、 信号量、 消息队列、 Mutex。 任务所等待的内核对象被删除时这些任务就可能被就绪, 然后调度发生。 任务改变自身的优先级或其它任务的优先级。 |
8 | 任务改变自身的优先级 或者其他任务的优先级时 | 调用OSTaskChangePrio 的时候, 最终会调用 OSSched 而引发一次调度。 |
9 | 任务通过调用 OSTaskSuspend 停止自身时 | OSTaskSuspend 是把任务悬挂起来, 最终会调用OSSched 而引发一次调度。 |
10 | 任务调用 OSTaskResume 回复其他停止了的任务 | 把一个任务重悬起状态恢复成就绪状态, 最终会调用OSSched 而引发一次调度。 |
11 | 通过 OSSchedUnlock 调度器被解锁时 | 把调度器解锁, 此时会调用OSSched 而引发一次调度。 |
12 | 通过调用 OSSchedRoundRobinYield 任务放弃了分配给它的时间片时 | 调用OSSched 而引发一次调度 |
13 | 用户主动的调用OSSched 函数。 | 用户主动的希望发生一次调度。 |
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)