start_kernel——setup_nr_cpu_ids

本文深入解析Linux内核中CPU管理的关键概念,包括NR_CPUS、nr_cpu_ids、cpu_possible_mask等,并详细解释了如何通过位图(bitmap)管理CPU资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

kernel/smp.c

/* An arch may set nr_cpu_ids earlier if needed, so this would be redundant */
void __init setup_nr_cpu_ids(void)
{
    nr_cpu_ids = find_last_bit(cpumask_bits(cpu_possible_mask),NR_CPUS) + 1;
}

include/linux/cpumask.h

#if NR_CPUS == 1
#define nr_cpu_ids      1
#else
extern int nr_cpu_ids;
#endif

NR_CPUS不是1,所以编译extern int nr_cpu_ids;,我在源代码中没有找到nr_cpu_ids的定义,资料上说nr_cpu_ids全局变量被声明为__read_mostly属性。
nr_cpu_ids保存的是所有可处于联机状态的CPU总数。
nr_cpu_ids具有当前系统能具备的CPU数的信息,默认值为NR_CPUS值,NR_CPUS是编译时用户可设置的常量值。NR_CPUS并非当前系统内存在的CPU的数值,而是Linux内核能支持的最大CPU数的最大值。
UP(Uni-Processor)中是1,32位SMP中具有2~32的值,64位内核中具有2~64位的值。
kernel/cpu.c

#ifdef CONFIG_INIT_ALL_POSSIBLE
static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly
    = CPU_BITS_ALL;
#else
static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly;
#endif

没有定义CONFIG_INIT_ALL_POSSIBLE,所以编译

static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly;

DECLARE_BITMAP我们在http://blog.youkuaiyun.com/yin262/article/details/46774041已经分析过了,这句定义的最终结果是生车功能一个unsigned long 类型的数组,数组名为cpu_possible_bits,用位图来管理cpu个数,每一位对应一个cpu。

关于利用位图来管理cpu变量,还有以下:
include/linux/cpumask.h

/*
 * The following particular system cpumasks and operations manage
 * possible, present, active and online cpus.
 *
 *     cpu_possible_mask- has bit 'cpu' set iff cpu is populatable
 *     cpu_present_mask - has bit 'cpu' set iff cpu is populated
 *     cpu_online_mask  - has bit 'cpu' set iff cpu available to scheduler
 *     cpu_active_mask  - has bit 'cpu' set iff cpu available to migration
 *
 *  If !CONFIG_HOTPLUG_CPU, present == possible, and active == online.
 *
 *  The cpu_possible_mask is fixed at boot time, as the set of CPU id's
 *  that it is possible might ever be plugged in at anytime during the
 *  life of that system boot.  The cpu_present_mask is dynamic(*),
 *  representing which CPUs are currently plugged in.  And
 *  cpu_online_mask is the dynamic subset of cpu_present_mask,
 *  indicating those CPUs available for scheduling.
 *
 *  If HOTPLUG is enabled, then cpu_possible_mask is forced to have
 *  all NR_CPUS bits set, otherwise it is just the set of CPUs that
 *  ACPI reports present at boot.
 *
 *  If HOTPLUG is enabled, then cpu_present_mask varies dynamically,
 *  depending on what ACPI reports as currently plugged in, otherwise
 *  cpu_present_mask is just a copy of cpu_possible_mask.
 *
 *  (*) Well, cpu_present_mask is dynamic in the hotplug case.  If not
 *      hotplug, it's a copy of cpu_possible_mask, hence fixed at boot.
 *
 * Subtleties:
 * 1) UP arch's (NR_CPUS == 1, CONFIG_SMP not defined) hardcode
 *    assumption that their single CPU is online.  The UP
 *    cpu_{online,possible,present}_masks are placebos.  Changing them
 *    will have no useful affect on the following num_*_cpus()
 *    and cpu_*() macros in the UP case.  This ugliness is a UP
 *    optimization - don't waste any instructions or memory references
 *    asking if you're online or how many CPUs there are if there is
 *    only one CPU.
 */

extern const struct cpumask *const cpu_possible_mask;
extern const struct cpumask *const cpu_online_mask;
extern const struct cpumask *const cpu_present_mask;
extern const struct cpumask *const cpu_active_mask;

cpu_possible_mask:系统内可安装的最多CPU的位图
cpu_online_mask:系统内安装的CPU中,正在使用的CPU的位图
cpu_present_mask:系统内安装的CPU的位图
cpu_active_mask:处于联机状态且可以动的(migration)的CPU的位图

以上都是结构体指针类型,由相应的bitmap转化而来:
kernel/cpu.c

#ifdef CONFIG_INIT_ALL_POSSIBLE
static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly
    = CPU_BITS_ALL;
#else
static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly;
#endif
const struct cpumask *const cpu_possible_mask = to_cpumask(cpu_possible_bits);
EXPORT_SYMBOL(cpu_possible_mask);

static DECLARE_BITMAP(cpu_online_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_online_mask = to_cpumask(cpu_online_bits);
EXPORT_SYMBOL(cpu_online_mask);

static DECLARE_BITMAP(cpu_present_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_present_mask = to_cpumask(cpu_present_bits);
EXPORT_SYMBOL(cpu_present_mask);

static DECLARE_BITMAP(cpu_active_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_active_mask = to_cpumask(cpu_active_bits);
EXPORT_SYMBOL(cpu_active_mask);

转换的过程,在之前的文章里也分析了。

<think>我们正在讨论Linux内核启动过程中的smp_init函数。根据用户的问题,需要详细解释smp_init的作用和调用时机,特别是与多处理器启动相关的过程。 首先,我们需要回顾一下Linux内核启动的大致流程: 1. 内核入口点(通常是head.S或类似的汇编代码)进行底层硬件初始化,包括设置MMU等。 2. 然后跳转到start_kernel函数(位于init/main.c),这是C语言代码的入口点。 3. start_kernel函数会调用一系列初始化函数,初始化内核的各个子系统。 在start_kernel函数中,会调用smp_init函数。具体来说,smp_init的调用发生在以下步骤之后: - 各种早期初始化(如setup_arch, setup_command_line, build_all_zonelists等) - 调度器初始化(sched_init) - 时间初始化(time_init) - 控制台初始化(console_init) - 等等 而smp_init的调用时机是在rest_init函数之前,在start_kernel函数的末尾。但是,请注意,在start_kernel中,smp_init并不是直接调用的,而是通过一个中间步骤。 实际上,在start_kernel中,我们会看到以下调用顺序(以Linux 5.x内核为例): 1. start_kernel() ... // 各种初始化 -> boot_cpu_init() // 激活引导CPU -> setup_arch() // 体系结构相关的设置 -> trap_init() // 初始化中断向量 -> smp_setup_processor_id() // 设置当前处理器ID ... -> rest_init() // 启动其他初始化线程 但是,多处理器的初始化实际上是在rest_init中启动的kernel_init线程中完成的,具体来说,是在kernel_init_freeable()函数中调用smp_init()。 因此,让我们具体分析: 在start_kernel函数的最后,会调用rest_init函数。在rest_init中,会创建两个内核线程: 1. kernel_init线程(PID=1),该线程最终会演变为用户空间的init进程。 2. kthreadd线程(PID=2),负责管理其他内核线程。 而在kernel_init线程中,会调用kernel_init_freeable函数(在init/main.c中)。在这个函数中,会调用smp_init()。 所以,调用链是: start_kernel() -> rest_init() -> kernel_init() -> kernel_init_freeable() -> smp_init() 现在,我们来看smp_init函数的作用(位于kernel/smp.c): smp_init函数的主要任务是初始化多处理器系统,启动所有非引导CPU(即从处理器)。在系统启动过程中,引导处理器(BSP)会先启动,然后通过smp_init启动其他从处理器(APs)。 具体工作包括: 1. 调用idle_threads_init():为每个可能的CPU初始化idle线程(空闲线程)。 2. 调用cpuhp_threads_init():初始化CPU热插拔线程。 3. 调用smpboot_register_percpu_thread():为每个CPU注册一个启动线程(用于启动其他CPU)。 4. 调用cpu_up():依次启动所有其他CPU(从0开始,但跳过引导CPU,即0号CPU已经在运行,所以会启动1,2,...等)。 在smp_init中,最关键的一步是通过cpu_up()启动其他CPUcpu_up()函数会触发CPU热插拔状态机,将CPU从状态CPUHP_OFFLINE变为CPUHP_ONLINE。在这个过程中,会调用体系结构相关的代码(如arch/x86/kernel/smpboot.c中的do_boot_cpu)来启动从处理器。 当其他CPU启动后,它们会初始化自己的运行环境(如设置本地APIC,设置任务状态段TSS等),并进入idle循环,等待调度器分配任务。 总结: - 调用时机:在start_kernel的末尾,通过rest_init创建的内核线程kernel_init中调用kernel_init_freeable,然后在其中调用smp_init。 - 作用:初始化多处理器系统,启动所有非引导CPU。 参考引用: 用户提供的引用[2]提到:“More about it will be in the chapter about scheduler. So for this moment the start_kernel calls the rest_init function which spawns an init (kernel_init function) process and become idle process itself.” 这印证了我们的说法:start_kernel调用rest_init,然后rest_init生成init进程(即kernel_init),而原来的进程(即0号进程)成为idle进程。 因此,我们可以这样组织答案: 1. 先说明smp_init的调用位置(在kernel_init_freeable中)。 2. 解释smp_init的作用(启动所有非引导CPU)。 3. 简要描述smp_init内部的关键步骤(idle线程初始化、注册启动线程、启动其他CPU)。 另外,注意用户的问题中特别提到“与多处理器启动相关的过程”,所以重点在启动其他CPU。 最后,生成相关问题。</think>在Linux内核启动过程中,`smp_init()`函数是多处理器(SMP)系统初始化的核心环节,负责激活所有非引导CPU(Application Processors, APs)。以下是其关键作用和调用时机的详细说明: --- ### 一、`smp_init()`的调用时机 在Linux内核启动流程中,该函数在**内核初始化的后期阶段**被调用,具体位置如下: ```mermaid graph TD A[start_kernel] --> B[rest_init] B --> C[kernel_init线程] C --> D[kernel_init_freeable] D --> E[smp_init] %% 多处理器初始化 ``` 1. **入口点**: 由`start_kernel()`(C语言入口)触发`rest_init()`,创建第一个用户进程线程`kernel_init`(PID=1)[^2]。 2. **关键调用路径**: `kernel_init` → `kernel_init_freeable()` → **`smp_init()`** (代码位置:`init/main.c`) 3. **时序特点**: - 在**调度器初始化**(`sched_init()`)和**控制台初始化**(`console_init()`)之后 - 在**用户空间初始化**(`init`进程启动)之前 - 仅当内核配置`CONFIG_SMP=y`(支持SMP)时生效 --- ### 二、`smp_init()`的核心作用 #### 1. 启动所有非引导CPU ```c // kernel/smp.c void __init smp_init(void) { for (int cpu = 1; cpu < nr_cpu_ids; cpu++) { cpu_up(cpu); // 激活每个AP } } ``` - 遍历除引导CPU(通常为CPU0)外的所有CPU - 调用`cpu_up()`触发AP的启动序列(通过IPI中断) #### 2. 初始化CPU拓扑结构 - 构建`cpu_sibling_map`(物理核心映射) - 建立`cpu_core_map`(CPU核心拓扑) - 为调度器提供负载均衡依据 #### 3. 注册每CPU线程 ```c smpboot_register_percpu_thread(&smpboot_threads); ``` - 为每个CPU创建**空闲线程**(`idle_threads`) - 初始化**热插拔线程**(CPU hotplug) #### 4. 启动CPU热插拔机制 - 注册`cpuhp`(CPU HotPlug)回调函数 - 使能动态CPU增删功能(如虚拟机热迁移) --- ### 三、多处理器启动流程详解 当`smp_init()`调用`cpu_up()`时,触发以下硬件级操作: 1. **发送IPI中断**:引导CPU通过APIC向目标AP发送**INIT-SIPI-SIPI**中断序列 2. **AP启动代码**:AP从实模式开始执行,定位到`startup_32`(x86)或`secondary_startup`(ARM) 3. **初始化AP环境**: - 加载GDT/IDT - 启用MMU和缓存 - 设置每CPU数据区(`per_cpu`) 4. **进入调度循环**:AP最终调用`cpu_startup_entry()`成为空闲线程 --- ### 四、技术难点与设计考量 1. **同步问题**: 通过`atomic`操作确保AP启动前关键资源(如内存屏障)已就绪 ```c while (!cpumask_test_cpu(cpu, &cpu_callin_mask)) cpu_relax(); // 等待AP响应 ``` 2. **错误处理**: 超时未响应的CPU会被标记为`offline`(`/sys/devices/system/cpu/offline`) 3. **性能优化**: 并行启动AP(通过`bringup_nonboot_cpus()`异步触发) --- ### 总结 `smp_init()`是Linux SMP系统的**核心激活点**,它: 1. **时机**:在单核基础初始化完成后调用,位于`kernel_init_freeable()`中 2. **作用**:启动所有AP、构建CPU拓扑、初始化调度基础设施 3. **意义**:将系统从单核状态切换到多核并行状态,为后续多任务调度奠定基础[^2]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值