当前位置: 首页 > ds >正文

Linux kernel 多核启动

平时看smp系统,比如多个A78核心同时跑起来,很多人会研究多核调度,但是也会比较好奇在什么时候多核被上电的,然后加入linux的smp系统的,今天来研究一下到底是如何从一个boot core到多个core跑起来。

线索1:start kernel 最后一点 rest_init

init(PID 1)

  • 起点是 kernel_init()init/main.c),先完成一堆 “freeable” 的内核初始化(kernel_init_freeable()),挂载根、准备用户态环境……

  • 然后按顺序尝试执行用户空间 init:run_init_process()(systemd、/sbin/init…)。

  • 代码点:

    • init/main.c: kernel_init() / kernel_init_freeable() / run_init_process()

  • 进入用户态后就从“内核态函数”转成真正的 /sbin/init 进程,继续整个用户空间的启动(比如 systemd 主循环)。

kthreadd(PID 2)

  • 主体在 kernel/kthread.c:kthreadd(),循环处理 kthread_create() 队列,把请求的内核线程创建好并唤醒运行。

  • 之后系统中各种内核子系统(block、rcu、workqueue、net、fs…)创建的 kthread 都由它“接生”。

  • 可截图:kernel/kthread.c 中的 kthreadd()kthread() 与相关创建/唤醒路径。

idle(swapper/0,PID 0)

  • 就是当前引导任务自身,“改行”去跑 cpu_startup_entry()do_idle()

  • 它负责在 CPU 空闲时节能、进入 C-states/停时钟 tickless、处理 IPI 唤醒等。

  • 位置:kernel/sched/idle.c

  • 注意:SMP 场景下,其它 CPU 上线后,每个 CPU 都会有自己的 swapper/N idle 线程;而 rest_init() 并不负责拉起其他 CPU,多核上线在后续 smp_init() 流程(PSCI/BL31 交互)里完成。

线索2:cpu 相关操作集合

arm64架构先对于cpu的操作集合

可以看到init prepare boot三巨头,不仅usb pcie可以热插拔,cpu也可以热插拔,这帮程序员真的是精力充沛,啥东西都搞的框架一套一套的,功能打磨的越来越完善,热插拔就是加了disable die kill相关ops。

基本上都是通用的psci接口,这一套psci的架构在arm的atf bl31和scp固件都有非常好的支持,走成熟的框架比自己diy要舒服,就算上了虚拟化也一堆人进行适配。

可以看到psci_ops又是一个单独定义的东西,其中cpu_on函数指针是cpu启动的关键

这里一路追踪到了invoke_psci_fn回调函数上面,这个东西究竟是又是谁赋值的,又是一层函数指针?

线索3:cpu init与prepare时机

在start_kernel中有设置架构相关准备

其中有cpu的初始化

其实就是挨个cpu进行一下setup

具体的setup就是调用cpu ops提供的init函数

那么prepare怎么没人执行,我们倒推一下调用链

原来是在rest init后面搞的

其中的freeable

线索4:cpu on 时机

结合上图的smp_init继续探索

先试一下并行启动再试一下串行启动

串行启动

注意这里有cpu_up _cpu_up __cpu_up小心迷路

两个下划线的,这个是arm架构相关的,不带和一个下划线的是通用的

task_struct都掺和进来了

这里终于调用到了cpu_boot回调函数

线索5:从_cpu_up到__cpu_up

 

start_kernel setup_arch psci_dt_init init_bootcpu_ops smp_init_cpus arch_call_rest_init rest_init kernel_init kernel_init_freeable smp_init bringup_nonboot_cpus _cpu_up

分析了好多代码上电的线索在_cpu_up断开了,我知道__cpu_up就在身边,但是近在眼前远在天边,今天非要揪出来怎么联动的,gpt了一下,解释如下。

cpu_up()

  • 位置kernel/cpu.c

  • 作用:这是 对外的统一接口,用于把一个指定的 CPU 拉起来。比如热插拔、启动时 bring-up 都会走这里。

  • 代码特点

    • 会做合法性检查(目标 CPU 是否离线、是否允许上线)。

    • 获取全局 cpu_add_remove_lock,保证并发安全。

    • 调用内部 _cpu_up()

_cpu_up()

  • 位置kernel/cpu.c

  • 作用:作为 cpu_up() 的内部实现,主要封装了一些 上下文管理

    • 调用 cpuhp_up_callbacks(),走 CPU 热插拔的 state machine

    • 每个 CPU hotplug state(如 CPUHP_BRINGUP_CPU, CPUHP_AP_ONLINE_DYN)都会有回调,逐步完成从硬件 bring-up 到调度器可用。

    • 真正执行 bring-up 的核心调用是 __cpu_up(), 但它是嵌在 state machine 里被触发的。

__cpu_up()

  • 位置:架构相关文件

    • ARM64:arch/arm64/kernel/smp.c

    • x86:arch/x86/kernel/smpboot.c

  • 作用架构相关的核心逻辑,直接发出启动 CPU 的请求。

    • ARM64 下会调用 smp_boot_secondary()

    • smp_boot_secondary() → 通过 PSCI 调用 ATF/BL31 的 CPU_ON,指定次级 CPU 的入口地址。

    • 次级 CPU 被唤醒,从 secondary_startup() 进入 Linux。

那总的看起来是状态机控制的通用到架构相关的cpu操作

  1. 背景

  • 早期内核(4.10 以前)CPU 启动/下线代码分散在 smp.c / cpu.c 等处,逻辑混乱,驱动/子系统很难挂钩。

  • 为了解耦,Linus 接受了 Thomas Gleixner 的补丁,把 CPU bring-up/down 抽象成 一条状态机 (cpuhp)

  • 每个子系统(调度器、RCU、timer、irqchip、arch bring-up …)在对应的状态点注册回调函数,保证顺序和依赖。


  1. 状态机设计

  • 核心数据结构enum cpuhp_state (定义在 include/linux/cpuhotplug.h

  • 每个状态对应一个阶段,比如:

    • 状态机由 cpuhp_up_callbacks() / cpuhp_down_callbacks() 驱动。


    1. 典型路径:cpu_up(cpu)

    走的是 CPUHP_OFFLINECPUHP_ONLINE 的正向状态迁移:

     
    

    cpu_up(cpu) └─> _cpu_up(cpu) └─> cpuhp_up_callbacks(cpu) └─> 依次执行状态机: - CPUHP_BRINGUP_CPU - CPUHP_AP_IDLE_DEAD - CPUHP_AP_SCHED_STARTING - ... - CPUHP_AP_ONLINE_IDLE - CPUHP_ONLINE

    • 每个状态点如果有回调,就会执行。

    • 如果某一步失败,会回滚状态机(调用对应的 teardown 回调),保证一致性。

    我们可以看到cpuhp_invoke_callback_range里有个循环调用cpuhp_invoke_callback的过程,在callback函数中会调用step的single方法,这里的hp是hotplugin的缩写,st的结构体cpuhp_step定义如下

    终于在这个地方联系上了之前两个下划线的那个__cpu_up了


    1. 重要状态说明(上线路径)

    • CPUHP_BRINGUP_CPU

      • __cpu_up() 触发,走进 smp_boot_secondary()

      • 此时会通过 PSCI 调 BL31 → CPU_ON,上电次级核。

    • CPUHP_AP_IDLE_DEAD

      • 次级 CPU 被唤醒后,从 secondary_startup() 进入 Linux。

      • 在这里等待被标记为 online,类似一个 “idle but not yet scheduled”。

    • CPUHP_AP_SCHED_STARTING

      • 调度器初始化次级 CPU 的运行队列(sched_cpu_starting())。

      • 这之后,调度器知道这个 CPU 存在,可以调度任务上去。

    • CPUHP_AP_ONLINE_IDLE

      • 把 CPU 标记为 online,进入 idle task 循环。

      • 此时 CPU 已经 fully usable,但还没有用户进程跑上来。

    • CPUHP_ONLINE

      • 最终状态。

      • 代表 CPU 已经完全对系统开放,可以执行普通任务。


    1. 状态机枚举定义

      1. include/linux/cpuhotplug.h

      2. 搜索 enum cpuhp_state,截一段关键枚举。

    2. 状态机执行逻辑

      1. kernel/cpu.c

      2. 函数 cpuhp_up_callbacks(),可以截图 while 循环里执行状态回调的部分。

    3. 状态回调注册

      1. 各子系统在 init 时会调用 cpuhp_setup_state() 注册自己的回调。

      2. 例如调度器在 kernel/sched/core.c 注册 sched_cpu_starting()

    4. 次级 CPU 启动入口

      1. arch/arm64/kernel/smp.csecondary_start_kernel()

      2. 这是 CPU 上电后真正开始执行 Linux 的地方,从上电到被c代码初始化过,arm自己有一套处理办法

    线索6:cpu on 如何到 psci

    我们找了半天发现终于cpu on了,但是cpu on到底执行到哪里去了还没找到,至少要追踪到操作寄存器吧,内核为了兼容各种各样的架构,各种各样的需求,各种各样的功能驱动,封装的层数太多了,一个操作要找半天,不过要搞这个还是只能继续学习了。

    设备树中的定义:

    对应驱动代码的定义:

    我们看到psci_dt_init做的事情是通过of_find_matching_node_and_match在dts中查找要用哪个psci_of_match data,然后调用它,这个psci_of_match.data其实是个函数

    终于找到了大名鼎鼎的smccc陷入bl31 psci电源管理的接口咯

    SMCCC 全称是 SMC Calling Convention,中文一般称作 SMC 调用约定

    它是 ARM 定义的一个标准,描述 操作系统/Hypervisor 与安全世界(EL3/EL2/TrustZone 固件)之间的调用接口规范

    • SMC (Secure Monitor Call):一种特殊指令,用来从普通世界(Non-secure world)切换到安全世界。

    • SMCCC:规定了 SMC 调用时 参数传递、返回值、寄存器使用、调用 ID 编码方式 等,确保不同固件/内核/Hypervisor 之间的兼容性。

    • 在 Linux 里,相关头文件在 include/linux/arm-smccc.h

    • ARM 官方文档叫: "ARM Secure Monitor Call Calling Convention (SMCCC)"

    线索7:bl31 怎么调到 psci

    bl31有bl31_entrypoint和bl31_warm_entrypoint两个entry,二者都会通过el3_entrypoint_common设置runtime_exceptions作为vector地址(tf-a/bl31/aarch64/bl31_entrypoint.S)

    在runtime_exceptions的sync_handler64中,会加载rt_svc_descs_indices,进而得到rt_svc_descs的index,进入rt_svc_descs对应entry中

    关于rt_svc_descs和psci的关系,大概整理如下

     
    

    bl31_main //tf-a/bl31/bl31_main.c runtime_svc_init //tf-a/common/runtime_svc.c rt_svc_descs = (rt_svc_desc_t *) RT_SVC_DESCS_START; rt_svc_desc_t *service = &rt_svc_descs[index]; service->init

    上述的service->init就是setup函数tf-a/include/common/runtime_svc.h

    在setup函数中tf-a/services/std_svc/std_svc_setup.c,psci_setup调用了plat_setup_psci_ops接口,这个也是各个平台可以自己实现的一个函数接口,在定义各类psci的ops

    http://www.xdnf.cn/news/19424.html

    相关文章:

  • Tomcat 企业级运维实战系列(六):综合项目实战:Java 前后端分离架构部署
  • 〔从零搭建〕数据中枢平台部署指南
  • 汽车加气站操作工证考试的复习重点是什么?
  • 如何取得专案/设计/设定/物件的属性
  • ETCD学习笔记
  • 手表--带屏幕音响-时间制切换12/24小时
  • 从零开始学习单片机18
  • 《云原生架构从崩溃失控到稳定自愈的实践方案》
  • 消费 $83,用Claude 实现临床护理系统记录单(所见即所得版)
  • C++三方服务异步拉起
  • MySQL函数 - String函数
  • Google Protobuf初体验
  • 深层语义在自然语言处理中的理论框架与技术融合研究
  • 使用电脑操作Android11手机,连接步骤
  • Python爬虫实战:研究统计学方法,构建电商平台数据分析系统
  • 面经分享--小米Java一面
  • 具有类人先验知识的 Affordance-觉察机器人灵巧抓取
  • STM32 之GP2Y1014AU0F的应用--基于RTOS的环境
  • 老题新解|不与最大数相同的数字之和
  • PCB 局部厚铜工艺:技术升级与新兴场景应用,猎板加工亮点
  • 同步/异步日志库
  • 响应式编程框架Reactor【4】
  • Web 聊天室消息加解密方案详解
  • open webui源码分析13-模型管理
  • 数据结构--栈(Stack) 队列(Queue)
  • Python API接口实战指南:从入门到精通
  • Linux查看有线网卡和无线网卡详解
  • 【Linux】基础I/O和文件系统
  • 初学者如何学习项目管理
  • 计算机毕设javayit商城 基于SSM框架的校园二手交易全流程管理系统设计与实现 Java+MySQL的校园二手商品交易与供需对接平台开发