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

Linux 6.x源码解剖:从start_kernel到第一个用户进程

Linux 6.x源码解剖:从start_kernel到第一个用户进程

用GDB揭开内核启动的神秘面纱

引言:内核启动的“创世时刻

当按下电源键,处理器开始执行第一条指令时,一个数字宇宙的诞生序曲悄然奏响。现代操作系统内核的启动过程堪称计算机科学中最精妙的交响乐,而Linux内核的启动更是将模块化初始化动态进程创建的艺术演绎到极致。本系列专栏首篇文章将带您深入Linux 6.x内核源码,通过GDB动态调试与源码解析,揭示从start_kernel到第一个用户进程init的全过程。

核心问题驱动

  • 操作系统如何从“无进程”状态过渡到多任务环境?
  • 0号进程(idle)、1号进程(init)、2号进程(kthreadd)如何诞生?
  • 调度器、内存管理等子系统如何协同完成启动仪式?

一、实验环境搭建:GDB + QEMU动态跟踪

1.1 调试环境配置(Linux 6.1.30示例)

# 编译调试版内核
make defconfig && make -j$(nproc) KCFLAGS="-g -O0"# 启动QEMU并冻结CPU
qemu-system-x86_64 \-kernel arch/x86/boot/bzImage \-initrd initramfs.cpio.gz \-s -S \          # -S: 启动时冻结, -s: 开启1234调试端口-append "nokaslr" # 禁用地址随机化,便于调试

1.2 GDB连接与基础断点设置

(gdb) file vmlinux          # 加载符号表
(gdb) target remote :1234   # 连接QEMU
(gdb) break start_kernel    # 内核C代码入口
(gdb) break rest_init       # 进程创建转折点
(gdb) break kernel_init     # 用户态起点
(gdb) c                    # 继续执行

表:GDB调试关键命令

命令作用示例
break [func]函数断点break trap_init
list查看源码上下文list start_kernel:50,100
ptregs查看寄存器ptregs
disassemble反汇编当前函数disassemble /m
nexti汇编级单步nexti

二、解剖start_kernel:内核的“大爆炸”起点

2.1 初始化全景图

start_kernel位于init/main.c,是汇编到C语言的交接点。在此之前,体系结构相关的汇编代码(如arch/x86/kernel/head_64.S)已完成基础环境搭建:

// init/main.c
asmlinkage __visible void __init start_kernel(void)
{char *command_line;/* 1. 早期初始化 */set_task_stack_end_magic(&init_task); // 手工创建0号进程boot_cpu_init();                     // 激活BSP处理器setup_arch(&command_line);           // 架构相关初始化/* 2. 核心子系统初始化 */trap_init();                         // 中断向量表设置mm_init();                           // 内存管理初始化sched_init();                        // 调度器初始化/* 3. 后期初始化 */time_init();                         // 时钟系统启动init_IRQ();                          // 中断控制器配置softirq_init();                      // 软中断初始化console_init();                      // 控制台激活/* 4. 启动rest_init */rest_init(); // 进入进程创建阶段
}

2.2 关键初始化函数解析

2.2.1 0号进程诞生:set_task_stack_end_magic
// include/linux/sched/task_stack.h
void set_task_stack_end_magic(struct task_struct *tsk)
{unsigned long *stackend = end_of_stack(tsk);*stackend = STACK_END_MAGIC; /* 0x57AC6E9D */
}

此函数为init_task(0号进程)的内核栈设置魔术字(0x57AC6E9D),用于检测栈溢出。init_task静态定义的进程描述符(PCB):

// init/init_task.c
struct task_struct init_task = {.state = 0, .stack = init_stack,       // 静态分配的内核栈.flags = PF_KTHREAD,       // 内核线程标志.prio = MAX_PRIO - 20,     // 默认优先级// ... 其他字段初始化
};
2.2.2 中断门设置:trap_init

x86架构下中断向量初始化关键代码:

// arch/x86/kernel/traps.c
void __init trap_init(void)
{/* 系统调用门 */set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32);/* 异常处理 */set_intr_gate(X86_TRAP_DE, divide_error);   // 除零异常set_intr_gate(X86_TRAP_PF, page_fault);     // 页错误// 加载IDT表load_idt(&idt_descr);
}

此函数建立了中断处理路由表,其中entry_INT80_32是传统系统调用入口。

2.2.3 调度器初始化:sched_init
// kernel/sched/core.c
void __init sched_init(void)
{for_each_possible_cpu(i) {struct rq *rq = cpu_rq(i);rq->curr = &init_task;  // 当前运行任务设为init_taskinit_rq_hrtick(rq);     // 高精度时钟初始化}init_idle(&init_task, cpu); // 将init_task设为idle任务
}

此时调度器已激活,但运行队列为空,故当前任务指向init_task

表:start_kernel阶段关键初始化函数

函数位置作用依赖关系
set_task_stack_end_magicinit/main.c0号进程栈初始化最早调用的函数之一
setup_archarch/x86/kernel/setup.c架构相关初始化需在内存管理前完成
trap_initarch/x86/kernel/traps.c中断向量表设置早于任何可能异常
mm_initinit/main.c内存管理初始化依赖物理内存检测
sched_initkernel/sched/core.c调度器启动需在进程创建前完成

三、rest_init:三进程诞生的“创世神话”

start_kernel完成所有基础初始化后,调用rest_init进入进程创建阶段

3.1 代码全景解析

// init/main.c
noinline void __ref rest_init(void)
{struct task_struct *tsk;int pid;/* 创建1号进程 - 用户态祖先 */pid = kernel_thread(kernel_init, NULL, CLONE_FS);// ... 错误检查/* 创建2号进程 - 内核线程祖先 */pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);/* 0号进程蜕变为idle */cpu_startup_entry(CPUHP_ONLINE);
}

3.2 1号进程:kernel_init的进化之路

1号进程创建后执行kernel_init函数:

static int __ref kernel_init(void *unused)
{/* 等待kthreadd就绪 */wait_for_completion(&kthreadd_done);/* 尝试执行用户态init程序 */if (execute_command) {run_init_process(execute_command); // 尝试指定路径的init} else {// 标准init路径搜索序列run_init_process("/sbin/init");run_init_process("/etc/init");run_init_process("/bin/init");run_init_process("/bin/sh");}panic("No working init found"); // 全部失败则崩溃
}

run_init_process内部调用do_execve系统调用,此时发生从内核态到用户态的关键跃迁

3.3 2号进程:kthreadd的守护使命

kthreadd所有内核线程的父进程,其核心逻辑为管理kthread_create_list链表:

int kthreadd(void *unused)
{for (;;) {set_current_state(TASK_INTERRUPTIBLE);if (!list_empty(&kthread_create_list)) break;schedule(); // 主动让出CPU}spin_lock(&kthread_create_lock);while (!list_empty(&kthread_create_list)) {// 创建新内核线程create_kthread(create);}spin_unlock(&kthread_create_lock);
}

内核线程创建请求(如kthread_create)会被加入此链表,由kthreadd统一处理。

3.4 0号进程:idle的终极轮回

rest_init调用cpu_startup_entry后,0号进程进入idle循环

// kernel/sched/idle.c
void cpu_startup_entry(enum cpuhp_state state)
{arch_cpu_idle_prepare();cpuhp_online_idle(state);while (1) {do_idle(); // 核心idle循环}
}static void do_idle(void)
{if (need_resched()) {schedule_idle(); // 主动调度} else {arch_cpu_idle_enter();native_safe_halt(); // 执行HLT指令节能arch_cpu_idle_exit();}
}

HLT指令使CPU进入低功耗状态,直到中断或调度请求唤醒。


四、进程切换机制剖析:从0到1的跃迁

4.1 进程状态转换模型

创建1号进程
调度器选择
执行execve
0号进程运行
1号进程就绪
1号进程运行
用户态init运行

4.2 context_switch源码解析

当调度器选择新进程时触发上下文切换:

// kernel/sched/core.c
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,struct task_struct *next)
{/* 1. 切换虚拟地址空间 */struct mm_struct *mm = next->mm;switch_mm_irqs_off(prev->active_mm, mm, next);/* 2. 切换寄存器状态 */switch_to(prev, next, prev);/* 3. 返回新进程的rq */return finish_task_switch(prev);
}

其中switch_to通过汇编代码arch/x86/entry/entry_64.S)完成硬件上下文保存/恢复。

4.3 首次进程切换的GDB观测

在GDB中设置断点观察切换过程:

(gdb) break __schedule
(gdb) break context_switch
(gdb) break finish_task_switch

切换发生时寄存器变化:

RAX: 0x0 
RBX: 0xffff888007e1b280 --> 0x0 
RCX: 0xffffffff820e9e80 (__per_cpu_offset) 
RDX: 0xffff888007e1b280 --> 0x0 
RSI: 0xffff888007e1b280 --> 0x0 
RDI: 0xffff888007e1b280 --> 0x0 
RBP: 0xffffffff83e5df20 (init_task+1952)

RBP指向init_task表明当前仍为0号进程上下文。


五、0号进程的终极解剖:idle的隐秘生活

5.1 idle进程的三大使命

  1. CPU节能:通过HLT指令降低功耗
  2. 空闲调度:执行schedule_idle让出CPU
  3. 硬件监控:检测lockup等异常

5.2 现代idle优化机制

5.2.1 C-State与P-State协同
// drivers/idle/intel_idle.c
static struct cpuidle_state skl_cstates[] = {{.name = "C1-SKL",.flags = MWAIT2flg(0x00),.exit_latency = 2,.target_residency = 2,.enter = &intel_idle},{.name = "C1E-SKL",.flags = MWAIT2flg(0x01),.exit_latency = 10,.target_residency = 20,.enter = &intel_idle},// ... 更深状态
};

内核根据退出延迟目标驻留时间智能选择C-State。

5.2.2 Tickless模式

tick_nohz_idle_enter中关闭周期时钟中断:

void tick_nohz_idle_enter(void)
{if (can_stop_idle_tick()) {__tick_nohz_idle_enter();ts->idle_active = 1;}local_irq_restore(flags);
}

消除周期性时钟中断可大幅降低功耗。


六、总结:内核启动的三重境界

  1. 0号进程创世:静态定义 → 动态idle
  2. 三进程分工确立
    • 0号:资源回收与节能
    • 1号:用户空间统治
    • 2号:内核线程管理
  3. 状态跃迁完成:无进程 → 内核线程 → 用户进程

表:Linux启动三进程对比

特性0号进程(idle)1号进程(init)2号进程(kthreadd)
进程ID012
创建方式静态定义kernel_thread创建kernel_thread创建
运行空间内核态用户态内核态
调度类idle_sched_classfair_sched_classfair_sched_class
关键作用CPU节能、兜底调度初始化用户空间创建内核线程
退出条件永不退出可被替换(systemd)永不退出

道家哲学隐喻:道生一(0号),一生二(1、2号),二生三(三进程),三生万物(所有进程)


下期预告:《中断与异常:内核的事件驱动引擎》

在下一期中,我们将深入探讨:

  1. 中断处理全景:从硬件中断到softIRQ
  2. 系统调用新机制syscall指令替代int 0x80
  3. 页错误处理艺术:匿名页、文件页与写时复制
  4. 实时补丁原理:如何实现μs级响应

彩蛋:我们将用GDB动态修改IDT表,观察中断劫持的防御机制!


本文使用知识共享署名4.0许可证,欢迎转载传播但须保留作者信息
技术校对:Linux 6.1.30源码、GDB 13.2文档
实验环境:QEMU 7.2.0, x86_64架构

相关文章:

  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Dad Jokes(冷笑话卡片)
  • 006网上订餐系统技术解析:打造高效便捷的餐饮服务平台
  • Python(十五)
  • 【iOS】多线程基础
  • CentOS7+JDK8虚拟机安装
  • 【Python进阶】元类编程
  • 从0开始使用 Vue3 和 TypeScript 搭建项目详细教程
  • 计算机组成原理知识点汇总(五)计算机运算方法
  • 终极陷阱:Java序列化漏洞的内爆原理与防御体系重建
  • Dockerfile 使用多阶段构建(build 阶段 → release 阶段)前端配置
  • sudo docker exec -it backend bash 以交互方式(interactive)进入正在运行的 Docker 容器的命令行环境
  • Dockerfile使用与最佳实践
  • java-springboot图片上传校验之只允许上传png、jpg、jpeg这三种类型,且文件大小不能超过10M,且检查不能是脚本或者有害文件或可行性文件
  • 缩量和放量指的是什么?
  • Tailwind CSS 实战:基于 Kooboo 构建 AI 对话框页面(六):图片上传功能
  • Kafka集群部署(docker容器方式)SASL认证(zookeeper)
  • 前端基础之《Vue(18)—路由知识点》
  • OpenCV 滑动条调整图像对比度和亮度
  • Spring @Autowired自动装配的实现机制
  • Redis配置了在磁盘上保存 RDB 快照,但目前无法持久化到磁盘
  • 网站需要更新的频率/百度爱采购推广一个月多少钱
  • 深圳乐安居网站谁做的/长春网站优化咨询
  • 网站建设的重要/网站seo外链建设
  • 百度百科提交入口/西安seo公司哪家好
  • 做碳循环的网站/外贸推广有哪些好的方式
  • 个人电子邮箱怎么注册/搜索引擎营销seo