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

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


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


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

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 */
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在ubuntu上搭建系统监控系统

大纲 数据生产方安装和运行验证 数据收集、存储和分发方下载和解压修改配置运行验证 数据消费方下载和运行验证新增数据源新增看板关联看板和数据源效果展现 参考资料 在一个监控系统中&#xff0c;一定会有“数据生产方”和“数据消费方”存在。“数据生产方”用于产出需要监控…

三个表的联合查询的场景分析-场景4:c表维护a和b表的id关联关系(一对多)

基础SQL演练&#xff0c;带详细分析&#xff0c;笔记和备忘。 目录 背景介绍 表数据 需求1&#xff1a;查询g表所有记录&#xff0c;以及关联的h的id 需求2&#xff1a;在需求1基础上&#xff0c;查出关联的h的其它字段&#xff08;name&#xff09; 需求3&#xff1a;在需…

Java基本语法(变量,数据类型,关键字、)

目录 什么是变量 声明 声明方式 赋值方式 声明的同时进行赋值 变量命名规范 字符组成&#xff1a;变量名可以包含以下字符&#xff1a; 开头限制&#xff1a; 空格禁止&#xff1a; 关键字/保留字&#xff1a; 大小写敏感&#xff1a; 长度限制&#xff1a; 推荐风…

软考101-上午题-【信息安全】-网络安全

一、网络安全 1-1、安全协议 SSL(Secure Socket Layer&#xff0c;安全套接层)是 Netscape 于 1994年开发的传输层安全协议&#xff0c;用于实现 Web 安全通信。1996 年发布的 SSL3.0 协议草案已经成为一个事实上的Web 安全标准。 端口号是43。 SSL HTTP HTTPS TLS(Transpo…

FL Studio21中文版百度云网盘下载及切换中文语言教程

FL Studio 21&#xff0c;即广为人知的“水果”软件&#xff0c;拥有众多强大的功能&#xff0c;满足了音乐制作人在创作过程中的各种需求。 首先&#xff0c;它具备出色的多轨道音频录制功能&#xff0c;能够同时处理多个音频轨道的录制&#xff0c;非常适合制作复杂的音乐作…

K8s Pod亲和性、污点、容忍度、生命周期与健康探测详解(中)

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Kubernetes航线图&#xff1a;从船长到K8s掌舵者》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 在上一章节中&#xff0c;我们详细探讨了Pod的亲和性&…

基于 StarRocks 的风控实时特征探索和实践

背景 金融风控特征是在金融领域中用于评估和管理风险的关键指标。它们帮助金融机构识别潜在风险&#xff0c;降低损失&#xff0c;并采取措施规避风险。例如&#xff0c;用户最后一次授信提交时间就是一个重要的金融风控特征。 金融风控实时特征场景是一个典型的大数据实时业务…

代码随想录算法训练营 DAY 24 | 回溯理论基础 77.组合 + 剪枝优化

回溯理论 回溯法就是递归函数&#xff0c;纯暴力搜索 解决的问题 组合&#xff08;无顺序&#xff09; 1 2 3 4 给出大小为2的所有组合 切割字符串 子集问题 1 2 3 4&#xff0c;子集有1 2 3 4,12,13,14&#xff0c;…123 124… 排列&#xff08;有顺序&#xff09; 棋盘…

OpenAI发布Voice Engine模型!用AI合成你的声音!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

合集:JS异步的六个解决方案详解。

Hello&#xff0c;各位老铁&#xff0c;最近发表了js异步的解决方案&#xff0c;是分开发的&#xff0c;这次我把他汇总起来&#xff0c;方便大家收藏、查看&#xff0c;欢迎点赞评论私信交流。 01.详解&#xff1a;JS异步解决方案之回调函数&#xff0c;及其弊端 02.详解&…

函数指针的运用

这段代码使用了函数指针&#xff0c;实现了根据用户输入的命令选择不同的操作&#xff0c;并对两个数进行相应的处理。以下是代码的总结&#xff1a; getMax, getSmall 和 getSum 函数分别用于获取两个数中的较大值、较小值和它们的和。 dataHandler 函数接收两个数据 data 和…

ElementUI表格table组件实现单选及禁用默认选中效果

在使用ElementUI&#xff0c;需要ElementUI表格table组件实现单选及禁用默认选中效果, 先看下效果图&#xff1a; 代码如下&#xff1a; <template><el-tableref"multipleTable":data"tableData"tooltip-effect"dark"style"widt…

2024 ccfcsp认证打卡 2022 03 02 出行计划

import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt(); // 出行计划数目int m sc.nextInt(); // 查询个数int k sc.nextInt(); // 等待核酸检测结果所需时间final int N 200010;i…

ROS 2边学边练(4)-- 何为主题(topics)

概念 主题是一种节点间的通信方式&#xff0c;某个节点充当发布特定&#xff08;主题&#xff09;消息&#xff08;数据&#xff09;的角色&#xff0c;另外一些节点则可以订阅接收该特定&#xff08;主题&#xff09;消息&#xff08;数据&#xff09;。两者&#xff0…

Centos JDK1.8 下载安装

https://www.oracle.com/java/technologies/javase/javase8u211-later-archive-downloads.html 一 RPM包安装 rpm -ivh jdk-8u391-linux-x64.rpm /etc/profile export JAVA_HOME/usr/java/jdk1.8.0-x64 export PATH$JAVA_HOME/bin:$PATHsource /etc/profile二 tar.gz 包手动…

如何在极狐GitLab 配置 邮件功能

本文作者&#xff1a;徐晓伟 GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 本文主要讲述了在极狐GitLab 用户…

封装性练习

练习 1 &#xff1a; 创建程序&#xff1a;在其中定义两个类&#xff1a; Person 和 PersonTest 类。定义如下&#xff1a; 用 setAge() 设置人的合法年龄 (0~130) &#xff0c;用 getAge() 返回人的年龄。在 PersonTest 类中实例化 Person 类的对象 b &#xff0c;调用 set…

基于Web的社区医院管理服务系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目持续更新中..... 2024年计算机毕业论文&#xff08;设计&#xff09;学生选题参考合集推荐收藏&#xff08;包含Springboot、jsp、ssmvue等技术项目合集&#xff09; 1. 系统功能…

模型 可编程思想

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_总纲目录。一切皆有可能。 1 可编程思想的应用 1.1 自动化智能投资顾问服务 传统的财富管理服务通常需要专业的财务顾问来为客户提供投资建议和资产管理服务。随着技术的发展&#xff0c;越来越多的投资者开始寻求…

【群晖】白群晖如何公网访问

【群晖】白群晖如何公网访问 ——> 点击查看原文 在使用默认配置搭建好的群晖NAS后&#xff0c;我们可以通过内网访问所有的服务。但是&#xff0c;当我们出差或者不在家的时候也想要使用应该怎么办呢&#xff1f; 目前白群提供了两种比较快捷的方式&#xff0c;一种是直接注…