本文由AI辅助写作,已审阅
本文聚焦每个内核线程在系统内何时诞生、由谁触发、以及它真正在做什么。执行 ps aux 看到那些方括号进程,读完本文你将知道它们每一个存在的理由。
诞生逻辑 — 谁创建它、什么条件触发、是启动时就有还是按需出现、有什么前提条件。
功能场景 — 它具体在做什么、什么情况下会活跃、跟哪些系统行为直接挂钩、出问题时的表现。

本文覆盖了30+个线程,按类别分为:
- 基础架构(kthreadd / kworker / ksoftirqd)
- 内存管理(kswapd / kcompactd / khugepaged / ksmd / zswapd)
- 存储 IO(writeback / xfsaild / jbd2 / kblockd / nfsd / RAID)
- 调度实时(migration / watchdog / khungtaskd / stopper)
- RCU 并发(rcu_preempt / rcuog)
- 电源硬件(cpuhp / acpi_thermal / kacpid)
一、基础架构类
[kthreadd] — PID 2,所有内核线程之父
诞生逻辑
由 PID 0(swapper/idle 进程)在内核启动的 rest_init() 阶段通过 kernel_thread() 直接创建,与 PID 1 是“兄弟”关系,紧随 PID 1 之后诞生。它诞生后立即进入休眠,等待其他内核代码向它提交“创建子线程”的请求。所有后续内核线程,都是由 kthreadd 代为 fork 出来的。
功能场景
只做一件事:监听 kthread_create_list 链表。任何驱动或内核子系统调用 kthread_create() 时,都只是往这个链表挂一个请求然后唤醒 kthreadd,真正的 kernel_thread() 系统调用由 kthreadd 执行。这样设计的原因是:内核线程必须是内核线程的子进程,而不能是用户进程的子进程,kthreadd 充当了这个统一的"亲本"。
[kworker/n:m] / [kworker/u:m] — 通用异步工作执行器
诞生逻辑
kworker 线程由内核的工作队列(workqueue)子系统在系统初始化时为每个 CPU 创建初始线程,之后动态伸缩。命名规则:n 是绑定的 CPU 编号,u 表示 unbound(不绑定 CPU),m 是该 CPU 上的线程序号,后缀 H 表示高优先级队列。
动态创建的触发条件:某 CPU 的 worker 线程全部处于阻塞状态(如等待 IO),此时工作队列中仍有待执行的 work,系统会新建一个 kworker 顶上。空闲超过 5 分钟的 kworker 会被自动销毁。
功能场景
凡是内核代码需要"稍后在进程上下文里异步执行某段逻辑",就通过 schedule_work() / queue_work() 提交一个 work_struct,由 kworker 负责执行。具体场景:
- 驱动中断处理完成后的后半段逻辑(如网卡中断后的 DMA 解映射)
若发现某个 kworker 长期占用 CPU,用 cat /proc/<pid>/stack 查调用栈可以定位是哪个 work 在空转。
[ksoftirqd/n] — 软中断的兜底处理器
诞生逻辑
系统启动时,spawn_ksoftirqd() 为每个 CPU 创建一个专属的 ksoftirqd 线程,绑定到对应 CPU,使用普通调度优先级(nice 0)。它不会主动轮询,只在特定条件下被内核中断路径唤醒。
唤醒条件:某次中断处理结束后,内核检查待处理的软中断数量,若单次循环内软中断连续触发超过 10 次,或时间超过 2ms,内核放弃继续内联处理,转而唤醒 ksoftirqd 接管剩余工作。
功能场景
Linux 中断处理分为硬中断(立刻执行、时间极短)和软中断(延迟执行、可被调度)。ksoftirqd 负责处理积压的软中断,覆盖以下类型:
实践意义:服务器网卡流量打满时,ksoftirqd CPU 占用飙高是正常信号;若发现单个 CPU 的 ksoftirqd 跑满而其他 CPU 空闲,说明网卡中断没有均衡到多个 CPU,需配置 RSS 或 RPS。
二、内存管理类
[kswapd/n] — 内存页面后台回收者
诞生逻辑
内核初始化内存管理子系统(kswapd_init())时,为每个 NUMA 节点各创建一个 kswapd 线程(n 为节点编号,单路机器只有 kswapd0)。线程创建后处于休眠状态,等待内存分配路径的唤醒。
唤醒条件:内存分配函数 alloc_pages() 在检查 zone 空闲页数时,若空闲页降至低水位(WMARK_LOW)以下,立即唤醒对应 NUMA 节点的 kswapd,无需等到分配失败。kswapd 工作到内存回到高水位(WMARK_HIGH)以上后重新休眠。(注:分配失败触发的是 direct reclaim,对应更低的 WMARK_MIN,是两个不同机制。)
功能场景
kswapd 的核心工作是扫描 LRU 链表,从中选出"最值得牺牲"的页面进行回收:
场景一:文件页回收page cache 中缓存的文件数据(读过的磁盘内容)。若页面是 clean(未被修改),直接丢弃;若是 dirty(已被修改但未回写),先触发回写,等回写完成再释放。这是最安全的回收方式,不损失数据。
场景二:匿名页换出(swap)进程堆、栈、mmap 的匿名内存不能直接丢弃,若系统有 swap 分区,kswapd 将其压缩写入 swap 设备,释放物理页。vm.swappiness 参数(0~200)控制倾向于回收匿名页还是文件页。
场景三:slab 缓存收缩内核自身的 dentry cache、inode cache 通过 shrinker 机制在 kswapd 扫描过程中被收缩,slabtop 命令可以看到这些缓存的大小。
诊断:vmstat 1 中 so(swap out)持续不为零,且 kswapd CPU 占用常驻不退,是内存严重不足的明确信号。
[kcompactd/n] — 内存碎片后台整理者
诞生逻辑
与 kswapd 类似,内存管理初始化时为每个 NUMA 节点创建一个 kcompactd 线程,默认休眠。
有两个唤醒路径:
kswapd 在完成一轮内存回收后,若检测到仍无法满足高阶(order ≥ 3,即连续 8 个及以上页面)内存分配需求,主动唤醒同节点的 kcompactd。- 内存分配直接失败时,分配路径在尝试直接回收后,也会唤醒 kcompactd。
功能场景
解决的问题是:系统总空闲内存充足,但物理上不连续,无法分配大块连续内存(如透明大页 2MB、DMA 缓冲区、内核模块加载区)。
kcompactd 执行"内存压缩":从 zone 低地址端扫描出可移动页面(用户匿名页、文件页等),从高地址端扫描出空闲页,将可移动页迁移到高端,低端聚集出连续的大块空闲区域。整个过程无需 swap,只是物理位置的重新排列。
实践场景:数据库服务器频繁申请大内存段、KVM 宿主机为虚拟机分配内存、长期运行的服务器在启用透明大页后,kcompactd 的活跃度是内存碎片化程度的直接体现。
[khugepaged] — 透明大页后台合并者
诞生逻辑
当 /sys/kernel/mm/transparent_hugepage/enabled 不为 never 时,内核在 hugepage_init() 阶段创建 khugepaged 线程。它以固定间隔(默认每 10 秒一轮)主动扫描,无需外部唤醒。若 THP 被设为 never,此线程不会被创建。
功能场景
遍历系统中所有进程的虚拟内存区域(VMA),寻找满足以下条件的内存区域:512 个连续且对齐的 4KB 页面,且这片区域有足够的访问热度。条件满足时,申请一个 2MB 的大页,将这 512 个小页的内容复制进去,更新页表指向大页,然后释放原来的 512 个小页。对进程完全透明,无需修改任何代码。
需要警惕的场景:
- Redis、Memcached:THP 会导致 fork 时 CoW 代价成倍增加(一次写操作可能触发 2MB 整页复制),造成内存消耗翻倍和延迟毛刺。生产环境通常明确关闭。
- Java JVM
- 数据库(MySQL、PostgreSQL):通常建议关闭自动合并,改用
madvise 让数据库自主管理大页。
[ksmd] — 内核同页内存合并者(KSM)
诞生逻辑
只有在以下两个条件同时满足时,ksmd 线程才会被创建并激活:
- 用户态写入
echo 1 > /sys/kernel/mm/ksm/run
它不是系统启动时默认存在的线程,而是按需启动的。KVM/QEMU 宿主机的管理工具(如 libvirt)通常会在启动虚拟机时自动激活 ksmd。
功能场景
只处理被应用程序用 madvise(addr, len, MADV_MERGEABLE) 明确标记的内存区域。扫描这些区域中内容相同的页面,将它们映射到同一个物理页,并设为只读。任何一方写入时,内核触发写时复制(CoW),透明地为写入方创建私有副本。
核心使用场景是虚拟化内存去重:宿主机上运行 10 台使用同一发行版的虚拟机,这些虚拟机的内核代码段、libc、systemd 等内存页内容完全相同,ksmd 将其合并后,10 台虚拟机共享一份物理内存。实测在同质虚拟机环境下可节省 20%~40% 内存。
代价是 CPU 开销(ksmd 持续扫描)以及 CoW 触发时的延迟,非虚拟化场景一般不开启。
[zswapd] / [zram writeback] — 内存压缩回收线程
诞生逻辑
仅在启用 zswap(内存压缩 swap 缓存)时存在。启用 zswap 后,zswapd 在内存压力超过阈值时被唤醒,负责将压缩缓存中的冷数据进一步写出到后端 swap 设备,为新的压缩内存腾出空间。
功能场景
主要出现在内存受限设备(嵌入式 Linux、Android、Chromebook、低配云实例)上。系统内存紧张时,匿名页先被压缩存入 zswap 内存池(通常能压缩到原大小的 30%~50%),只有 zswap 池也满了,zswapd 才将最冷的压缩页写到真正的 swap 磁盘,实现"内存→压缩内存→磁盘"的三级分层。
三、存储与 IO 类
[writeback] / [bdi-default] — 脏页后台回写线程
诞生逻辑
每个注册到内核的**后端存储设备(BDI,Backing Device Info)**在初始化时,由 wb_init() 为其创建专属的 writeback 线程。也就是说:挂载一块磁盘,就会出现对应的 writeback 线程;卸载后线程消失。NFS 挂载同样有自己的 writeback 线程。
writeback 线程有两类唤醒路径:
- 周期定时内核定时器每隔
dirty_writeback_centisecs(默认 500,即 5 秒)唤醒一次,例行扫描脏页 - 压力触发脏页占总内存比例超过
dirty_background_ratio(默认 10%)时,写入进程调用 balance_dirty_pages() 主动唤醒 writeback
功能场景
Linux 的写操作默认是写缓存模式:数据先写入内存 page cache,标记为脏页,进程立刻返回。writeback 线程负责在合适时机将这些脏页真正写回磁盘:
场景一:周期回写每 5 秒一轮,将所有"在内存中停留超过 dirty_expire_centisecs(默认 30 秒)"的脏页提交给底层块设备驱动写盘。这是数据持久化的主要路径。
场景二:压力回写当系统产生脏页的速度超过磁盘 IO 带宽,脏页比例超过 dirty_background_ratio 时,writeback 线程加速工作;若超过 dirty_ratio(默认 20%),写入进程本身也会被限速(throttle),直到脏页降下来。
场景三:设备下线前回写卸载文件系统(umount)或同步(sync)时,触发强制回写,writeback 线程必须将该设备的所有脏页清零才允许继续。
实践意义:突然断电丢失的数据,就是这段时间内还没被 writeback 写回磁盘的脏页。dirty_expire_centisecs=3000 意味着最坏丢失 30 秒内的写入。
[kblockd] — 块设备层请求派发线程
诞生逻辑
块设备子系统(block layer)初始化时通过 blk_dev_init() 创建 kblockd 工作队列,并在首次需要时创建 kworker 线程处理其中的工作项。它是块层专用的工作队列执行器,不同于通用 kworker。
功能场景
块层在收到上层(文件系统、direct IO)的 IO 请求后,经过合并、排序(IO 调度器:deadline/mq-deadline/none 等),需要将最终请求异步派发给设备驱动。这个"发射"动作由 kblockd 执行。
核心意义在于解耦:IO 调度器做好决策后,不阻塞在设备驱动的处理上,由 kblockd 异步完成实际的请求下发,允许上层继续接受新请求。对于慢速设备(HDD)尤为重要。
[xfsaild/dm-n] — XFS 文件系统日志推进线程
诞生逻辑
挂载一个 XFS 文件系统时,xfs_fs_mount() 创建该分区专属的 xfsaild 线程(dm-n 中的 n 对应设备号)。卸载时线程销毁。每个 XFS 分区一个,多个 XFS 分区则有多个 xfsaild 线程并行工作。
功能场景
XFS 是日志型文件系统,元数据修改(创建文件、修改目录等)先以日志记录(log item)写入循环日志区,保证崩溃一致性。但日志空间有限,日志中的记录必须在合适时机回写到元数据的真实位置,才能将日志空间标记为可复用。
xfsaild 维护 AIL(Active Item List,活跃项链表),这是所有已提交日志项的有序链表。它持续将 AIL 中最老的日志项对应的元数据写回磁盘,写完后将该项从 AIL 删除,推进 AIL 头指针,释放日志空间。
若 xfsaild 停止工作(如磁盘错误),日志空间被写满后,所有 XFS 写操作将阻塞,表现为进程大量进入 D 状态。
[jbd2/xxx-n] — ext4 文件系统日志提交线程
诞生逻辑
挂载 ext4(启用日志模式,默认启用)文件系统时,JBD2(Journal Block Device 2)子系统创建该分区专属的 jbd2 线程。xxx 是设备名,n 是分区号,如 jbd2/sda1-8。
功能场景
ext4 的日志提交机制:文件系统的元数据修改先在内存中以 transaction 方式组织,jbd2 线程负责周期性(每 5 秒)或在事务满时,将 transaction 中的所有修改以原子方式写入日志区(journal),保证崩溃时可恢复。
与 xfsaild 类似,jbd2 之后还需要将日志中的数据 checkpoint 回元数据区。jbd2 线程持续工作在高 IO 负载下会造成日志设备(若是独立分区)的写 IO 压力集中。
[kraid5d] / [md_XXX] — 软件 RAID 线程
诞生逻辑
通过 mdadm 创建或启动 RAID 阵列时(md 内核模块加载后),为每个活跃的 md 设备创建专属的守护线程。
不同 RAID 级别有不同的线程:
- RAID 1/5/6 正常运行时:
md_XXX(XXX 为设备名)负责处理写请求的内部协调 - 阵列降级后换盘:
md_resync/XXX 或 md_reshape/XXX 线程负责重建
功能场景
场景一:写操作协调(RAID 5/6)每次写操作需要读取旧数据和旧奇偶校验值,计算新奇偶校验,再写回新数据和新奇偶校验(Read-Modify-Write)。这个计算过程在 md 线程中异步完成,避免阻塞写入方。
场景二:阵列重建(Rebuild)更换故障磁盘后,内核自动开始重建。md_resync 线程以较低 IO 优先级在后台从其他盘恢复数据,可通过 /proc/mdstat 看到进度和剩余时间。重建期间阵列不可用性风险极高,另一块盘再出问题数据即丢失。
场景三:定期一致性校验(Scrub)md_XXX 线程定期(默认每周)进行阵列一致性校验(check),读取全部数据并验证奇偶校验值,发现静默数据损坏(bit rot)。
[nfsd] — NFS 内核态文件服务线程
诞生逻辑
执行 systemctl start nfs-server 或手动 rpc.nfsd 8(8 为线程数)时,用户态程序通过写入 /proc/fs/nfsd/threads 通知内核创建对应数量的 nfsd 线程。线程数量可在运行时动态调整。
功能场景
nfsd 线程池直接在内核空间处理客户端的 NFS 协议请求,避免了数据在内核/用户空间之间拷贝的开销。每个 nfsd 线程独立处理一个客户端连接的请求序列:
- 读请求从本地文件系统读取数据,通过 RPC 返回给 NFS 客户端
- 写请求接收 NFS 客户端数据,写入本地文件系统(默认异步,
sync 挂载选项强制步) - 元数据操作文件创建、删除、属性查询,通过 NFSv3/v4 协议交互线程数不足时,NFS 客户端请求排队,表现为客户端 ls或读写操作卡顿。高并发 NFS 场景通常需要将线程数从默认 8 提升到 32 甚至更多。
四、调度与实时类
[migration/n] — 进程跨 CPU 迁移执行者
诞生逻辑
SMP 调度子系统初始化时(migration_init()),为每个 CPU创建一个 migration 线程,绑定到对应 CPU,设为最高实时优先级(SCHED_FIFO 99)。系统启动时全部创建,始终存在。
最高实时优先级的原因:迁移操作必须在目标 CPU 的调度器上下文中执行,且不允许被其他任务抢占,否则可能破坏调度器状态。
功能场景
当调度器决定将一个任务从 CPU A 迁移到 CPU B 时(负载均衡、CPU 热插拔、sched_setaffinity() 修改亲和性等),不能直接在 CPU A 上"强行移走"这个任务(任务可能正在另一个 CPU 上运行)。正确方式是:
- 调度器在 CPU B 的 migration 线程上挂起任务迁移请求
- CPU B 的 migration/n 以最高优先级运行,将目标任务"拉"到 CPU B 的运行队列
日常系统中 migration 线程几乎不消耗 CPU,但在 CPU 热插拔(echo 0 > /sys/devices/system/cpu/cpuN/online)时,所有运行在该 CPU 上的任务需要被迁移走,此时 migration 线程会有短暂的活跃期。
[watchdog/n] — CPU 死锁与挂起检测者
诞生逻辑
watchdog_enable() 在每个 CPU 上创建一个 watchdog 线程,绑定到对应 CPU,设为最高实时优先级(SCHED_FIFO 99)。它每 watchdog_thresh/2 秒(默认 5 秒)运行一次,更新该 CPU 的时间戳。
之所以需要最高实时优先级:watchdog 的职责是检测调度器本身是否失效。如果调度器卡死,只有最高优先级的线程才有机会被 NMI 打断运行。
功能场景
场景一:软死锁检测(Soft Lockup)每个 CPU 有一个 hrtimer 定期检查 watchdog 上次更新时间戳的时间点。若某 CPU 超过 watchdog_thresh(默认 20 秒)没有调度过 watchdog/n,说明该 CPU 一直被某段内核代码(while 循环、自旋锁)占用,调度器无法介入。内核打印进程调用栈,并根据 softlockup_panic 参数决定是否触发 panic 重启。
场景二:硬死锁检测(Hard Lockup)通过 NMI(非屏蔽中断)周期性中断每个 CPU,检查该 CPU 的硬中断计数器是否有变化。若超过阈值时间(默认 10 秒)计数器完全静止,说明连硬中断都无法执行,CPU 被内核代码彻底卡死(通常是持有自旋锁的路径进入了死循环)。直接触发 panic,生成 crash dump。
[khungtaskd] — D 状态进程超时告警者
诞生逻辑
内核初始化阶段由 hung_task_init() 创建,系统启动时始终存在,以 TASK_INTERRUPTIBLE 方式睡眠,每隔 hung_task_check_interval_secs(默认 120 秒)被定时器唤醒一次。
功能场景
遍历系统中所有处于 D(TASK_UNINTERRUPTIBLE,不可中断睡眠)状态的进程,记录它们首次进入 D 状态的时间。若某进程在 D 状态停留超过 hung_task_timeout_secs(默认 120 秒),内核打印该进程的调用栈 warning。
进程长时间处于 D 状态的典型原因:
- NFS 挂载点服务端不可达,进程等待 NFS IO 永久阻塞
- 文件系统死锁(如 ext4 journal 相关的锁冲突)
khungtaskd 本身不会杀死这些进程,只负责告警。hung_task_panic=1 可配置为直接触发 panic(配合 crash dump 用于生产环境故障分析)。
[stopper/n] — CPU 紧急停止线程
诞生逻辑
随 migration 线程一同在 SMP 初始化时创建,每个 CPU 一个,同样是最高实时优先级(SCHED_FIFO 99)。
功能场景
实现 stop_machine() 机制:需要让全系统所有 CPU 同时暂停执行时(如内核模块加载、某些关键数据结构修改、CPU 热插拔的最后阶段),stopper/n 线程在各自 CPU 上执行"停止函数",确保该 CPU 不在执行任何其他代码。
常见触发点:内核实时补丁(kpatch/livepatch)打补丁的瞬间,需要 stop_machine 保证没有 CPU 在执行被替换的代码片段。
在一些较新的内核中,stopper 的功能被整合进了 migration/n 或由 cpuhp/n 热插拔线程接管了部分状态流转,你会看到 migration或cpuhp而不是直接显示stopper 名称的内核线程。
五、RCU 并发控制类
[rcu_preempt] / [rcu_sched] — RCU 宽限期推进者
诞生逻辑
RCU(Read-Copy-Update)子系统在 rcu_init() 时创建这些线程。具体线程名取决于内核配置:
Linux 5.x 后将多个 RCU 变种合并,通常只见到 rcu_preempt。
功能场景
RCU 的核心思想:读者不加锁,写者更新时不替换旧数据,而是创建新副本,等所有"正在读旧数据的 CPU"都退出临界区后(宽限期结束),再释放旧数据。
rcu_preempt 负责检测和推进"宽限期"(Grace Period):判断所有 CPU 是否都经过了一次调度上下文切换(意味着没有 CPU 还在持有旧 RCU 读锁),条件满足后触发所有挂起的 RCU 回调(即释放旧数据)。
这是 Linux 内核中使用最广泛的并发机制,网络路由表、文件系统目录缓存(dentry)、进程描述符等核心数据结构的"无锁读"都依赖 RCU,rcu_preempt 线程则是这套机制的后台"仲裁者"。
[rcuog/n] / [rcuop/n] — RCU 回调卸载线程
诞生逻辑
仅在配置了 CONFIG_RCU_NOCB_CPU 且启动参数包含 rcu_nocbs=<cpu列表> 时创建。rcuog(offload grace period)和 rcuop(offload poll)线程对应一组 CPU,将这些 CPU 的 RCU 后台工作卸载出去。
功能场景
在实时系统或 HPC(高性能计算)场景中,某些 CPU 需要完全专注于实时任务,不能被 RCU 回调打断。rcuog/n 代替这些"隔离 CPU"处理 RCU 宽限期检测;rcuop/n 执行这些 CPU 积累的 RCU 回调函数(如内存释放),将延迟抖动完全转移到 rcuog/rcuop 所在的 CPU 上。
六、安全与加密类
[cryptd] — 异步加密任务执行者
诞生逻辑
当内核加密子系统(Crypto API)首次收到异步加密请求时,按需创建 cryptd 线程(懒创建模式)。负载不同时线程数量动态变化。若系统没有加密 IO,此线程可能不存在。
功能场景
加密操作(AES、SHA、GCM 等)在软件实现下 CPU 开销较大。cryptd 将这些操作从调用者的上下文(如网络 softirq)移出,在独立的内核线程中异步执行:
- IPsec VPN每个数据包的 ESP 加解密通过 cryptd 异步执行,不阻塞网络栈
- dm-crypt(LUKS 磁盘加密)块设备 IO 在 cryptd 中加解密,与磁盘 IO 并发执行
- TLS 卸载
现代 CPU 的 AES-NI 硬件加速指令极大降低了 cryptd 的 CPU 负担;无 AES-NI 的 CPU(部分嵌入式芯片)在 VPN 高流量时 cryptd 会成为明显瓶颈。
[kauditd] — 内核审计日志写出者
诞生逻辑
仅在内核编译时启用了 CONFIG_AUDIT 且系统启动时 audit 守护进程(auditd)运行时,kauditd 线程才会被创建。由 audit_init() 在 auditd 首次连接时创建。
功能场景
Linux 审计系统(auditd)跟踪安全相关的系统调用(文件访问、权限变更、网络连接、进程执行等)。内核在 syscall 执行路径中生成审计记录,放入内核缓冲区,kauditd 负责将这些记录通过 netlink socket 传递给用户空间的 auditd 进程写入日志。
kauditd 是内核到用户态的单向通道,将审计事件的"写盘"工作从内核关键路径中剥离。若 auditd 停止或日志磁盘写满,kauditd 缓冲区溢出时内核会根据配置丢弃记录或阻塞相关 syscall(audit_backlog_limit、audit_failure 参数控制行为)。
七、电源与硬件管理类
[cpuhp/n] — CPU 热插拔协调线程
诞生逻辑
CPU 热插拔子系统(cpuhp_threads_init())在系统启动时为每个 CPU 创建一个 cpuhp 线程。平时全部休眠,只在 CPU 状态发生变化时被唤醒。
功能场景
执行 echo 0 > /sys/devices/system/cpu/cpu3/online 或运行 systemctl isolcpus 类操作时,CPU 下线流程必须:
- 将该 CPU 上运行的所有任务迁移到其他 CPU(通过 migration 线程)
- 通知每个内核子系统(调度器、RCU、timers 等)该 CPU 已下线
这些步骤通过 cpuhp 线程按注册的回调链依序执行,保证各子系统有序感知 CPU 的变化,而不是直接关闭 CPU 导致数据结构损坏。
[acpi_thermal_pm] — ACPI 热管理执行线程
诞生逻辑
ACPI 热管理子系统(acpi_thermal_init())在检测到平台有温度传感器和散热管理对象时创建,通常在系统启动后不久,ACPI 表解析完成时诞生。
功能场景
持续监控平台温度传感器(CPU Package temp、主板传感器等)读数,与 ACPI 热区(Thermal Zone)定义的阈值对比:
- 温度超过被动冷却阈值(Passive Trip Point):触发 CPU 降频(通过 ACPI P-state 或 cpufreq)
- 温度超过主动冷却阈值(Active Trip Point):控制风扇转速提升
- 温度超过临界阈值(Critical Trip Point):通知系统执行紧急关机,防止硬件损坏
笔记本电脑和服务器的"散热管理"本质上由这个线程居中协调。
[kacpid] / [kacpi_notify] — ACPI 事件处理线程
诞生逻辑
ACPI 子系统初始化完成后,由 acpi_os_create_thread() 创建,专门处理 ACPI 事件队列。
功能场景
当硬件事件通过 ACPI 上报时(按下电源键、合上笔记本盖子、插入/拔出 AC 适配器、温度阈值报警),ACPI 固件通过 SCI 中断(System Control Interrupt)通知内核。内核中断处理程序仅将事件加入队列,kacpid 在进程上下文中执行对应的 ACPI 方法(AML 字节码),再将事件传递给上层(如 systemd-logind 处理合盖/开盖动作)。
现代 Linux 内核中 ACPI 事件处理已逐步由 kworker/uevent 机制及 systemd-logind 统一接管,若内核配置或实现路径不同(或未启用相关 ACPI 线程模型),则不会出现独立的 [kacpid]/[kacpi_notify] 内核线程。
八、虚拟化与现代 IO 类
[vhost-n] / [vhost_net] — 虚拟机 IO 内核加速线程
诞生逻辑
QEMU/KVM 启动虚拟机,如果虚拟机使用 virtio 网卡,QEMU 通过 /dev/vhost-net 将 virtio 网络数据面后端 offload 到内核 vhost-net 模块;同理,如果使用 virtio 磁盘(virtio-blk / virtio-scsi)则分别通过 vhost-blk / vhost-scsi 或 QEMU block backend 进行处理。内核在绑定 vhost 设备后,为对应的 vhost backend context 创建内核线程(通常表现为 vhost-*,线程命名与设备/队列相关)。
功能场景
传统 virtio 模型中,虚拟机的网络数据需经过:虚拟机内核 → KVM → QEMU 用户态 → 宿主机内核网络栈,路径较长,其中 QEMU 参与 virtio backend 数据处理。vhost_net 将 virtio backend 数据路径从 QEMU 用户态迁移至内核:
- 虚拟机发包:vhost_net 线程直接从共享 virtqueue 中取出数据描述符,构建 skb 并注入宿主机网络栈
- 宿主机收包:内核网络栈数据直接写入 virtqueue,并通过 eventfd 通知虚拟机
该机制减少 virtio datapath 中 QEMU 用户态参与与多次上下文切换,降低延迟并提升吞吐能力。在网络密集型虚拟机(如转发类场景)中,vhost_net 可能成为宿主机 CPU 消耗的重要来源之一。
[io_wq_mgr] / [io_wq/n] — io_uring 后台 IO 工作线程
诞生逻辑
应用程序调用 io_uring_setup() 创建 io_uring 实例时,内核按需创建 io_wq 线程(懒创建)。当提交的 IO 操作无法立即完成(如普通文件的随机读写,内核实际上需要等待磁盘)时,这些操作被转交给 io_wq 线程池异步执行。
功能场景
io_uring 的设计目标是将 IO 的提交和回收与系统调用解耦,应用通过共享内存的环形队列与内核通信,理想情况下零系统调用。但并非所有 IO 都能真正异步——io_wq 就是处理那些"假异步"操作的托底:
O_DIRECT文件读写在某些文件系统上仍需同步等待openat()、statx()等元数据操作getdents64()目录遍历
io_wq 线程池的存在使 io_uring 能对上层应用保持完全异步的语义,即使底层实现不得不阻塞。高并发服务(Nginx、数据库)迁移到 io_uring 后,io_wq 线程的存在是其异步语义的保障。
九、调试与监控类
[kgdboc] — 内核调试串口线程
诞生逻辑
仅在内核启动参数包含 kgdboc=ttyS0,115200 类配置,且 KGDB(内核 GDB 调试)功能编译进内核时存在。
功能场景
允许通过串口(或其他串行接口)连接 GDB 调试器,对运行中的内核进行断点、单步、内存查看等调试操作。kgdboc 线程处理串口通信和调试协议,是内核"远程调试"能力的核心。嵌入式 Linux 开发和内核驱动调试场景中使用。
[ksyms_optimized] / [kthread_cpuhp] — 符号表与内存管理辅助
诞生逻辑
部分发行版内核(如 Ubuntu、RHEL 的 OOT 补丁)会引入额外的辅助线程,在系统符号表更新(模块加载/卸载)或 CPU 状态变更时,完成相关数据结构的异步更新,避免在关键路径上阻塞。
快速索引:按触发条件分类
系统启动时创建(始终存在)
kthreadd · kworker · ksoftirqd/n · kswapd/n · migration/n · watchdog/n · stopper/n · khungtaskd · rcu_preempt · kcompactd/n
挂载文件系统时创建
writeback(每个 BDI 设备)· xfsaild(XFS 分区)· jbd2/xxx(ext4 分区)
按需/条件创建
nfsd(启动 NFS 服务时)· vhost_net(KVM 虚拟机启动时)· cryptd(首次异步加密请求)· io_wq(io_uring 实例创建时)· ksmd(echo 1 > /sys/kernel/mm/ksm/run)· khugepaged(THP 非 never 时)
ACPI/硬件相关
kacpid · acpi_thermal_pm(ACPI 硬件平台)· cpuhp/n(CPU 热插拔子系统)
每个内核线程都是内核设计者解决某个具体工程问题的结果。看懂它们,就是看懂 Linux 如何在软件层面协调硬件、内存、IO 和进程之间的复杂关系。