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

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.dtcms.com/a/359037.html

    相关文章:

  • LINUX-网络编程-TCP-UDP
  • Python 入门 Swin Transformer-T:原理、作用与代码实践
  • AI + 行业渗透率报告:医疗诊断、工业质检领域已进入规模化落地阶段
  • 通过数据蒸馏打破语音情感识别的资源壁垒
  • 基于单片机音乐喷泉/音乐流水灯/音乐播放器设计
  • 移动零,leetCode热题100,C++实现
  • SpringCloud Alibaba Sentinel 流量治理、熔断限流(四)
  • 【源码】智慧工地系统:智能化施工现场的全新管理方案
  • 第十七章 ESP32S3 SW_PWM 实验
  • 深入解析Nginx常见模块2
  • web渗透之RCE漏洞
  • 针对 “TCP 会话维持与身份验证” 的攻击
  • (二)设计模式(Command)
  • SQL Server 临时表合并与数量汇总的实现方法
  • 大模型不听话?试试提示词微调
  • “可选功能“中找不到 OpenSSH, PowerShell 命令行来安装OpenSSH
  • windows 谷歌浏览器一直提示无法更新Chrome弹窗问题彻底解决
  • Learning Curve|学习曲线
  • 数据库攻略:“CMU 15-445”Project0:C++ Primer(2024 Fall)
  • 【开题答辩全过程】以 “与我同行”中华传统历史数字化平台的设计和分析-------为例,包含答辩的问题和答案
  • Linux软件定时器回顾
  • 本地部署开源媒体服务器 Komga 并实现外部访问( Windows 版本)
  • 容器存储驱动升级:美国VPS文件系统优化全指南
  • 上海我店模式的多维度探究
  • 对于STM32工程模板
  • CRM、ERP、HRP系统有啥区别?
  • 250830-Docker从Rootless到Rootful的Gitlab镜像迁移
  • 深刻理解软硬件链接
  • ubuntu24.04 qt6安装
  • 学习游戏制作记录(各种优化)