
Linux 内核进程0 与进程1 进程 2 的关系核心关系一句话进程0 是进程1 的父亲但两者本质完全不同。对比表格维度进程0idle / swapper进程1init / systemd诞生方式由内核启动代码硬编码创建不经过fork()由进程0 调用kernel_thread()创建本质内核的一部分永远存在第一个用户态进程PID01运行模式始终在内核态内核态启动最终切换到用户态职责CPU 空闲时占位idle loop管理所有用户进程的生命周期可被kill不可以不可以kill -9 无效父进程无自举产生进程0子进程孤儿收养不负责负责收养所有孤儿进程CPU上电│▼start_kernel() ← 内核初始化入口│ 进程0 在此诞生task_struct 静态定义│▼rest_init()│├─── kernel_thread(kernel_init, ...) ← 创建 PID1│ ││ └─── 最终 exec(/sbin/init) 或 systemd│ 进入用户态│└─── cpu_startup_entry(CPUHP_ONLINE) ← 进程0 进入 idle loop│└─── while(1) { schedule(); cpu_relax(); }关键机制说明进程0 的本质idle 任务每个 CPU 核心都有一个专属的 idle 进程SMP 系统中 CPU1/2/3 的 idle 是从 CPU0 的进程0 克隆出来的调度器找不到可运行进程时就切换到 idleidle 循环执行hlt指令让 CPU 降低功耗进程1 的本质用户空间守护神kernel_init()├── 挂载根文件系统├── 初始化设备└── exec 以下之一按顺序尝试/sbin/init ← SysV init/etc/init/bin/init/bin/sh ← 最后兜底现代系统通常是 systemd// 当一个进程的父进程退出时内核将其重新挂到 initPID1下// 这就是为什么 ps 里很多进程的 PPID 都是 1find_new_reaper() → 最终指向 PID1一个易混淆点进程0 是不是第一个进程从时间顺序看是它最先存在从 Unix 进程模型看它不算标准进程无法被ps完整看到ps aux看不到 PID0可以用以下命令验证进程1cat /proc/1/status # 查看 init/systemd 的状态cat /proc/1/cmdline # 确认是 systemd 还是 init ls -la /proc/0 # 通常不存在或无法访问总结进程0 是内核自举时的原始种子负责在没有任务时让 CPU 空转进程1 是它孵化出的第一个真正进程负责拉起整个用户空间世界。内核进程 vs 用户进程 详解先回答你的三个问题问题答案内核进程能被ps看到吗能名字用方括号包裹如[kworker][ksoftirqd]进程1 是内核进程还是用户进程用户进程最终 exec 了/sbin/init或systemd内核进程没有用户空间/用户态正确内核进程永远在内核态运行没有用户地址空间内核进程 vs 用户进程 对比维度内核进程Kernel Thread用户进程User Process创建方式kthread_create()/kernel_thread()fork()exec()地址空间无用户地址空间mm NULL有独立的用户态虚拟地址空间运行模式永远在内核态用户态 系统调用时进内核态ps显示有名字带方括号[kworker/0:1]正常显示路径名/usr/bin/xxx能访问用户内存不能能典型例子[kworker][ksoftirqd][migration][kswapd]bashnginxsystemd用 ps 实际看一下ps aux | head -20输出示例USER PID COMMAND root 1 /sbin/init ← 进程1用户进程无方括号 root 2 [kthreadd] ← 内核线程管理者 root 3 [rcu_gp] ← RCU 内核线程 root 9 [ksoftirqd/0] ← 软中断处理 root 10 [rcu_sched] root 14 [migration/0] ← CPU迁移线程 root XX [kworker/0:1] ← 工作队列线程 root XX [kswapd0] ← 内存回收线程规律方括号 内核进程无方括号 用户进程内核进程的地址空间细节内核进程的 task_struct├── mm NULL ← 没有用户地址空间└── active_mm 借用上一个进程的mm仅用于访问内核地址用户进程的 task_struct├── mm 指向自己的 mm_struct└── active_mm 同上内核进程调度时虚拟地址空间切换的开销也更小不用切 CR3 页表寄存器。进程1 为什么是用户进程kernel_init() ← 一开始在内核态执行此时像内核线程│├── 挂载根文件系统├── 初始化设备│└── execve(/sbin/init) ← 关键exec 之后- 加载 ELF 到用户地址空间- mm 从 NULL 变为真实 mm_struct- 返回用户态- 从此是彻彻底底的用户进程进程1 的身份在execve那一刻发生了转变之前像内核线程之后是用户进程。内核进程的工作举例内核进程职责[kthreadd]PID2所有内核线程的父进程负责创建其他内核线程[kworker]处理工作队列延迟工作、中断下半部[ksoftirqd]处理软中断网络收包等[kswapd]内存不足时换出页面[migration]SMP 负载均衡迁移进程到其他 CPU[jbd2]ext4 日志提交总结一句话内核进程 只活在内核态、无用户地址空间、干内核杂活进程1 虽然由内核创建但exec之后已是用户进程是所有用户空间的祖先。kthreadd (PID2) 的诞生直接回答问题答案由谁创建进程1kernel_init的兄弟由进程0在rest_init()中创建父进程是谁进程0PID0何时创建rest_init()函数中与进程1几乎同时创建rest_init() 源码还原// kernel/init/main.c static noinline void __ref rest_init(void) { // 第一步创建 PID1kernel_init pid kernel_thread(kernel_init, NULL, CLONE_FS); // 第二步创建 PID2kthreadd pid kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); kthreadd_task find_task_by_pid_ns(pid, init_pid_ns); // 第三步进程0 自己进入 idle loop cpu_startup_entry(CPUHP_ONLINE); // 永不返回 }PID1 和 PID2 都是进程0 直接kernel_thread()创建的父进程都是 PID0。完整的创建时序start_kernel()│└── rest_init() ← 进程0 执行到这里│├── kernel_thread(kernel_init) → PID1 父0│├── kernel_thread(kthreadd) → PID2 父0│ ││ └── kthreadd 进入循环│ 等待其他地方发来创建内核线程的请求│ 所有后续内核线程kworker/ksoftirqd等│ 都由它 fork父进程变为 PID2│└── cpu_startup_entry() → 进程0 进入 idle loopkthreadd 的工作机制// kernel/kthread.cint kthreadd(void *unused){for (;;) {// 没有创建请求时让出 CPUset_current_state(TASK_INTERRUPTIBLE);if (list_empty(kthread_create_list))schedule();// 有请求时遍历链表逐个 fork 出新内核线程while (!list_empty(kthread_create_list)) {create list_entry(...);kernel_thread(kthread, create, CLONE_FS | CLONE_FILES);}}}调用链其他代码调用kthread_create()→ 把请求挂到kthread_create_list→ 唤醒 kthreadd → kthreaddfork出新线程进程家谱总结PID0 (idle/swapper) ← 内核硬编码无父├── PID1 (kernel_init → systemd) ← rest_init 创建└── PID2 (kthreadd) ← rest_init 创建├── PID3 [rcu_gp]├── PID9 [ksoftirqd/0]├── PID14 [migration/0]├── PIDxx [kworker/...]└── ...所有内核线程用命令验证# 查看 PID2 的父进程 cat /proc/2/status | grep PPid # 输出PPid: 0 # 查看所有内核线程的父进程 ps -eo pid,ppid,comm | grep ^\s*[0-9] | awk $22 # 会看到大量 kworker/ksoftirqd 等PPID 都是 2一句话总结进程0 在rest_init()里先后创建了进程1 和进程2之后自己去 idle进程2kthreadd从此充当内核线程工厂所有后续内核线程都由它产生。CPU Idle 进程详解问题答案进程号是多少PID0但每个CPU核心都有一个共享 PID0叫什么名字swapper/0、swapper/1、swapper/2... 按CPU编号一个CPU一个吗是的N核CPU就有N个idle进程何时创建CPU0的idle在内核自举时天然存在其他CPU的idle在SMP初始化时创建生命周期与系统同寿永不退出为什么共享 PID0PID0 是特殊的不走正常的 PID 分配机制ps 命令显示PID COMMAND0 [swapper/0] ← CPU0 的 idle???实际上 ps 看不到 PID0因为 /proc/0 不存在但通过内核调试工具可以看到每个CPU的idle task用以下命令验证# 查看每个CPU的idle进程通过调试文件系统 cat /proc/schedstat # 调度统计可见idle时间 # 或者 top -d1 # 看 %ididle百分比多核场景下的 idle 家族进程名对应CPU创建时机swapper/0CPU0内核启动时天然存在是整个系统的原始进程swapper/1CPU1smp_init()唤醒 CPU1 时创建swapper/2CPU2smp_init()唤醒 CPU2 时创建swapper/NCPUN同上创建时序4核CPU为例start_kernel()│├── 【此刻 swapper/0 天然存在】│ task_struct init_task INIT_TASK(init_task) ← 静态编译进内核│├── rest_init()│ ├── 创建 PID1 (kernel_init)│ └── 创建 PID2 (kthreadd)│└── cpu_startup_entry() ← swapper/0 进入 idle loop│▼kernel_init() 执行过程中│└── smp_init() ← SMP 多核初始化├── 唤醒 CPU1 → CPU1 上创建 swapper/1 → 进入 idle loop├── 唤醒 CPU2 → CPU2 上创建 swapper/2 → 进入 idle loop└── 唤醒 CPU3 → CPU3 上创建 swapper/3 → 进入 idle loopswapper/0 的特殊性// include/linux/init_task.h// swapper/0 是静态定义的不是 fork 出来的struct task_struct init_task INIT_TASK(init_task);// 对比普通进程普通进程 → fork() → 动态分配 task_struct → 分配PIDswapper/0 → 静态链接进内核镜像 → PID永远是0 → 无需分配idle 进程的工作内容// kernel/sched/idle.cstatic void do_idle(void){while (1) {// 1. 检查是否有进程需要运行if (need_resched()) {schedule_idle(); // 让出CPU给其他进程continue;}// 2. 真正没事干时让CPU省电arch_cpu_idle(); // x86: HLT指令ARM: WFI指令// CPU停止执行等中断唤醒}}CPU架构idle 指令效果x86HLTCPU停止取指降频降压ARMWFI(Wait For Interrupt)类似等中断唤醒RISC-VWFI同ARM生命周期系统启动 ──────────────────────────── 系统关机│ │swapper/0 ████████████████████████████ 永不退出swapper/1 ████████████████████████████ 永不退出swapper/2 ████████████████████████████ 永不退出swapper/3 ████████████████████████████ 永不退出↑ ↑有任务时 任务结束后被调度走 重新回来执行 idleidle 进程的优先级是调度器中最低的只要有任何其他进程可运行idle 就会让出 CPU。一句话总结swapper是每个 CPU 核心专属的占位进程N核系统就有N个全部共用 PID0系统启动时创建永不消亡专职在无任务时让 CPU 执行省电指令。验证一下# top / ps 都看不到 swapper ps aux | grep swapper # 无结果 top # 找不到 swapper/0、swapper/1 # /proc 文件系统也没有 ls /proc/0 # 报错No such file or directory为什么看不到原因说明/proc/0不存在ps/top依赖/proc/PID目录PID0 没有对应目录内核特殊处理swapper 的task_struct是静态变量不在正常进程链表的可遍历范围内供用户态查看设计如此idle 进程是内核内部实现细节不需要暴露给用户空间能看到它的方式内核视角# 方式1查看每个CPU的调度统计间接证明idle存在 cat /proc/schedstat # 方式2ftrace 跟踪调度事件 echo 1 /sys/kernel/debug/tracing/events/sched/sched_switch/enable cat /sys/kernel/debug/tracing/trace | grep swapper # 输出类似 # swapper/0-0 [000] → 某进程 # 某进程 [000] → swapper/0-0 # 方式3perf 工具 perf stat -a sleep 1 # 会统计idle时间ftrace的输出格式swapper/0-0含义swapper/0 进程名CPU0的idle最后的-0 PID0一句话总结top/ps完全看不到 swapper即便通过内核调试工具能看到PID 也确实是 0。它存在于内核里但对用户空间是透明的。进程0、1、2 的正确称谓与关系总结称谓问题先说清楚PID正确称谓不准确的叫法原因0idle进程或swapper进程0它根本不是标准意义的进程是内核自举体1init进程或systemd内核线程1它最终是用户进程不是内核线程2内核线程 kthreadd进程2它确实是内核线程叫内核线程更准确结论统称进程0/1/2是口语习惯技术上更准确的说法是idle(0)、init(1)、kthreadd(2)三者关系一张图┌─────────────────────────────┐│ 内核自举start_kernel │└──────────────┬──────────────┘│ 天然存在▼┌─────────────────────────────┐│PID0swapper/idle ││ 【内核自举体】 ││ · 静态编译进内核 ││ · 不是fork产生的 ││ · 永远在内核态 │└──────┬──────────┬───────────┘│ 创建 │ 创建(kernel_thread) (kernel_thread)│ │┌───────────▼──┐ ┌────▼──────────────────┐│PID1init │ │PID2kthreadd ││ 【用户进程】 │ │ 【内核线程】 ││ · execve后 │ │ · 永远内核态 ││ 进入用户态 │(变身为用户进程的祖师爷)│ · 充当内核线程工厂│内核进程的爸爸└──────┬───────┘ └────┬───────────────────┘│ │ fork所有用户进程 ├── kworker都是它的后代 ├── ksoftirqd(bash/nginx/...) ├── kswapd├── migration └── ...所有内核线程三者核心特性对比特性PID0 swapperPID1 init/systemdPID2 kthreadd本质内核自举体用户进程内核线程诞生方式静态定义不forkkernel_thread创建kernel_thread创建父进程无PID0PID0用户地址空间mmNULL有exec后NULL运行模式永远内核态用户态为主永远内核态ps能看到看不到能看到能看到方括号能被kill不能不能不能职责CPU空闲占位省电用户空间祖先孤儿收养内核线程工厂数量N个每CPU一个1个1个生命周期与系统同寿与系统同寿与系统同寿各自最关键的特殊性PID0 最特殊的地方唯一一个不是通过 fork/clone 创建的进程是整个系统的起点但对用户空间完全不可见N核系统有N个但全部共享 PID0PID1 最特殊的地方唯一一个经历了身份转变的进程内核线程kernel_init执行阶段↓ execve(/sbin/init)用户进程systemd运行阶段是所有用户进程的祖先孤儿进程的最终归宿PPID被改为1PID2 最特殊的地方所有内核线程的统一父进程自身是内核线程却专门负责生产其他内核线程kthread_create() 的请求都由它来执行 fork一句话精华PID0 是起点内核自举原始体省电占位PID1 是用户世界的根从内核态蜕变为用户态收养孤儿PID2 是内核世界的根所有内核线程的父亲永驻内核态。三者由0依次创建共同构成整个Linux进程世界的骨架。