(学习日记)2024.03.25:UCOSIII第二十二节:系统启动流程详解

news2024/11/27 14:44:31

写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。


标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。


点击此处进入学习日记的总目录

2024.03.25:UCOSIII第二十二节:系统启动流程详解

  • 三十六、UCOSIII:系统启动流程详解
    • 1、运行启动文件
    • 2、主流程 main
    • 3、系统初始化函数OSInit()
      • 1. 空闲任务的初始化
      • 2. 空闲任务的定义
      • 3. 时钟节拍任务的初始化
    • 4、启动任务AppTaskStart()
      • 1. 时间戳初始化
      • 2. SysTick初始化
      • 3. 内存初始化
    • 5、任务调度器启动函数OSStart()

三十六、UCOSIII:系统启动流程详解

本章总结一下系统在刚启动时初始化的过程
介绍顺序为上电后的运行顺序:

1、运行启动文件

在系统上电的时候第一个执行的是启动文件里面由汇编编写的复位函数Reset_Handler

Reset_Handler   PROC
EXPORT  Reset_Handler             [WEAK]
IMPORT  __main
IMPORT  SystemInit
LDRR0, =SystemInit
                BLX     R0
LDRR0, =__main
                BX      R0
ENDP

复位函数的最后会调用C库函数__main__main函数的主要工作是初始化系统的堆和栈,最后调用C中的main()函数,从而去到C的世界。

2、主流程 main

首先看main函数:

int  main (void)
{
    OS_ERR  err;

    OSInit(&err);                                               /* Init uC/OS-III.                                      */

    OSTaskCreate((OS_TCB     *)&AppTaskStartTCB,                /* Create the start task                                */
                 (CPU_CHAR   *)"App Task Start",
                 (OS_TASK_PTR ) AppTaskStart,
                 (void       *) 0,
                 (OS_PRIO     ) APP_TASK_START_PRIO,
                 (CPU_STK    *)&AppTaskStartStk[0],
                 (CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10,
                 (CPU_STK_SIZE) APP_TASK_START_STK_SIZE,
                 (OS_MSG_QTY  ) 5u,
                 (OS_TICK     ) 0u,
                 (void       *) 0,
                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                 (OS_ERR     *)&err);

    OSStart(&err);                                              /* Start multitasking (i.e. give control to uC/OS-III). */		
}

这种启动方式在野火的教程里被称作小心翼翼,十分谨慎法

这种方法是在main()函数中将硬件和RTOS系统先初始化好,然后创建一个启动任务后就启动调度器。
在启动任务里面创建各种应用任务,当所有任务都创建成功后,启动任务把自己删除,具体的伪代码实现如下:

int main (void)
{
    /* 硬件初始化 */
    HardWare_Init();		//(1)

    /* RTOS 系统初始化 */
    RTOS_Init();		//(2)

    /* 创建一个任务 */
    RTOS_TaskCreate(AppTaskCreate);		//(3)

    /* 启动RTOS,开始调度 */
    RTOS_Start();		//(4)
}

/* 起始任务,在里面创建任务 */
voidAppTaskCreate( void *arg )		//(5)
{
    /* 创建任务1,然后执行 */
    RTOS_TaskCreate(Task1);		//(6)

    /* 当任务1阻塞时,继续创建任务2,然后执行 */
    RTOS_TaskCreate(Task2);

    /* ......继续创建各种任务 */

    /* 当任务创建完成,删除起始任务 */
    RTOS_TaskDelete(AppTaskCreate);		//(7)
}

void Task1( void *arg )		//(8)
{
    while (1)
    {
        /* 任务实体,必须有阻塞的情况出现 */
    }
}

void Task2( void *arg )		//(9)
{
    while (1)
    {
        /* 任务实体,必须有阻塞的情况出现 */
    }
}
  • (1):硬件初始化。来到硬件初始化这一步还属于裸机的范畴, 我们可以把需要使用到的硬件都初始化好而且测试好,确保无误。
  • (2):RTOS系统初始化。比如RTOS里面的全局变量的初始化, 空闲任务的创建等。不同的RTOS,它们的初始化有细微的差别。
  • (3):创建一个开始任务。然后在这个初始任务里面创建各种应用任务。
  • (4):启动RTOS调度器,开始任务调度。这个时候调度器就去执行刚刚创建好的初始任务。
  • (5):我们通常说任务是一个不带返回值的无限循环的C函数, 但是因为初始任务的特殊性,它不能是无限循环的,只执行一次后就关闭。在初始任务里面我们创建我们需要的各种任务。
  • (6):创建任务。每创建一个任务后它都将进入就绪态,系统会进行一次调度, 如果新创建的任务的优先级比初始任务的优先级高的话,那将去执行新创建的任务, 当新的任务阻塞时再回到初始任务被打断的地方继续执行。反之,则继续往下创建新的任务,直到所有任务创建完成。
  • (7):各种应用任务创建完成后,初始任务自己关闭自己,使命完成。
  • (8)(9):任务实体通常是一个不带返回值的无限循环的C函数,函数体必须有阻塞的情况出现, 不然任务(如果优先权恰好是最高)会一直在while循环里面执行,其他任务没有执行的机会。

3、系统初始化函数OSInit()

在调用创建任务函数之前,我们必须要对系统进行一次初始化。
而系统的初始化是根据我们配置宏定义进行初始化的, 有一些则是系统必要的初始化,如空闲任务,时钟节拍任务等。

下面我们来看看系统初始化的主要源码:

void  OSInit (OS_ERR  *p_err)
{
    CPU_STK      *p_stk;
    CPU_STK_SIZE  size;

    if (p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }

    OSInitHook();   /*初始化钩子函数相关的代码*/

    OSIntNestingCtr= (OS_NESTING_CTR)0;     /*清除中断嵌套计数器*/

    OSRunning =  OS_STATE_OS_STOPPED;       /*未启动多任务处理*/

    OSSchedLockNestingCtr = (OS_NESTING_CTR)0;/*清除锁定计数器*/

    OSTCBCurPtr= (OS_TCB *)0;       /*将OS_TCB指针初始化为已知状态  */
    OSTCBHighRdyPtr = (OS_TCB *)0;

    OSPrioCur = (OS_PRIO)0;                 /*将优先级变量初始化为已知状态*/
    OSPrioHighRdy                   = (OS_PRIO)0;
    OSPrioSaved                     = (OS_PRIO)0;


    if (OSCfg_ISRStkSize > (CPU_STK_SIZE)0)
    {
        p_stk = OSCfg_ISRStkBasePtr;        /*清除异常栈以进行栈检查*/
        if (p_stk != (CPU_STK *)0)
        {
            size  = OSCfg_ISRStkSize;
            while (size > (CPU_STK_SIZE)0)
            {
                size--;
                *p_stk = (CPU_STK)0;
                p_stk++;
            }
        }
    }

    OS_PrioInit();  /*初始化优先级位图表*/

    OS_RdyListInit();       /*初始化就绪列表*/

    OS_TaskInit(p_err);   /*初始化任务管理器*/
    if (*p_err != OS_ERR_NONE)
    {
        return;
    }

    OS_IdleTaskInit(p_err);    /* 初始化空闲任务  */       
    if (*p_err != OS_ERR_NONE)
    {
        return;
    }

    OS_TickTaskInit(p_err);   /* 初始化时钟节拍任务*/        
    if (*p_err != OS_ERR_NONE)
    {
        return;
    }

    OSCfg_Init();
}

在这个系统初始化中,我们主要看两个地方

  • 一个是空闲任务的初始化
  • 一个是时钟节拍任务的初始化

这两个任务是必须存在的任务,否则系统无法正常运行。

1. 空闲任务的初始化

其实初始化就是创建一个空闲任务,空闲任务的相关信息由系统默认指定, 用户不能修改

void  OS_IdleTaskInit (OS_ERR  *p_err)
{
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

    OSIdleTaskCtr = (OS_IDLE_CTR)0;         //(1)
/* ---------------- CREATE THE IDLE TASK ---------------- */
    OSTaskCreate((OS_TCB     *)&OSIdleTaskTCB,
                (CPU_CHAR   *)((void *)"μC/OS-III Idle Task"),
                (OS_TASK_PTR)OS_IdleTask,
                (void       *)0,
                (OS_PRIO     )(OS_CFG_PRIO_MAX - 1u),
                (CPU_STK    *)OSCfg_IdleTaskStkBasePtr,
                (CPU_STK_SIZE)OSCfg_IdleTaskStkLimit,
                (CPU_STK_SIZE)OSCfg_IdleTaskStkSize,
                (OS_MSG_QTY  )0u,
                (OS_TICK     )0u,
                (void       *)0,
                (OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR |OS_OPT_TASK_NO_TLS),
                (OS_ERR     *)p_err);               //(2)
}
  • (1):OSIdleTaskCtr在os.h头文件中定义,是一个32位无符号整型变量, 该变量的作用是用于统计空闲任务的运行的,怎么统计呢,我们在空闲任务中讲解。现在初始化空闲任务,系统就将OSIdleTaskCtr清零。
  • (2):我们可以很容易看到系统只是调用了OSTaskCreate()函数来创建一个任务,这个任务就是空闲任务, 任务优先级为OS_CFG_PRIO_MAX-1,OS_CFG_PRIO_MAX是一个宏,该宏定义表示μC/OS的任务优先级数值的最大值,我们知道, 在μC/OS系统中,任务的优先级数值越大,表示任务的优先级越低,所以空闲任务的优先级是最低的。 空闲任务栈大小为OSCfg_IdleTaskStkSize,它也是一个宏,在os_cfg_app.c文件中定义,默认为128, 则空闲任务栈默认为128*4=512字节。

2. 空闲任务的定义

空闲任务其实就是一个函数,其函数入口是OS_IdleTask

void  OS_IdleTask (void  *p_arg)
{
    CPU_SR_ALLOC();


    /* Prevent compiler warning for not using 'p_arg'*/
    p_arg = p_arg;

     while (DEF_ON)
     {
        CPU_CRITICAL_ENTER();
        OSIdleTaskCtr++;
#if OS_CFG_STAT_TASK_EN > 0u
        OSStatTaskCtr++;
#endif
        CPU_CRITICAL_EXIT();
        /* Call user definable HOOK */
        OSIdleTaskHook();
    }
}

空闲任务的作用还是很大的,它是一个无限的死循环。
因为其优先级是最低的,所以任何优先级比它高的任务都能抢占它从而取得CPU的使用权。

为什么系统要空闲任务呢?
因为CPU是不会停下来的,即使啥也不干,CPU也不会停下来,此时系统就必须保证有一个随时处于就绪态的任务, 而且这个任务不会抢占其他任务,当且仅当系统的其他任务处于阻塞中,系统才会运行空闲任务。
这个任务可以做很多事情,任务统计, 钩入用户自定义的钩子函数实现用户自定义的功能等,但是需要注意的是,在钩子函数中用户不允许调用任何可以使空闲任务阻塞的函数接口, 空闲任务是不允许被阻塞的。

3. 时钟节拍任务的初始化

OS_TickTaskInit()函数也是创建一个时钟节拍任务

void  OS_TickTaskInit (OS_ERR  *p_err)
{
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

    OSTickCtr         = (OS_TICK)0u; /* Clear the tick counter   */

    OSTickTaskTimeMax = (CPU_TS)0u;


    OS_TickListInit();/* Initialize the tick list data structures  */

    /* ---------------- CREATE THE TICK TASK ---------------- */
    if (OSCfg_TickTaskStkBasePtr == (CPU_STK *)0)
    {
        *p_err = OS_ERR_TICK_STK_INVALID;
        return;
    }

    if (OSCfg_TickTaskStkSize < OSCfg_StkSizeMin)
    {
        *p_err = OS_ERR_TICK_STK_SIZE_INVALID;
        return;
    }
    /* Only one task at the 'Idle Task' priority              */
    if (OSCfg_TickTaskPrio >= (OS_CFG_PRIO_MAX - 1u))
    {
        *p_err = OS_ERR_TICK_PRIO_INVALID;
        return;
    }

    OSTaskCreate((OS_TCB     *)&OSTickTaskTCB,
                (CPU_CHAR   *)((void *)"μC/OS-III Tick Task"),
                (OS_TASK_PTR )OS_TickTask,
                (void       *)0,
                (OS_PRIO     )OSCfg_TickTaskPrio,
                (CPU_STK    *)OSCfg_TickTaskStkBasePtr,
                (CPU_STK_SIZE)OSCfg_TickTaskStkLimit,
                (CPU_STK_SIZE)OSCfg_TickTaskStkSize,
                (OS_MSG_QTY  )0u,
                (OS_TICK     )0u,
                (void       *)0,
                (OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS),
                (OS_ERR     *)p_err);
}

4、启动任务AppTaskStart()

系统在启动任务里面创建各种应用任务,当所有任务都创建成功后,启动任务把自己删除

static  void  AppTaskStart (void *p_arg)
{
    CPU_INT32U  cpu_clk_freq;
    CPU_INT32U  cnts;
    OS_ERR      err;


   (void)p_arg;

    BSP_Init();                                                 /* Initialize BSP functions                             */
    CPU_Init();

    cpu_clk_freq = BSP_CPU_ClkFreq();                           /* Determine SysTick reference freq.                    */
    cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;        /* Determine nbr SysTick increments                     */
    OS_CPU_SysTickInit(cnts);                                   /* Init uC/OS periodic time src (SysTick).              */

    Mem_Init();                                                 /* Initialize Memory Management Module                  */

#if OS_CFG_STAT_TASK_EN > 0u
    OSStatTaskCPUUsageInit(&err);                               /* Compute CPU capacity with no task running            */
#endif

    CPU_IntDisMeasMaxCurReset();


    OSTaskCreate((OS_TCB     *)&AppTaskLed1TCB,                /* Create the Led1 task                                */
                 (CPU_CHAR   *)"App Task Led1",
                 (OS_TASK_PTR ) AppTaskLed1,
                 (void       *) 0,
                 (OS_PRIO     ) APP_TASK_LED1_PRIO,
                 (CPU_STK    *)&AppTaskLed1Stk[0],
                 (CPU_STK_SIZE) APP_TASK_LED1_STK_SIZE / 10,
                 (CPU_STK_SIZE) APP_TASK_LED1_STK_SIZE,
                 (OS_MSG_QTY  ) 5u,
                 (OS_TICK     ) 0u,
                 (void       *) 0,
                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                 (OS_ERR     *)&err);
								 
    OSTaskCreate((OS_TCB     *)&AppTaskLed2TCB,                /* Create the Led2 task                                */
                 (CPU_CHAR   *)"App Task Led2",
                 (OS_TASK_PTR ) AppTaskLed2,
                 (void       *) 0,
                 (OS_PRIO     ) APP_TASK_LED2_PRIO,
                 (CPU_STK    *)&AppTaskLed2Stk[0],
                 (CPU_STK_SIZE) APP_TASK_LED2_STK_SIZE / 10,
                 (CPU_STK_SIZE) APP_TASK_LED2_STK_SIZE,
                 (OS_MSG_QTY  ) 5u,
                 (OS_TICK     ) 0u,
                 (void       *) 0,
                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                 (OS_ERR     *)&err);

    OSTaskCreate((OS_TCB     *)&AppTaskLed3TCB,                /* Create the Led3 task                                */
                 (CPU_CHAR   *)"App Task Led3",
                 (OS_TASK_PTR ) AppTaskLed3,
                 (void       *) 0,
                 (OS_PRIO     ) APP_TASK_LED3_PRIO,
                 (CPU_STK    *)&AppTaskLed3Stk[0],
                 (CPU_STK_SIZE) APP_TASK_LED3_STK_SIZE / 10,
                 (CPU_STK_SIZE) APP_TASK_LED3_STK_SIZE,
                 (OS_MSG_QTY  ) 5u,
                 (OS_TICK     ) 0u,
                 (void       *) 0,
                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                 (OS_ERR     *)&err);
		
		
		OSTaskDel ( & AppTaskStartTCB, & err );
		
		
}

其中需要注意的如下:

CPU_Init();
//时间戳初始化

OS_CPU_SysTickInit(cnts);
//MCU的内核定时器SysTick初始化

Mem_Init();
//内存初始化

1. 时间戳初始化

在启动任务AppTaskStart()中,有一个CPU初始化函数,CPU初始化函数可以初始化时间戳

void  CPU_Init (void)
{
/* --------------------- INIT TS ---------------------- */
#if ((CPU_CFG_TS_EN     == DEF_ENABLED) || \
    (CPU_CFG_TS_TMR_EN == DEF_ENABLED))
    CPU_TS_Init();     /* 时间戳测量的初始化   */

#endif
/* -------------- INIT INT DIS TIME MEAS -------------- */
#ifdef  CPU_CFG_INT_DIS_MEAS_EN
    CPU_IntDisMeasInit();  /* 最大关中断时间测量初始化     */

#endif

/* ------------------ INIT CPU NAME ------------------- */
#if (CPU_CFG_NAME_EN == DEF_ENABLED)
    CPU_NameInit();         //CPU 名字初始化
#endif
}

时间戳,它的精度高达ns级别,是CPU内核的一个重要资源。

在Cortex-M(注意:M0内核不可用)内核中有一个外设叫DWT(Data Watchpoint and Trace),是用于系统调试及跟踪, 它有一个32位的寄存器叫CYCCNT。
CYCCNT是一个向上的计数器,记录的是内核时钟运行的个数。
内核时钟跳动一次, 该计数器就加1,当CYCCNT溢出之后,会清零重新开始向上计数。
CYCCNT的精度非常高,其精度取决于内核的频率是多少, 如果是STM32F1系列,内核时钟是72M,那精度就是1/72M = 14ns,而程序的运行时间都是微秒级别的,所以14ns的精度是远远够的。
CYCCNT最长能记录的时间为:60s=2的32次方/72000000(假设内核频率为72M,内核跳一次的时间大概为1/72M=14ns), 而如果是STM32H7系列这种400M主频的芯片,那它的计时精度高达2.5ns(1/400000000 = 2.5)。
如果是i.MX RT1052这种比较厉害的处理器,最长能记录的时间为: 8.13s=2的32次方/528000000(假设内核频率为528M, 内核跳一次的时间大概为1/528M=1.9ns) 。

想要启用DWT外设,需要由另外的内核调试寄存器DEMCR的位24控制,写1启用,DEMCR的地址是0xE000 EDFC。
在这里插入图片描述
启用DWT_CYCCNT寄存器之前,先清零。
让我们看看DWT_CYCCNT的基地址,从ARM-Cortex-M手册中可以看到其基地址是0xE000 1004, 复位默认值是0,而且它的类型是可读可写的,我们往0xE000 1004这个地址写0就将DWT_CYCCNT清零了。

在这里插入图片描述
关于CYCCNTENA,它是DWT控制寄存器的第一位,写1启用,则启用CYCCNT计数器,否则CYCCNT计数器将不会工作,它的地址是0xE000EDFC。
在这里插入图片描述
所以想要使用DWT的CYCCNT步骤:

  • 先启用DWT外设,这个由另外内核调试寄存器DEMCR的位24控制,写1启用
  • 在启用CYCCNT寄存器之前,先清零。
  • 启用CYCCNT寄存器,这个由DWT的CYCCNTENA 控制,也就是DWT控制寄存器的位0控制,写1启用

这样子,我们就能去看看μC/OS的时间戳的初始化了

#define  DWT_CR      *(CPU_REG32 *)0xE0001000
#define  DWT_CYCCNT  *(CPU_REG32 *)0xE0001004
#define  DEM_CR      *(CPU_REG32 *)0xE000EDFC

#define  DEM_CR_TRCENA                   (1 << 24)

#define  DWT_CR_CYCCNTENA                (1 <<  0)

#if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
void  CPU_TS_TmrInit (void)
{
    CPU_INT32U  cpu_clk_freq_hz;

    /* Enable Cortex-M3's DWT CYCCNT reg. */
    DEM_CR         |= (CPU_INT32U)DEM_CR_TRCENA;

    DWT_CYCCNT      = (CPU_INT32U)0u;
    DWT_CR         |= (CPU_INT32U)DWT_CR_CYCCNTENA;

    cpu_clk_freq_hz = BSP_CPU_ClkFreq();
    CPU_TS_TmrFreqSet(cpu_clk_freq_hz);
}
#endif

2. SysTick初始化

时钟节拍的频率表示操作系统每1秒钟产生多少个tick。
tick即是操作系统节拍的时钟周期,时钟节拍就是系统以固定的频率产生中断(时基中断), 并在中断中处理与时间相关的事件,推动所有任务向前运行。
时钟节拍需要依赖于硬件定时器,在STM32 裸机程序中经常使用的SysTick时钟是 MCU的内核定时器,通常都使用该定时器产生操作系统的时钟节拍。

用户需要先在“ os_cfg_app.h”中设定时钟节拍的频率,该频率越高, 操作系统检测事件就越频繁,可以增强任务的实时性,但太频繁也会增加操作系统内核的负担加重,所以用户需要权衡该频率的设置。

我们在这里采用默认的 1000Hz(之后若无特别声明,均采用 1000 Hz),也就是时钟节拍的周期为 1 ms。

函数OS_CPU_SysTickInit()用于初始化时钟节拍中断,初始化中断的优先级,SysTick中断的启用等等,这个函数要跟不同的CPU进行编写, 并且在系统任务的第一个任务开始的时候进行调用,如果在此之前进行调用,可能会造成系统奔溃,因为系统还没有初始化好就进入中断, 可能在进入和退出中断的时候会调用系统未初始化好的一些模块

cpu_clk_freq = BSP_CPU_ClkFreq();	/* Determine SysTick reference freq. */
cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;
OS_CPU_SysTickInit(cnts); 	/*Init μC/OS periodic time src (SysTick).*/

3. 内存初始化

我们都知道,内存在嵌入式中是很珍贵的存在,而一个系统是软件,则必须要有一块内存属于系统所管理的。
所以在系统创建任务之前, 就必须将系统必要的东西进行初始化。
μC/OS采用一块连续的大数组作为系统管理的内存, CPU_INT08U Mem_Heap[LIB_MEM_CFG_HEAP_SIZE], 在使用之前就需要先将管理的内存进行初始化

Mem_Init();

5、任务调度器启动函数OSStart()

在创建完任务的时候,我们需要开启调度器。
因为创建仅仅是把任务添加到系统中,还没真正调度,那怎么才能让系统支持运行呢?
μC/OS为我们提供一个系统启动的函数接口——OSStart(),我们使用OSStart()函数就能让系统开始运行

void  OSStart (OS_ERR  *p_err)
{
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

    if (OSRunning == OS_STATE_OS_STOPPED) {
        OSPrioHighRdy   = OS_PrioGetHighest();/* Find the highest priority */
        OSPrioCur       = OSPrioHighRdy;
        OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
        OSTCBCurPtr     = OSTCBHighRdyPtr;
        OSRunning       = OS_STATE_OS_RUNNING;
        OSStartHighRdy();/* Execute target specific code to start task  */
        *p_err           = OS_ERR_FATAL_RETURN;
        /* OSStart() is not supposed to return  */
    }
    else
    {
        *p_err           = OS_ERR_OS_RUNNING; /* OS is already running */
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1556537.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot通过threadLocal+参数解析器实现保存当前用户登录信息

首先先介绍一下threadLocal ThreadLocal 线程局部变量&#xff0c;创建一个线程变量后&#xff0c;针对这个变量可以让每个线程拥有自己的变量副本&#xff0c;每个线程是访问的自己的副本&#xff0c;与其他线程的相互独立。 大致知道threadLocal就可以了&#xff0c;然后我…

Vue中使用Vuex(超详细)基本使用方法

在vue中使用vuex&#xff0c;不同的vue版本要对应使用不同的vuex&#xff0c;在这里不做详情介绍&#xff0c;想具体了解的&#xff0c;请自行度娘或者必应一下。 在使用vuex之前&#xff0c;我们创建一个新的项目&#xff0c;这里我们使用的是vue的脚手架创建一个vue项目。 …

二维码门楼牌管理应用平台建设:智能匹配与高效管理

文章目录 前言一、二维码门楼牌管理应用平台的意义二、地址坐标校验的重要性三、对外采数据匹配校验的实现方式四、智能匹配与人工审核的结合五、二维码门楼牌管理应用平台的前景展望 前言 随着城市化进程的加速&#xff0c;门楼牌管理成为城市治理中不可或缺的一环。传统的门…

keil安装器件支持包

创建keil项目时&#xff0c;发现没有我们想要的器件支持包 这时我们可以选择在线安装 1.点击安装按钮 2.点击检查更新&#xff0c;下方有进度条&#xff0c;他会安装keil支持的所有器件支持包所以比较慢

Github profile Readme实现小游戏[github自述游戏]

Github profile Readme常用于个人主页介绍&#xff0c;将它与action自动化流程结合&#xff0c;可以实现一些小游戏 例如&#xff1a;2048、五子棋 2048实现 losehu (RUBO) GitHub 五子棋 https://github.com/losehu/losehu/tree/main 通过python/C编写可执行文件&#xf…

浏览器工作原理与实践--垃圾回收:垃圾数据是如何自动回收的

在上一篇文章中&#xff0c;我们提到了JavaScript中的数据是如何存储的&#xff0c;并通过例子分析了原始数据类型是存储在栈空间中的&#xff0c;引用类型的数据是存储在堆空间中的。通过这种分配方式&#xff0c;我们解决了数据的内存分配的问题。 不过有些数据被使用之后&am…

【Gitea的介绍】

&#x1f525;博主&#xff1a;程序员不想YY啊&#x1f525; &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家&#x1f4ab; &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 &#x1f308;希望本文对您有所裨益&#xff0c;如有…

BGP实训

BGP基础配置实训 实验拓扑 注&#xff1a;如无特别说明&#xff0c;描述中的 R1 或 SW1 对应拓扑中设备名称末尾数字为 1 的设备&#xff0c;R2 或 SW2 对应拓扑中设备名称末尾数字为2的设备&#xff0c;以此类推&#xff1b;另外&#xff0c;同一网段中&#xff0c;IP 地址的主…

基于重写ribbon负载实现灰度发布

项目结构如下 代码如下&#xff1a; pom&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocat…

淘宝商品详情数据(商品分析,竞品分析,代购商城建站与跨境电商,ERP系统商品数据选品)

淘宝商品详情数据在多个业务场景中发挥着关键作用&#xff0c;以下是一些主要的应用场景&#xff1a; 请求示例&#xff0c;API接口接入Anzexi58 商品分析&#xff1a;通过对淘宝商品详情的全面分析&#xff0c;商家可以深入了解商品的属性、价格、销售量、评价等信息。这些数…

你真的会数据结构吗:二叉树

❀❀❀ 文章由不准备秃的大伟原创 ❀❀❀ ♪♪♪ 若有转载&#xff0c;请联系博主哦~ ♪♪♪ ❤❤❤ 致力学好编程的宝藏博主&#xff0c;代码兴国&#xff01;❤❤❤ halo铁汁们&#xff0c;没错又是你们人见人爱&#xff0c;花见花开的大伟啊&#xff0c;今天也是周六&#x…

node.js学习(2)

版权声明 以下文章为尚硅谷PDF资料&#xff0c;B站视频链接&#xff1a;【尚硅谷Node.js零基础视频教程&#xff0c;nodejs新手到高手】仅供个人学习交流使用。如涉及侵权问题&#xff0c;请立即与本人联系&#xff0c;本人将积极配合删除相关内容。感谢理解和支持&#xff0c;…

椋鸟数据结构笔记#3:链表

萌新的学习笔记&#xff0c;写错了恳请斧正。 目录 链表的定义 链表的分类 方向&#xff08;单向还是双向&#xff09; 头节点&#xff08;哨兵节点&#xff09;的有无 循环或不循环 8种分类 不带头单向不循环链表的实现 带头单向循环链表的实现 链表与顺序表的差异 链…

java全排列(力扣Leetcode46)

全排列 力扣原题链接 问题描述 给定一个不含重复数字的数组 nums&#xff0c;返回其所有可能的全排列。你可以按任意顺序返回答案。 示例 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 示例 2…

FME学习之旅---day17

我们付出一些成本&#xff0c;时间的或者其他&#xff0c;最终总能收获一些什么。 【FME-HOW-TO系列】28 栅格邻域函数 RasterConvolver转换器说明&#xff1a; 接受包含栅格几何对象的输入要素&#xff0c;并在对所有波段应用卷积滤波 器后输出要素。 本人对栅格数据处理的较…

从易到难,推荐9个适合练手的C++项目

老有一些同学和我说学习了 C 以后&#xff0c;想要做些项目锻炼自己&#xff0c;让我从「简单到难」都推荐一些。 那有啥说的&#xff0c;必须推荐&#xff01;毕竟 C 的优质项目我见过太多了&#xff01; 下面我就按照「从易到难」的梯度&#xff0c;依次来推荐&#xff0c;…

反应式编程(一)什么是反应式编程

目录 一、背景二、反应式编程简介2.1 定义2.2 反应式编程的优势2.3 命令式编程 & 反应式编程 三、Reactor 入门3.1 Reactor 的核心类3.2 Reactor 中主要的方法1&#xff09;创建型方法2&#xff09;转化型方法3&#xff09;其他类型方法4&#xff09;举个例子 四、Reactor …

kafka学习笔记02(小滴课堂)

Kafka命令行生产者发送消息和消费者消费消息实战 已存在的kafka不能重复创建。 broker设置的是1&#xff0c;factor大于broker了&#xff0c;所以报错。 生产者发送消息&#xff1a; kafka列表出现了新的kafka。 我们使用这个kafka。 我们启动消费者&#xff1a; 我们现在不从…

机器学习--支持向量机(通俗版本+demo)

场景 假设我们要在一个在线零售平台上自动区分商品评论是正面的还是负面的。评论中的语言多种多样&#xff0c;且往往含有大量的非结构化文本数据&#xff0c;直接使用简单的规则来分类是非常困难。这时候我们采取支持向量机算法来分类是一个比较好的选择。 支持向量机 支持…

Find Any File (FAF) for Mac:您的专属文件搜索神器

在数字时代&#xff0c;我们的Mac硬盘中堆积着各式各样的文件&#xff0c;从工作文档到家庭照片&#xff0c;从音乐视频到学习资料&#xff0c;无一不体现出我们的生活和工作的丰富多彩。然而&#xff0c;当我们需要快速找到某个特定文件时&#xff0c;却常常在茫茫文件海中迷失…