cpu hotplug 架构 --- Linux Kernel 实现之美

本文详细探讨了CPU从offline到online的状态变化过程,包括时钟、软中断、cpufreq等多个模块的处理。通过一个从最高能级cpu_active到cpu_offline状态的实验,解释了Linux内核中这一复杂流程。

以前的博文
https://blog.youkuaiyun.com/leesagacious/article/details/107734812?spm=1001.2014.3001.5501
在这里插入图片描述

cpu从offline到online 是一个循序渐进的过程,崎岖坎坷, 包含了时钟、软中断、cpufreq … 多个模块的处理, 就像一个人跨越社会阶层一样. 不是一蹴而就的.

ok, 如果你偏要杠精说: 女生嫁人实现跃迁是一蹴而就, 那其实是生育议价权的原因, 一个学生高考从村庄到城市的逆袭是多年学习的结果, ‘更有极端的通过买彩票来实现一夜暴富, 不知道交了多少智商税. 中奖的概率比被雷劈还小, 😄, 去买吧,买个梦想,买个痴心妄想 .

先来看一个cpu_hp 状态跌落实验. 这是从最高能级cpu_active落到cpu_offline状态.

如果你没有明白, 下面帮你简单理解:
所谓的跃迁或跌落指的是从一个状态转变到另一个状态的过程.
ok, 是不是有点像波尔原子能级跃迁模型了 ? 😄,
不过这里从高能级跌落到低能级对外"释放"的是移动设备续航最重要的 电.
注意了, 原子核没有动,即cpu还是处于EL1状态(或EL2、EL3), 或ring0状态(X86).
不废话了, 看下面的小实验吧.

在这里插入图片描述
在这里插入图片描述

/*
 * 该函数在系统启动前期被bootstrap cpu 执行.
 * bp 是嫡出, AP是庶出. smp环境中也是嫡长子继承制啊, 其他的cpu就等着bp发出IPI.
 * 欲戴王冠,必承其重, 解压内核、系统初始化等任务AP义不容辞,BP们则干瞪眼.😄
 */
void __init smp_init(void)
{
   /*
    *  static DEFINE_PER_CPU(struct task_struct *, idle_threads);
    * 
    *  为每个AP创建一个idle进程. idle_threads它是一个 PER CPU 类型.
    *  PER CPU 变量唯一的缺点是 被访问时 要禁止抢占. 空间换效率不说, 除此之外, 它很让人满意.
    *  这是linux kernel 一个精彩的特性 !
    * 
    *  ok, 为什么要为AP们创建 idle thread ?
    *  如果cpu 运行队列中没有可以运行的进程了, 那需要向其他cpu做均衡, 如果大家都很闲, 直接执行idle thread了
    */
    idle_threads_init();
   /*
   	* 核心来了!  
   	* cpu hp thread. 它是一个内核线程.
   	* 执行 kthread_create_on_cpu()来实现, 在指定的cpu上创建 kthread, 且处于park状态.
   	* 这是cpu亲和性的一个应用场景. 但是这里作者考虑的却不是想着利用cpu缓存这个优势.
   	* 这里不允许cpuhp kthread 自由恋爱.就直接相亲一个cpu从一而终了 😄😄
   	* 
    * 现在过年回家, 发现绝大多数都是相亲的, 的确相亲比自由恋爱更能容易结婚. 
    * 不管婚后相处咋样, 男多女少大环境下,娶个wife急迫的人太多了. 
    * 三观啥的抛一边, 一年以后抱baby! ok, 完成人生一大任务.
    */
    cpuhp_threads_init(void);

   /*
    * 啥也不说了, 看内核做服务做的真周到, 直接告诉你, 要喊醒AP让他们起来干活了.
	*/
	pr_info("Bringing up secondary CPUs ...\n");

   /*
    *  该函数实现的很是利索. 直接执行 cpu_up(),AP们跃迁到 CPU_ONLINE状态.
    *  到了这里已经很明显了, 启动阶段的 Linux kernel 是风格干脆、不瞻前顾后的, BP以后直接就是AP,  
    * 
	*  ok, 从 uboot 作为火箭牺牲自己把 kernel img 这颗卫星送到轨道到现在所有的cpu都处于online state, 
	*  不容易呀, 就像韦伯红外望远镜一样,怎么wake up自身的探测设备、调整增益天线的幅度等等. 到这里阶段性任务已经全部结束了 
	* 
	* 一言以蔽之, uboot做的了初一, kernel就做的了十五 !
	* 这是kerenl init 线程的功劳 !
	* 
	*  接下来就是所有内核进程的鼻主,杰出的kthreadd 进程要诞生了 ! 😄
	*  一旦kthreadd产生, 整个启动就完成了. 韦伯望远镜就开始工作模式了.
   	*/
	bringup_nonboot_cpus(setup_max_cpus);
}
/*
 * 目标很明确, 就是让该cpu的热插拔状态逾跃迁到 target状态.
 * 路很遥远. 每一个状态都是一个坎, 都是一个目标, 都是cpu提升服务能力的一次升级.
 * 当然了,终极目标就是 CPU_ONLINE, 坎坷比玄奘大师的九九八十一难还要多. 
 */
static int cpu_up(unsigned int cpu, enum cpuhp_state target)
{
	/*
	 * 这是 kernel的一个惯用伎俩, 上来啥也不说, 先考虑返回值.
	 * 有错误该函数自己处理不了就往外抛给caller, 没错误就继续执行下去.
	 */
    int err = 0;
    
    /*
     * cat /sys/devices/system/cpu/possible 
     */
	if (!cpu_possible(cpu)) {
			pr_err("can't online cpu %d because it is not configured as may-hotadd at boot time\n", cpu);
	#if defined(CONFIG_IA64)
			pr_err("please check additional_cpus= boot parameter\n");
	#endif
			return -EINVAL;
		}


out:
    return err;
}
<think>我们被要求回答如何在Linux上配置和搭建kernel gdb调试环境。根据提供的引用,我们可以总结出以下步骤: 1. 编译内核:需要配置和编译内核,以便带有调试信息。 2. 制作根文件系统(rootfs):可以使用busybox来制作一个简单的根文件系统。 3. 使用QEMU启动编译好的内核和根文件系统。 4. 使用GDB连接QEMU进行调试。 我们将按照这些步骤详细说明。 步骤1:编译内核 - 下载内核源码(可以从官网或git仓库获取) - 配置内核,确保开启调试信息(在配置中,Kernel hacking -> Compile-time checks and compiler options -> 选中Compile the kernel with debug info) - 编译内核 步骤2:制作根文件系统 - 下载busybox源码并编译 - 创建rootfs目录,将busybox安装到该目录 - 创建必要的设备文件(如/dev/console等)和初始化脚本(如init) 步骤3:使用QEMU启动内核 - 使用QEMU命令启动内核,指定内核镜像和rootfs,并开启调试端口(例如:-S -s 表示暂停CPU并监听1234端口) 步骤4:使用GDB调试 - 在另一个终端启动GDB,加载内核符号表(vmlinux- 连接到QEMU(target remote:1234) - 设置断点(如b start_kernel)进行调试 下面我们详细展开每个步骤。 ### 详细步骤 #### 1. 编译内核 假设我们使用linux-5.4.34版本的内核。 ```bash # 下载内核源码(示例使用wget,也可以使用git) wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz tar xvf linux-5.4.34.tar.xz cd linux-5.4.34 # 配置内核,可以选择默认配置(如x86_64_defconfig)并开启调试信息 make x86_64_defconfig # 使用menuconfig打开配置菜单,确保调试信息开启 make menuconfig ``` 在menuconfig中,定位到: Kernel hacking ---> Compile-time checks and compiler options ---> [*] Compile the kernel with debug info 保存配置后编译: ```bash make -j$(nproc) # 使用多核编译 ``` 编译完成后,内核镜像文件为`arch/x86/boot/bzImage`(以x86架构为例),同时,符号表文件为当前目录下的`vmlinux`。 #### 2. 制作根文件系统 使用busybox制作一个简单的rootfs。 首先下载并编译busybox: ```bash wget https://busybox.net/downloads/busybox-1.32.1.tar.bz2 tar xvf busybox-1.32.1.tar.bz2 cd busybox-1.32.1 make menuconfig ``` 在busybox配置中,确保设置为静态编译: Settings ---> [*] Build static binary (no shared libs) 然后编译并安装到rootfs目录: ```bash make -j$(nproc) && make install ``` 接下来创建rootfs目录结构: ```bash cd .. mkdir rootfs cd rootfs cp -r ../busybox-1.32.1/_install/* . mkdir dev proc sys sudo mknod dev/console c 5 1 sudo mknod dev/ram b 1 0 sudo mknod dev/sda b 8 0 # 如果需要模拟磁盘设备 ``` 创建init文件(rootfs目录下): ```bash touch init chmod +x init ``` init文件内容示例: ```bash #!/bin/sh echo "Welcome to my Linux!" mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs none /dev echo /sbin/mdev > /proc/sys/kernel/hotplug mdev -s exec /bin/sh ``` 然后使用以下命令生成根文件系统的镜像文件(例如ext4格式): ```bash dd if=/dev/zero of=rootfs.img bs=1M count=100 mkfs.ext4 rootfs.img sudo mkdir -p /mnt/rootfs sudo mount rootfs.img /mnt/rootfs sudo cp -r rootfs/* /mnt/rootfs/ sudo umount /mnt/rootfs ``` 这样我们就得到了rootfs.img。 #### 3. 使用QEMU启动 使用QEMU启动内核,并挂载根文件系统。同时,开启调试端口(1234)并暂停CPU等待调试器连接。 ```bash qemu-system-x86_64 \ -kernel linux-5.4.34/arch/x86/boot/bzImage \ -drive file=rootfs.img,if=ide,format=raw \ -append "root=/dev/sda console=ttyS0" \ -nographic \ -S -s ``` 解释: - `-kernel`:指定内核镜像 - `-drive`:指定根文件系统镜像 - `-append`:内核启动参数,指定根设备为/dev/sda(IDE磁盘)和控制台 - `-nographic`:不使用图形界面 - `-S`:启动时暂停CPU - `-s`:监听TCP端口1234(gdb默认端口) #### 4. 使用GDB调试 打开另一个终端,进入内核源码目录,启动gdb: ```bash cd linux-5.4.34 gdb vmlinux ``` 在gdb中连接QEMU: ```bash (gdb) target remote :1234 ``` 设置断点(例如在start_kernel): ```bash (gdb) b start_kernel ``` 继续执行: ```bash (gdb) c ``` 然后就可以进行调试了。 ### 注意事项 1. 在调试过程中,可能会遇到寄存器显示问题(如引用[5]所述),这是因为局部变量可能被优化到寄存器中,而gdb显示的是寄存器的值,但实际内存中的变量可能没有改变。这需要结合代码上下文理解。 2. 如果需要调试模块(module),需要确保模块也带有调试信息,并且在启动时加载。在QEMU启动参数中,可以通过-initrd指定包含模块的initramfs,或者通过其他方式加载。 以上步骤搭建了一个基本的调试环境。 §§相关问题§§ 1. 如何在内核调试中设置断点? 2. 如何调试Linux内核模块? 3. 在QEMU中如何配置网络以便调试?</think>### 如何在 Linux 上配置和搭建 Kernel GDB 调试环境 以下是基于 QEMU 和 GDB 的内核调试环境搭建步骤(以 **Ubuntu 20.04** 为例): --- #### **1. 编译 Linux 内核** ```bash # 下载内核源码(示例版本 5.4.34) wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz tar -xvf linux-5.4.34.tar.xz cd linux-5.4.34 # 配置内核(启用调试信息) make menuconfig ``` **关键配置**: `Kernel hacking` → `Compile-time checks` → ✅ **[*] Compile the kernel with debug info**(必须开启) ✅ 关闭 `Kernel address space layout randomization (KASLR)`(避免地址偏移)[^3] ```bash # 编译内核 make -j$(nproc) ``` 生成文件: - 内核镜像:`arch/x86/boot/bzImage` - 符号表:`vmlinux`(GDB 调试用) --- #### **2. 制作根文件系统(RootFS)** 使用 **BusyBox** 构建最小文件系统: ```bash # 下载并编译 BusyBox wget https://busybox.net/downloads/busybox-1.32.1.tar.bz2 tar -xvf busybox-1.32.1.tar.bz2 cd busybox-1.32.1 make menuconfig ``` **配置**: `Settings` → ✅ **Build static binary (no shared libs)** ```bash make -j$(nproc) && make install ``` ```bash # 创建 RootFS 目录结构 mkdir rootfs cp -r _install/* rootfs/ cd rootfs mkdir -p dev proc sys sudo mknod dev/console c 5 1 # 创建设备节点 ``` ```bash # 创建 init 脚本(vim init) #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys exec /bin/sh chmod +x init # 生成 RootFS 镜像 dd if=/dev/zero of=rootfs.img bs=1M count=100 mkfs.ext4 rootfs.img sudo mount -o loop rootfs.img /mnt sudo cp -r rootfs/* /mnt sudo umount /mnt ``` --- #### **3. 启动 QEMU 虚拟机** ```bash qemu-system-x86_64 \ -kernel arch/x86/boot/bzImage \ # 指定内核 -drive file=rootfs.img,format=raw \ # 挂载 RootFS -append "root=/dev/sda console=ttyS0 nokaslr" \ # 禁用 KASLR -nographic \ # 禁用图形界面 -S -s \ # -S: 暂停CPU-s: 开启 GDB 端口(默认:1234) -net nic -net bridge,br=virbr0 # 可选:启用网络[^4] ``` --- #### **4. 使用 GDB 调试内核** ```bash # 新终端中加载符号表并连接 QEMU cd linux-5.4.34 gdb vmlinux ``` ```gdb (gdb) target remote :1234 # 连接 QEMU (gdb) b start_kernel # 在初始化函数设断点 (gdb) c # 继续执行 ``` **常用命令**: - `bt`:查看调用栈 - `list`:显示源码 - `next`/`step`:单步调试 - `p variable`:打印变量值(注意寄存器优化问题[^5]) --- #### **调试模块(Kernel Module)** 1. 编译模块时添加 `-g` 选项保留调试符号 2. 在 QEMU 中加载模块后,GDB 执行: ```gdb (gdb) add-symbol-file module.ko 0xffffffffc0000000 # 地址通过 lsmod 查看 (gdb) b module_func # 在模块函数设断点 ``` --- #### 关键注意事项 1. **调试信息**:确保内核编译开启 `CONFIG_DEBUG_INFO=y` 2. **KASLR**:启动参数必须包含 `nokaslr`,否则断点地址失效 3. **寄存器变量**:GDB 打印变量时可能显示寄存器值(非实际内存值),需结合代码分析[^5] 4. **版本匹配**:QEMU、GCC、GDB 版本需兼容(推荐 GDB ≥10.1[^3]) > 通过以上步骤,即可实现断点跟踪、变量监视等内核级调试操作。 --- ### 相关问题 1. **如何解决 GDB 调试时变量值显示异常的问题?** (提示:寄存器优化导致的伪变量问题[^5]) 2. **在 QEMU 中如何配置共享文件夹以便传输内核模块?** 3. **如何调试 Linux 内核启动过程中的内存管理错误?** 4. **使用 KGDB 调试实体机和 QEMU 调试有何区别?** 5. **如何捕获并调试内核的 OOPS(空指针异常)错误?** [^1]: Linux内核调试环境搭建及start_kernel实验 [^2]: Linux内核分析(二)--qemu搭建kernel调试环境 [^3]: 利用QEMU+GDB搭建Linux内核调试环境 [^4]: ubuntu20.04 搭建kernel调试环境第七篇--gdb调试module [^5]: ubuntu20.04 搭建kernel调试环境第八篇gdb调试技巧
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值