μC/OS III - 任务调度 Ⅰ:调度过程和调度点

2023-05-16

这是μ/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;
}

上面的任务创建之后,会执行任务处理函数TaskStart1TaskStart2TaskStart1不停的亮灭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_limitARM对两种增长方式都支持,但是由于编译器的原因一般都是选择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

上面的代码片段,如果有需要就保存线程的入口函数和名字,然后PrioBasePrio保存线程的优先级,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_extStkBasePrt字段填写为栈的起始位置。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_LISTNbrEntries

继续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();

上面的代码总结为:

  1. OS_TaskDbgListAdd(p_tcb)调试相关的代码,暂不深入。
  2. OSTaskQty++记录当前任务的个数。
  3. 如果OS不处于OS_STATE_OS_RUNNING运行状态,则调用OS_CRITICAL_EXIT并退出函数。
  4. 如果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;
    }
}
  1. OSPrioHighRdy = OS_PrioGetHighest();这条语句是找到OSPrioTbl位图里面的最高优先级是多少,放到全局变量OSPrioHighRdy里。
  2. OSPrioCur = OSPrioHighRdy;OSPrioHighRdy放入OSPrioCur(现在执行正在执行的任务的优先级)。
  3. OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;找到最高优先级的第一个任务实体,放到全局变量OSTCBHighRdyPtr里。
  4. OSTCBCurPtr表示当前执行的任务实体指针,设置为OSTCBHighRdyPtr
  5. OSRunning = OS_STATE_OS_RUNNING把任务设置为运行状态。
  6. 随后开始执行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的地址就是这个TCBStkPtr字段的地址。

OSStartHighRdy这个函数是在的流程可以总结如下:

  1. 关中断。
  2. NVIC_SYSPRI14寄存器的值设为NVIC_PENDSV_PRI0xFF),即把PendSV的中断优先级设置为最低的0xFF
  3. 把主栈指针MSP寄存器设置为OS_CPU_ExceptStkBase
  4. 跳转到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 ~ S31OSTCBHighRdyPtr->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
    |      ...       |     ...       |       ...        |
    
  5. OSPrioHighRdy(所有任务中最高的任务优先级)的值赋值给OSPrioCur(当前执行的任务的优先级)。

  6. OSTCBHighRdyPtr(最高优先级的任务实体的指针)的值赋值给OSTCBCurPtr(当前执行的任务指针)。
  7. OSTCBHighRdyPtr->StkPtr的值赋值给线程堆栈指针寄存器PSP
  8. 设置CONTROL寄存器的第二个比特位置为1,第二个比特位为SPSEL,为1是线程模式使用PSP作为栈指针。并加上一个ISB使设置对战指针生效。下面的指令都是把PSP当成栈指针在使用,即OSTCBHighRdyPtr->StkPtr
  9. 之后,把OSTCBHighRdyPtr->StkPtr里面的数据依次递减读出来,按照下面的顺序放到下面的寄存器里:R4 ~ R11LRR0 ~ R3R12LRR1R2。恢复到寄存器就是按照出栈的方式进行的,经过这样处理之后,几个有用的寄存器被设置,而剩下的值无关紧要,不去关心。

    | 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
    |      ...       |     ...       |       ...        |
    
  10. 打开中断,跳转到R1寄存器所指的位置,即线程函数入口处,开始执行第一个任务。

四. 任务调度的流程和时机之SysTickPendSV

CPU相关的.S文件中,会找到类似如下的代码,把SysTickPendSV的中断处理函数定向到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函数可以总结为如下步骤:

  1. 判断OS是否处于OS_STATE_OS_RUNNING状态,如果不是,退出。
  2. CPU_INT_DIS关闭中断。
  3. 判断是否处于中断状态,通过OSIntNestingCtr,如果不是,退出。
  4. 判断是否处于中断嵌套状态,如果是,退出。
  5. 判断是否锁住了调度,如果是,退出。
  6. 寻找到任务的最高优先级,放入OSPrioHighRdy,寻找最高优先级任务的实体,放入到OSTCBHighRdyPtr
  7. 如果最高优先级的任务就是当前的任务,退出。
  8. 增加当前最高优先级的调度计数器CtxSwCtr,增加全局的调度计数器OSTaskCtxSwCtr
  9. 调用OSIntCtxSw函数:

    [os_cpu_a.S OSIntCtxSw函数]
    
    OSIntCtxSw:
        LDR     R0, =NVIC_INT_CTRL
        LDR     R1, =NVIC_PENDSVSET
        STR     R1, [R0]
        BX      LR
    

    这个函数是触发PENDSV中断。

  10. CPU_INT_EN开打中断。因为这时候CPU本身处于处理模式(SysTick ISR函数中),打开中断之后由于末尾连锁的机制,即使没有加ISB指令,CPU也不会向下执行两条指令,而直接触发PendSV ISR

在这之后,触发了PendSV,加上末尾连锁的机制,不会再返回线程模式而直接进入了PendSVISR

进入PendSV ISR的流程可能有如下的几种:

  1. 线程模式 → SysTick ISRPendSV
  2. 线程模式 → 其他更高优先级的ISRSysTick ISRPendSV
  3. 线程模式 → … → 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(惰性压着)特性,那么压栈的顺序是xPSRPCLR(R14)、R12R3R2R1R0。此后当前任务的栈内存分布如下:

|===================================|
| 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可以总结为以下步骤:

  1. 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
    |      ...       |       ...        |
    
  2. LREXC_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
    |      ...       |       ...        |
    
  3. OSPrioCur = OSPrioHighRdyOSTCBCurPtr = OSTCBHighRdyPtr

  4. 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   |
    |---------------------------------------------------------|
    

    这一步有一些疑问,会再下一步一并讨论。

  5. OSTCBHighRdyPtr->StkPtr赋值给R0,再用R0OSTCBHighRdyPtr->StkPtr栈内的值恢复到R4 ~ R11LRR14/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寄存器指向的位置
    
  6. 这时,触发异常退出流程(也可能有新的中断进来,进入末尾连锁机制,但是最终都会触发异常退出流程),将PSP中的数据恢复到R0 ~ R3R12R14(LR)、PCxPSRPC指针被更新,执行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最上面都会预留FPSCRS15 ~ S0。但是每次代码里却没有主动的去PUSHPOP这个区域,原因是因为Cortex-M4Lazy 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++;
}

OSIntExitOSIntNestingCtr进行自减操作,再触发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(使用前将#替换为@)

μC/OS III - 任务调度 Ⅰ:调度过程和调度点 的相关文章

  • 洛谷 P2651 添加括号III

    思路 xff1a a1肯定是分子 xff0c a2肯定是分母 xff0c 只要确认a1a3a4 a2是否是整数 只要确认a1a3a4 a2是否是整数 每次将a2 61 a2 gcd a2 ai i 61 1 3 4 5 即可约分 span
  • P2651 添加括号III(数论,洛谷,java,最大公约数)

    洛谷链接 xff1a https www luogu org problem P2651 span class token keyword import span java span class token punctuation span
  • 洛谷 - p2651 添加括号III(思维,数学)

    题目传送 题意 思路 我们首先想想如何判断一个分数是否能约分成整数 判断方法 1 我们可以分解分子的质因子和分母的质因子 xff0c 如果分母的质因子数量和种类数完全被分子的质因子包括 xff0c 那么一定可以约分成为整数 2 如果分母的所
  • uC/OS-III源码下载(版本2009-2021)

    uC OS III源码下载 xff08 新版网站 xff09 下载方式一 官网 xff08 即GitHub方式 xff09 二 CSDN 下载方式 一 官网 xff08 即GitHub方式 xff09 链接 uCOS3官网 点击CODEBA
  • LeetCode437:路径总和III

    要求 给定一个二叉树的根节点 root xff0c 和一个整数 targetSum xff0c 求该二叉树里节点值之和等于 targetSum 的 路径 的数目 路径 不需要从根节点开始 xff0c 也不需要在叶子节点结束 xff0c 但是
  • FreeRTOS 任务调度 任务切换

    64 嵌入式 简述启动调度器 移植层调度器启动第一个任务 任务切换参考 FreeRtos 简述 前面文章 lt FreeRTOS 任务调度 任务创建 gt 介绍了 FreeRTOS 中如何创建任务以及其具体实现 一般来说 xff0c 我们会
  • 对uC/OS-III时钟节拍运转机制的一点理解

    目录 如何产生时基信号系统时钟中断管理时基任务时基列表更新写在最后 我在初学uC OS III的时候 xff0c 时基产生后到底是如何去驱动操作系统运转的 xff0c 对于这个问题一直有很多疑问 xff0c 最后读了手册并且仔细推敲源码后终
  • FreeRTOS-任务调度

    1 系统配置完成systick模块后 xff0c 通常下每当产生一个systick中断就会运行SysTick Handler 2 分析void xPortSysTickHandler void a 通过操作寄存器basepri关闭一些中端
  • EFM32jg之FreeRTOS(5)-任务调度、创建、切换

    64 EFM32JG移植FreeRTOS 1 任务调度器 1 xff09 创建空闲任务 xff0c 优先级为0 xff0c 表示最低优先级 xff0c 在无其他高优先级任务的情况下 xff0c 执行空闲任务 xff0c 若打开configU
  • uC/OS-III 的特点

    1 其中最有用的功能应该是时间片轮转法 xff08 roundrobin 这个是 uC OS II 中不支持的 xff0c 但是现在已经是 uC OS III 的一个功能了 2 uC OS III 被设计用于 32 位处理器 xff0c 但
  • freeRtos源码解析(二)–任务调度

    freeRtos源码解析 二 任务调度 一 启动任务调度器 启动任务调度器之后 xff0c CPU正式进入任务模式调度各任务 xff08 CPU在中断模式和任务模式之间不断轮转 xff09 freeRtos任务调度依赖于内核的三个中断 xf
  • 【STM32】入门(十一):初识uCOS-III

    STM32 STM32单片机总目录 1 轮询 中断 多任务对比 2 什么是任务 如果您学过linux xff0c 那么任务可以理解为线程 在代码中的体现就是线程函数 xff0c 一个函数中有个无限循环函数 xff0c 并且永不返回 例如 x
  • μC/OS III - 任务调度 Ⅰ:调度过程和调度点

    这是 C OS III任务调度的第一篇文章 xff1a 调度过程和调度点 基于Cortex M系列的处理器 xff0c 从最简单的创建任务开始 xff0c 分析 C OS III的任务调度过程 包括上下文切换的详细过程 任务的栈分配详情 引
  • UC/OS-III 消息队列

    消息队列 一 消息队列基本概念讲解1 消息队列基本概念2 消息池2 1 消息池概念2 2 消息池初始化2 3 消息队列的运作机制2 4 消息队列的阻塞机制2 5 消息队列的应用场景 二 消息队列创建步骤1 定义消息队列2 创建消息队列 三
  • PX4-4-任务调度

    PX4所有的功能都封装在独立的模块中 xff0c uORB是任务间数据交互和同步的工具 xff0c 而管理和调度每个任务 xff0c PX4也提供了一套很好的机制 xff0c 这一篇我们分享PX4的任务调度机制 我们以PX4 1 11 3版
  • C# Task异步编程

    Task任务用法 Task用的是线程池 线程池的线程数量的有上限的 这个可以通过ThreadPool修改 我们经常会用到task run new task 和task factory startnew方法来创建任务 Task Factory
  • Java定时任务调度工具详解之Quartz篇(中级)一:浅谈JobExecutionContext&JobDatai&浅谈Trigger

    概要 OpenSymphony提供的强大的开源任务调度框架 官网 http www quartz scheduler org 纯java实现 精细控制排程 特点 强大的调度功能 灵活的应用方式 分布式和集群能力 主要用到的设计模式 Buil
  • java定时任务实现的几种方式

    摘要 在开发测试工具的应用后台 经常听到同事说要做个定时任务把做日志处理 或者数据清理 包括做些复杂的业务计算逻辑 在选择定时任务的时候 怎么能够快速实现 并且选择一种更适合自己的方式呢 我这里把定时任务的实现收集整理了一些方法 希望可以帮
  • C语言实现多级反馈队列调度算法

    include
  • OSAL

    OSAL为 Operating System Abstraction Layer 即操作系统抽象层 支持多任务运行 它并不是一个传统意义上的操作系统 但是实现了部分类似操作系统的功能 OSAL概念是由TI公司在ZIGBEE协议栈引入 他的意

随机推荐

  • 关于嵌入式

    学习方向 首先要学习下基础课程单片机 xff0c 汇编和C语言等等 xff0c 然后再学习嵌入式 xff0c 如果说你要想水平高的话 xff0c 最好学习下操作系统 xff0c 数据结构 xff0c 算法及一些硬件方面的知识等等 看你是想在
  • make_unique的使用

    关于make unique的构造及使用例程 xff0c MSDN的讲解非常详细 xff08 https msdn microsoft com zh cn library dn439780 aspx xff09 使用过程中 xff0c 我的理
  • C#学习记录——C#编写串口程序

    因为电气自动化专业出差太多 xff0c 考虑学点其他的看能不能实现转行 xff0c 也没太清晰的路线 xff0c 看网上好多推荐电气自动化转C 上位机开发的 xff0c 也抽时间学习了解下C xff0c 因为非软件专业 xff0c 对计算机
  • the working directory ‘XXX’ does not exist

    积累点滴 今天在idea上重新建了一个项目 xff0c 结果一运行就报了 the working directory XXX does not exist 的错误 明明上一个项目都运行好好的 xff0c 怎么新建一个就出问题了呢 xff1f
  • Git 子模块(Submodule)

    提示 xff1a Git 子模块 Submodule 操作 文章目录 一 Git 子模块 Submodule 是什么 xff1f 二 使用步骤1 创建子仓库2 clone 带有子仓库的git项目 三 子仓库代码的修改和更新 一 Git 子模
  • Java Web项目开发项目经验总结

    一 学会如何读一个JavaWeb项目源代码 步骤 xff1a 表结构 gt web xml gt mvc gt db gt spring ioc gt log gt 代码 1 先了解项目数据库的表结构 xff0c 这个方面是最容易忘记的 x
  • React + TS + Mobx 示例

    一 创建项目 方式一 xff1a create react app todo React ts demo scripts version 61 react scripts ts cd todo React ts demo npm start
  • AMD IOMMU与Linux (2) -- IVRS及AMD IOMMU硬件初始化

    介绍AMD IOMMU driver基于IVRS的硬件初始化情况 1 I O Virtualization ACPI table 2 drivers iommu amd init c 1 I O Virtualization ACPI ta
  • AMD IOMMU与Linux (3) -- DMA

    Linux中DMA会使用硬件IOMMU如AMD IOMMU INTEL VT D xff0c 也会使用软件的SWIOTLB 这篇梳理一下LINUX内核在有AMD IOMMU的情况下 xff0c 是如何做DMA的 xff0c 内容包括如下 1
  • AMD IOMMU与Linux (4) -- Domain, Group, Device

    1 domain的本质是一个页表 xff0c 1对1的关系 2 IOMMU DOMAIN UNMANAGED vs IOMMU DOMAIN DMA a IOMMU DOMAIN UNMANAGED DMA mappings managed
  • 第三篇:知其然,知其所以然-USB音频设备的开发过程

    最近 xff0c 有朋友正好在开发一个USB音频设备 xff0c 所以询问我一些USB音频设备开发方面的技术细节问题 xff1b 也和音响发烧友聊到USB音频设备的实现方式与其优缺点 xff1b 后来 xff0c 也和人谈到实现一个USB音
  • 第七篇:风起于青萍之末-电源管理请求案例分析(下)

    第五篇 风起于青萍之末 电源管理请求案例分析 上 http blog csdn net u013140088 article details 18180249 第六篇 风起于青萍之末 电源管理请求案例分析 中 http blog csdn
  • 第十九篇:USB Audio/Video Class设备协议

    转发请注明出处 随着项目的不断进行 我想在网上查找了一下USB Audio Video的最新资料 看看有没有业内人士的更新 由于我们的项目一直在技术的最前延 而且这个USB IF官方发布的协议 也非常非常新 结果找了半天 都是我这篇文章的转
  • 《网络架构系列2-Http详解》

    不诗意的女程序媛不是好厨师 转载请注明出处 xff0c From李诗雨 https blog csdn net cjm2484836553 article details 104136511 网络架构系列2 Http详解 1 Http的协议
  • 第三十二篇:Windbg中USB2.0调试环境的搭建

    2011年的时候 xff0c 为了开发USB Mass storage UASP USB attached SCSI Protocol 的设备驱动程序 xff0c 从米国买了两个USB2 0的调试小设备 xff08 如下图 xff0c 每个
  • 理解SerDes 之一

    理解SerDes FPGA发展到今天 xff0c SerDes Serializer Deserializer 基本上是标配了 从PCI到PCI Express 从ATA到SATA xff0c 从并行ADC接口到JESD204 从RIO到S
  • 理解SerDes 之二

    理解SerDes 之二 2012 11 11 21 17 12 转载 标签 xff1a dfe serdes it 2 3 接收端均衡器 Rx Equalizer 2 3 1 线形均衡器 Linear Equalizer 接收端均衡器的目标
  • USB3.0的物理层测试探讨

    USB简介 USB Universal Serial Bus 即通用串行总线 xff0c 用于把键盘 鼠标 打印机 扫描仪 数码相机 MP3 U盘等外围设备连接到计算机 xff0c 它使计算机与周边设备的接口标准化 在USB1 1版本中支持
  • ARM SoC漫谈

    作者 xff1a 重走此间路 链接 xff1a https zhuanlan zhihu com p 24878742 来源 xff1a 知乎 著作权归作者所有 商业转载请联系作者获得授权 xff0c 非商业转载请注明出处 芯片厂商向客户介
  • μC/OS III - 任务调度 Ⅰ:调度过程和调度点

    这是 C OS III任务调度的第一篇文章 xff1a 调度过程和调度点 基于Cortex M系列的处理器 xff0c 从最简单的创建任务开始 xff0c 分析 C OS III的任务调度过程 包括上下文切换的详细过程 任务的栈分配详情 引