SylixOS 多核启动

建议阅读本文前,先阅读下面这篇文章:

ARM 多核启动机制详解

1、概述

关于多核启动的基本机制,前文中已经进行了较为详细的分析,这里不再展开。本篇文章的重点在于:在操作系统启动完成后,多核之间是如何进行协同工作的,以及各个 CPU 是如何进入多任务调度状态的。

具体来说,我们将重点关注以下问题:

  • 主核如何完成系统初始化并进入调度
  • 从核如何被唤醒并参与系统运行
  • 多核之间如何进行同步与协作

以下内容均基于 ARMv7 架构进行说明。

2、主核启动(单核阶段)

2.1 startup.S

SylixOS 镜像的入口通常为 startup.S(由链接脚本指定)。该阶段运行在汇编环境中,主要完成系统最早期的初始化工作,包括:

  • 定义异常/中断向量表
  • 关 CPU 中断(IRQ、FIQ)
    • 此时中断系统尚未初始化,为避免异常中断导致系统进入不可控状态,需要提前关闭
  • 设置堆栈空间
    • 后续将进入 C 语言环境执行,因此必须先建立基础栈环境
  • 清 BSS 段
  • 关闭 D-Cache/I-Cache (回写、无效)
  • 无效并关闭分支预测
  • 关闭 MMU(无效 TLB)
    • 该步骤主要是为了避免 BootLoader(如 U-Boot)中已开启的 Cache / MMU 对当前系统造成影响,从而引发一致性问题
  • 调用 bspInit,进入 C 语言初始化阶段

2.2 设置内核启动参数

c 复制代码
ULONG API_KernelStartParam(CPCHAR pcParam);

函数 API_KernelStartParam 原型分析:

  • 此函数成功返回 ERROR_NONE,失败返回错误号;
  • 参数 pcParam 是启动参数,是以空格分开的一个字符串列表。

2.3 启动内核

启动内核调用的是 API_KernelStartAPI_KernelStart 是一个宏,内容如下:

c 复制代码
#define API_KernelStart API_KernelPrimaryStart

它对应内核里的 API_KernelPrimaryStart 函数。此函数原型如下:

c 复制代码
VOID API_KernelPrimaryStart (PKERNEL_START_ROUTINE pfuncStartHook,
							 PVOID pvKernelHeapMem,
							 size_t stKernelHeapSize,
							 PVOID pvSystemHeapMem,
							 size_t stSystemHeapSize);

函数 API_KernelPrimaryStart 原型分析:

  • 此函数没有返回值;
  • 参数 pfuncStartHook 是系统启动中的用户回调;
  • 参数 pvKernelHeapMem 是内核堆内存首地址;
  • 参数 stKernelHeapSize 是内核堆大小;
  • 参数 pvSystemHeapMem 是系统堆内存首地址;
  • 参数 stSystemHeapSize 是系统堆大小。

✔️ 内核启动流程

API_KernelPrimaryStart 函数是系统内核的入口,只允许系统逻辑主核调用,其主要流程如下:

  • 初始化内核底层组件
    • 内存管理、消息队列、调度器等
  • 调用 bspIntInit 进行中断初始化
  • 初始化内核高层组件
    • 创建空闲任务、ITimer 任务、中断延迟队列、总线系统等
    • ⚠️ 注意:此阶段仅创建任务,不会立即调度执行
  • 调用 pfuncStartHook 用户回调函数
  • 调用 _KernelBootSecondary
    • 通知从核可以启动(通过共享变量实现)(这里只是告知从核可以启动,并不是启动从核
  • 调用 _KernelPrimaryEntry 接口
    • 调用 bspTickInit 进行系统 Tick 初始化
    • 激活当前 CPU、使能调度
    • 调用 archTaskCtxStart,使得系统的主核 (负责初始化的核) 进入多任务状态

此时, SylixOS 单核启动的流程基本完成

拓展:实际上,操作系统开始运行第一个任务时,当前核 CPU 中断就会被打开。archTaskCtxCreate 创建任务上下文时,默认使能 IRQ、FIQ 中断。所以当 archTaskCtxStart 启动任务时,就会开中断


✔️ 用户启动回调

mini2440 的 BSP 里实现的回调参数为 usrStartup。其调用如下:

c 复制代码
API_KernelStart(usrStartup,
				(PVOID)&__heap_start,
				(size_t)&__heap_end - (size_t)&__heap_start,
				LW_NULL, 0);

usrStartup 函数会初始化应用相关的组件,并且创建操作系统需要执行的第一个任务 halBootThread

c 复制代码
static VOID  usrStartup (VOID)
{
    LW_CLASS_THREADATTR     threadattr;
	......
#if LW_CFG_VMM_EN > 0
    halVmmInit();
#endif                                                                  /*  LW_CFG_VMM_EN > 0           */

#if LW_CFG_CACHE_EN > 0
    halCacheInit();
#endif                                                                  /*  LW_CFG_CACHE_EN > 0         */

    API_ThreadAttrBuild(&threadattr,
                        __LW_THREAD_BOOT_STK_SIZE,
                        LW_PRIO_CRITICAL,
                        LW_OPTION_THREAD_STK_CHK,
                        LW_NULL);
    API_ThreadCreate("t_boot",
                     (PTHREAD_START_ROUTINE)halBootThread,
                     &threadattr,
                     LW_NULL);                                          /*  Create boot thread          */
}

✔️ 第一个任务:halBootThread

halBootThread 是系统启动后的第一个实际运行任务,主要负责:

  • 初始化各种设备(EMMC、网卡等)驱动、总线(SPI、I2C 等)驱动
  • 初始化日志系统、符号、动态装载器等
  • 调用 PSCI 接口启动其他 CPU

✔️ 关于 VMM / Cache 初始化时机

VMM 和 Cache 的初始化并未放在 halBootThread 中,而是在更早阶段完成,原因在于:

线程创建后并不会立即调度执行

而 Cache / MMU 越早启用,对系统整体性能越有利。

3、从核启动

从核通常通过 PSCI 接口启动,并传入启动入口地址(通常与主核相同,即 startup.S)。


✔️ 启动流程

从核启动后流程如下:

  • 从 startup.S 进入
  • 判断当前 CPU 非主核,跳转至从核入口
  • 关闭 Cache / MMU / 分支预测
  • 跳转至 C 函数 halSecondaryCpuMain

halSecondaryCpuMain 函数一般只需调用从核系统内核入口函数 API_KernelSecondaryStart 即可。此函数原型如下:

c 复制代码
VOID API_KernelSecondaryStart(PKERNEL_START_ROUTINE pStartHook);

函数原型分析:

  • 此函数没有返回值;
  • 参数 pStartHook 是从核的用户回调函数,用于初始化本 CPU 基本资源,例如 MMU, CACHE, FPU 等
c 复制代码
#define KN_SECONDARY_WAIT()     _K_ulSecondaryHold != 0xdeadbeef

VOID  API_KernelSecondaryStart (PKERNEL_START_ROUTINE  pfuncStartHook)
{
    KN_SMP_MB();
    while (KN_SECONDARY_WAIT()) {
        LW_SPINLOCK_DELAY();                                            /*  短延迟并释放总线            */
    }
    
    _KernelSecondaryLowLevelInit();                                     /*  从核底层初始化              */
    
    _DebugHandle(__LOGMESSAGE_LEVEL, "kernel secondary cpu usrStartup...\r\n");
    if (pfuncStartHook) {                                               /*  用户是否要求需要初始化      */
        pfuncStartHook();                                               /*  用户系统初始化              */
    }

    _KernelSecondaryCoreStartup(LW_CPU_GET_CUR());                      /*  主核初始化完毕直接启动多任务*/
}

✔️ 主从核同步启动

API_KernelSecondaryStart 在执行过程中,会循环等待主核的启动通知(第一次等待):

c 复制代码
检测 _K_ulSecondaryHold 变量

只有当主核完成关键初始化并释放该变量后,从核才继续执行。

✔️ 执行用户回调

c 复制代码
VOID  API_KernelSecondaryStart (PKERNEL_START_ROUTINE  pfuncStartHook)
{
	......
    if (pfuncStartHook) {                                               /*  用户是否要求需要初始化      */
        pfuncStartHook();                                               /*  用户系统初始化              */
    }
    ......
}

✔️ 进入调度

从核最终调用:

c 复制代码
static VOID _KernelSecondaryCoreStartup (PLW_CLASS_CPU pcpuCur)
  • 进一步等待主核运行结束(第二次等待)
  • 激活当前 CPU、使能调度
  • 调用 archTaskCtxStart,使得当前核进入多任务状态

在 SylixOS 里,不管是主核还是从核,当他们都进入多任务状态的时候,这两者属于对等关系,不分主从。此时,整个 SylixOS 多核系统启动完成。

3.1 同步

在多核启动过程中,一个关键问题是:各 CPU 之间的启动同步与数据一致性保证

✔️ 启动同步(Boot Synchronization)

主核在 halBootThread 中通过 API_CpuUp 启动从核后,通常需要等待所有从核完成初始化。

在多核系统中,必须存在一个统一的"同步点",以保证:

  • 所有 CPU 在一致的状态下进入调度
  • 避免部分 CPU 过早运行导致资源访问异常

这一过程在系统设计中通常称为:

✅ CPU 启动同步(CPU Boot Synchronization)

✅ 多核启动屏障(Boot Barrier)

✔️ Cache 一致性问题(Cache Coherency)

由于每个 CPU 都具有独立的 L1 Cache,在多核启动过程中可能出现数据不一致问题。

因此,在关键同步点通常需要:

  • 执行 Cache 清理(clean / invalidate)
  • 确保各 CPU 看到一致的内存视图

SylixOS 中实现了 API_CacheBarrier 接口,用来多核同步 CACHE。

c 复制代码
LW_API  
INT    API_CacheBarrier (VOIDFUNCPTR             pfuncHook, 
                         PVOID                   pvArg, 
                         size_t                  stSize, 
                         const PLW_CLASS_CPUSET  pcpuset)
{
    BOOL            bLock;
    ULONG           i, ulCPUId, ulNumChk;
    PLW_CLASS_CPU   pcpu, pcpuCur;
    
    if (!pcpuset || !pfuncHook) {
        _ErrorHandle(EINVAL);
        return  (PX_ERROR);
    }
    
    if (LW_CPU_GET_CUR_NESTING()) {                                     /*  不能在中断中调用            */
        _ErrorHandle(ERROR_KERNEL_IN_ISR);
        return  (PX_ERROR);
    }
    
    ulNumChk = ((ULONG)stSize << 3);
    ulNumChk = (ulNumChk > LW_NCPUS) ? LW_NCPUS : ulNumChk;

    bLock = __SMP_CPU_LOCK();

    ulCPUId = LW_CPU_GET_CUR_ID();
    pcpuCur = LW_CPU_GET(ulCPUId);
    
    pcpuCur->CPU_bCacheBarStart = LW_TRUE;                              /*  设置当前核起始同步点        */
    pcpuCur->CPU_bCacheBarEnd   = LW_FALSE;                             /*  未开始第二阶段同步          */
    KN_SMP_MB();
    
    for (i = 0; i < ulNumChk; i++) {
        if (LW_CPU_ISSET(i, pcpuset)) {
            pcpu = LW_CPU_GET(i);
            while (!pcpu->CPU_bCacheBarStart);                          /*  自旋等待其他 CPU            */
        }
    }
    /* 回调函数通常都是刷 cache */
    pfuncHook(pvArg);                                                   /*  执行回调函数                */
    
    pcpuCur->CPU_bCacheBarEnd = LW_TRUE;                                /*  设置当前核结束同步点        */
    KN_SMP_MB();
    
    for (i = 0; i < ulNumChk; i++) {
        if (LW_CPU_ISSET(i, pcpuset)) {
            pcpu = LW_CPU_GET(i);
            while (!pcpu->CPU_bCacheBarEnd);                            /*  自旋等待其他 CPU            */
        }
    }

    __SMP_CPU_UNLOCK(bLock);
    
    return  (ERROR_NONE);
}
相关推荐
Yu_Lijing2 小时前
基于C++的《Head First设计模式》笔记——原型模式
c++·笔记·设计模式
猹叉叉(学习版)2 小时前
【系统分析师_知识点整理】 8.项目管理
笔记·项目管理·软考·系统分析师
张人玉2 小时前
C#类常用知识总结Pro
服务器·c#
hssfscv3 小时前
软件设计师 试题三 面向对象——UML事物、关系、图
笔记·学习·uml
EnglishJun3 小时前
ARM嵌入式学习(十三)--- IMX6ULL串口
arm开发·学习
yugi9878383 小时前
基于STM32F107和DP83848的TCP服务器数据收发方案
服务器·stm32·tcp/ip
番茄去哪了3 小时前
Retrofit框架调用第三方api
java·服务器·retrofit
wubba lubba dub dub7503 小时前
第四十周学习周报
学习
攻城狮在此3 小时前
MobaXterm下载安装及SSH远程连接(交换机/路由器/服务器)
linux·运维·服务器·网络