rk3588/kernel-6.1 kernel 内核模块架构详解
本文聚焦 rk3588/kernel-6.1/kernel 目录。该目录不是单一功能模块,而是 Linux 内核“通用核心层”聚合区:进程/调度/同步/中断/时间/模块装载/系统控制等都在这里落地实现。
1. kernel/ 在内核全局中的位置
可以把内核粗略分成:
init/:早期启动与 initcall 驱动初始化mm/:内存管理fs/:VFS 与文件系统net/:网络协议栈drivers/:设备驱动kernel/:跨子系统通用核心机制
kernel/Makefile 显示其既包含单文件核心(如 fork.o、exit.o、sysctl.o、workqueue.o),也包含关键子目录(sched/、locking/、irq/、rcu/、time/、module/、printk/、cgroup/ 等)。
从职责上看,kernel/ 更像“操作系统运行时公共层”。drivers/ 负责具体硬件,fs/ 负责文件系统抽象,net/ 负责协议栈,mm/ 负责内存;而 kernel/ 负责把这些模块串成一个可运行的系统:
- 进程由
fork.c创建后交给sched/调度; - 驱动通过
irq/接收硬件中断,再通过softirq.c或workqueue.c延后处理; - 文件系统、网络、驱动都依赖
locking/和rcu/做并发保护; - 模块驱动由
module/加载,参数由params.c解析,运行时策略由sysctl.c调整; - 故障时由
printk/、panic.c、watchdog.c、trace/提供诊断能力。
所以阅读 kernel/ 不能只按目录理解,还要按“生命周期”理解:任务创建、调度运行、中断唤醒、同步保护、定时超时、退出回收、日志诊断,这些路径会跨越多个文件。
2. 构建与配置组织方式(Kbuild/Kconfig)
2.1 Kbuild 组织(kernel/Makefile)
obj-y 和 obj-$(CONFIG_*) 决定了模块装配:
- 始终编译的核心:
fork.o、exit.o、signal.o、sys.o、pid.o、kthread.o、workqueue.o、cred.o、reboot.o等 - 按配置启用:
CONFIG_MODULES -> module/ + kmod.oCONFIG_AUDIT -> audit*.oCONFIG_KPROBES -> kprobes.oCONFIG_SECCOMP -> seccomp.oCONFIG_BPF -> bpf/CONFIG_CGROUPS -> cgroup/ - 核心子目录装配:
sched/、locking/、power/、printk/、irq/、rcu/、time/
Kbuild 的作用不只是“把文件编译进去”。它还决定功能边界和依赖关系:
obj-y表示内核核心必须存在的能力,例如进程、调度、信号、内核线程;obj-$(CONFIG_xxx)表示功能受配置控制,例如 BPF、audit、seccomp、module、cgroup;- 子目录形式通常代表较复杂的框架,例如
sched/、irq/、time/、rcu/,这些目录内部还会根据配置选择不同实现; - 同一个 API 在不同配置下可能编译出不同路径,例如抢占模型、RT mutex、lockdep、NO_HZ、RCU 类型。
这解决了 Linux 需要同时适配服务器、桌面、手机、嵌入式 SoC 的问题:同一套源码通过 Kconfig/Kbuild 组合出不同内核能力。
2.2 Kconfig 入口特点
kernel/ 下常见为分片配置文件(而非单独 kernel/Kconfig):
Kconfig.preempt:内核抢占模型(NONE/VOLUNTARY/PREEMPT/RT)Kconfig.hz:时钟中断频率(100/250/300/1000Hz)Kconfig.locks:自旋锁/读写锁/排队锁等锁实现策略Kconfig.freezer:freezer 开关(与休眠/cgroup freezer 相关)
这些配置会直接改变运行时行为:
CONFIG_PREEMPT_NONE更偏吞吐,内核态长路径不主动抢占;CONFIG_PREEMPT_VOLUNTARY在显式点让出 CPU;CONFIG_PREEMPT降低交互延迟;CONFIG_PREEMPT_RT会改变大量锁和中断线程化语义;CONFIG_HZ影响 tick 周期,进一步影响调度粒度、timer 精度和功耗;CONFIG_LOCKDEP增加锁依赖检查,适合调试但有额外开销;CONFIG_NO_HZ减少空闲 CPU 周期 tick,有助于功耗和虚拟化场景。
因此分析 kernel/ 代码时必须结合 .config,否则同一处源码在实际运行中可能走完全不同的条件编译路径。
3. kernel/ 目录具备的功能全景
kernel/ 目录可以理解为 Linux 的“通用运行时核心”。它不直接对应某个外设,而是给全系统提供进程、调度、中断、同步、时间、模块、安全、调试、资源控制等基础能力。
| 功能类别 | 代表文件/目录 | 主要能力 |
|---|---|---|
| 进程生命周期 | fork.c、exit.c、pid.c |
创建进程/线程、分配 PID、退出回收、父子关系维护 |
| 调度 | sched/ |
CFS/RT/DL/idle 调度类、上下文切换、负载均衡、CPU 亲和性 |
| 信号与 ptrace | signal.c、ptrace.c |
信号发送/递送/处理、调试器跟踪进程 |
| 凭证与权限 | cred.c、capability.c、groups.c |
UID/GID、capability、进程凭证复制与提交 |
| 系统调用公共实现 | sys.c、sys_ni.c、exec_domain.c |
通用 syscall 支撑、未实现 syscall 占位、执行域兼容 |
| 内核线程与异步执行 | kthread.c、workqueue.c、async.c、task_work.c |
内核线程、工作队列、异步任务、返回用户态前回调 |
| 中断与下半部 | irq/、softirq.c、irq_work.c |
IRQ 注册/分发、irqdomain、MSI、softirq、tasklet、irq_work |
| 同步与并发 | locking/、rcu/ |
mutex/rwsem/spinlock/rtmutex/lockdep、RCU/SRCU 宽限期 |
| 时间与定时器 | time/ |
timekeeping、clocksource、tick、hrtimer、POSIX timer、NTP |
| 模块加载 | module/、kmod.c、params.c |
.ko 加载/卸载、ELF 重定位、符号解析、模块参数 |
| 日志与 panic | printk/、panic.c、reboot.c |
内核日志、console 输出、panic、重启/关机 |
| 系统配置接口 | sysctl.c、utsname_sysctl.c、ksysfs.c |
/proc/sys、sysfs 内核信息、运行时参数调优 |
| namespace | nsproxy.c、pid_namespace.c、user_namespace.c、utsname.c |
PID/user/UTS namespace 及进程 namespace 关联 |
| cgroup/资源控制 | cgroup/、ucount.c、user.c |
cgroup 层级、任务归属、用户资源计数与限制 |
| 安全与审计 | seccomp.c、audit*.c |
syscall 过滤、审计规则、审计事件生成 |
| futex | futex/ |
用户态锁的内核等待/唤醒基础 |
| tracing/观测 | trace/、tracepoint.c、events/ |
ftrace、tracepoint、kprobe/uprobe、perf events、ring buffer |
| BPF | bpf/ |
eBPF 程序、map、helper、验证器与运行时支撑 |
| DMA 映射 | dma/ |
DMA mapping/coherent/CMA/SWIOTLB/IOMMU 抽象 |
| 电源管理 | power/、freezer.c、cpu_pm.c |
suspend/hibernate、wakelock、freezer、CPU PM 通知 |
| CPU/SMP/hotplug | cpu.c、smp.c、smpboot.c、stop_machine.c |
CPU 上下线、SMP call、stop_machine、启动辅助线程 |
| 崩溃与热重启 | crash_core.c、kexec*.c、crash_dump.c |
kexec、kdump、崩溃内核准备 |
| 动态补丁/优化 | livepatch/、jump_label.c、static_call*.c |
livepatch、静态分支、static call 热路径优化 |
| 调试检测 | watchdog.c、hung_task.c、debug/、kcov.o、kcsan/、gcov/ |
lockup/hung task 检测、KGDB/KDB、覆盖率、并发竞争检测 |
4. 核心子模块逐项详解
4.1 进程创建与退出:fork.c / exit.c / pid.c
要解决的问题
Linux 必须把“一个程序正在运行”抽象成可调度、可管理、可回收的任务。进程/线程生命周期模块解决四类问题:
- 如何从当前任务复制出新任务;
- 如何给任务分配 PID、父子关系、线程组关系;
- 如何让新任务接入调度器、cgroup、namespace、审计、perf 等系统;
- 如何在任务退出时按顺序释放所有资源,并通知父进程
wait()。
实现的功能
fork.c 实现 fork()、vfork()、clone()、clone3()、kernel_thread() 等入口,核心是 kernel_clone() 与 copy_process()。exit.c 实现 exit()、exit_group()、wait4()、waitid() 等,核心是 do_exit() 和 do_wait()。pid.c 负责 PID 分配、查找、引用计数和 PID namespace 下的 PID 映射。
创建路径如何实现
进程创建主链路:
1 | 用户态 clone/fork/vfork |
copy_process() 不是简单复制 task_struct。它按资源语义决定“共享还是复制”:
CLONE_VM决定是否共享地址空间;CLONE_FILES决定是否共享文件描述符表;CLONE_FS决定是否共享根目录、当前目录、umask;CLONE_SIGHAND/CLONE_THREAD决定信号处理和线程组关系;- namespace、cred、cgroup、perf、seccomp、futex、io_uring 都会参与校验或初始化。
它解决的问题是:让 fork() 看起来像“复制进程”,但在实现上尽量共享资源、延迟复制,并维持内核对象引用计数正确。
退出路径如何实现
退出主链路:
1 | 用户态 exit/exit_group 或 fatal signal |
do_exit() 的核心是“有序拆除”。顺序非常重要:例如地址空间、文件表、信号、cgroup、perf、RCU 回收和父进程通知之间存在依赖。如果顺序错误,可能出现 use-after-free、父进程无法 wait、僵尸进程无法回收等问题。
用途与典型场景
- 用户程序创建进程、线程;
- 内核创建 kthread;
- 容器运行时创建带 namespace/cgroup 的任务;
- shell 等待子进程退出;
ps/top观察进程状态;- OOM、fatal signal、panic 前任务清理。
4.2 调度核心:sched/
要解决的问题
系统中 runnable 任务通常远多于 CPU 数量。调度器要决定:
- 哪个任务现在运行;
- 任务何时被抢占;
- 唤醒任务应该放到哪个 CPU;
- RT/DL/CFS/idle 等不同策略如何共存;
- 多核和 RK3588 大小核平台上如何平衡性能、功耗和实时性。
实现的功能
kernel/sched/ 包含:
core.c:调度主框架、上下文切换、唤醒、调度系统调用;fair.c:CFS,普通进程调度;rt.c:实时调度SCHED_FIFO/SCHED_RR;deadline.c:SCHED_DEADLINE;idle.c:idle 任务;stop_task.c:stop machine 等最高优先级任务;topology.c:CPU 拓扑、调度域;cpufreq_schedutil.c:schedutil 频率调节;pelt.c:PELT 负载跟踪;psi.c:Pressure Stall Information。
核心实现机制
每个 CPU 有一个 runqueue:
1 | DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); |
任务切换主链路:
1 | schedule() |
唤醒主链路:
1 | try_to_wake_up() |
CFS 的核心思想是用 vruntime 近似公平运行时间。RT 调度按优先级抢占。Deadline 使用运行时间、周期、截止期描述任务需求。
解决的问题
- 防止一个任务长期独占 CPU;
- 让交互任务获得较低延迟;
- 让实时任务按优先级抢占普通任务;
- 在多核间迁移任务以避免某些 CPU 过载;
- 为 cpufreq 提供负载信号;
- 支撑 cgroup CPU 控制和 PSI 压力统计。
RK3588 关注点
RK3588 是大小核 SoC,调度器还要结合 CPU capacity、能效模型、cpufreq、thermal pressure。普通 CFS 任务不仅要“公平”,还要尽量放到合适性能/能耗的 CPU 上。性能抖动、卡顿、功耗异常通常都需要同时看 sched/、time/、cpufreq 和设备中断负载。
4.3 并发同步:locking/
要解决的问题
内核同时运行在多 CPU、硬中断、软中断、内核线程和用户进程上下文中。共享数据如果没有同步,会产生竞态;同步过度又会死锁或拖慢系统。
locking/ 解决:
- 原子上下文不能睡眠时如何保护数据;
- 进程上下文可睡眠时如何等待资源;
- 多读少写场景如何降低开销;
- 如何发现死锁和错误加锁顺序;
- PREEMPT_RT 下如何让锁更适合实时性。
实现的功能
mutex.c:可睡眠互斥锁;rwsem.c:读写信号量;semaphore.c:传统信号量;spinlock.c、qspinlock.c:自旋锁和排队自旋锁;qrwlock.c:排队读写锁;rtmutex_api.c:带优先级继承的实时互斥锁;lockdep.c:锁依赖图和死锁检测;osq_lock.c:乐观自旋队列。
实现机制
mutex 的基本流程:
1 | mutex_lock() |
spinlock 的基本流程:
1 | spin_lock() |
lockdep 的思路是记录“锁类”和“获取顺序”,构造依赖图。如果发现可能形成环,就报告潜在死锁。
用途
rq->lock保护调度队列;tasklist_lock保护进程树;files_struct、inode、dentry、网络队列等大量对象依赖锁;- RT 内核中许多 spinlock 会转换成可睡眠 rtmutex 语义。
解决的问题
它让内核能在并发环境下保持数据一致,同时用 lockdep 在开发阶段尽早发现 ABBA 死锁、睡眠上下文错误、IRQ 上下文锁错误。
4.4 延迟回收与读多写少并发:rcu/
要解决的问题
很多内核数据结构读多写少,例如任务列表、路由表、文件描述符表、dentry cache。读路径如果每次都加锁,会严重影响性能。
RCU 解决的问题是:读者几乎无锁,写者更新后等待所有旧读者离开,再释放旧对象。
实现的功能
update.c:RCU 通用 API;tree.c:Tree RCU 主实现;srcutree.c/srcutiny.c:可睡眠 RCU;sync.c:同步等待;rcu_segcblist.c:回调分段管理;rcutorture.c、rcuscale.c:压力测试。
实现机制
典型更新:
1 | 读者: |
RCU 需要判断一个 grace period:所有在旧指针发布前已经进入 RCU 读侧临界区的 CPU/任务都退出后,旧对象才可释放。
用途
- 网络路由查找;
- 进程 PID 查找;
- 文件系统路径缓存;
- cgroup、namespace、模块列表;
- BPF map 和 tracing 数据结构。
解决的问题
RCU 把读路径从“竞争锁”变成“轻量标记 + 内存屏障”,极大降低多核读密集路径开销。代价是写路径更复杂,且必须严格遵守 RCU 指针访问规则。
4.5 中断核心与下半部:irq/ / softirq.c / irq_work.c
要解决的问题
硬件中断来自不同控制器、不同硬件编号、不同触发方式。驱动希望只看到统一的 Linux IRQ 编号和统一注册接口。
中断核心解决:
- 硬件 IRQ 到 Linux IRQ 的映射;
- 中断处理函数注册与释放;
- 中断屏蔽、确认、EOI、重触发;
- threaded IRQ;
- MSI/MSI-X;
- 中断亲和性;
- 高耗时工作下放到下半部。
实现的功能
kernel/irq/:
irqdesc.c:irq_desc分配和管理;manage.c:request_threaded_irq()、free_irq()、__setup_irq();handle.c:handle_irq_event()等事件分发;chip.c:irq_chip通用操作封装;irqdomain.c:硬件中断号到 Linux IRQ 映射;msi.c:MSI/MSI-X domain;affinity.c:中断亲和性。
softirq.c:
- softirq 向量;
- tasklet;
ksoftirqd;__do_softirq()。
注册和分发链路
驱动注册:
1 | request_threaded_irq() |
硬件触发后:
1 | arch/GIC 入口 |
softirq 路径:
1 | raise_softirq() |
用途与解决的问题
- 设备驱动中断处理;
- 网络收包
NET_RX_SOFTIRQ; - 定时器
TIMER_SOFTIRQ; - RCU 回调;
- block 层完成通知;
- threaded IRQ 避免硬中断中做耗时操作。
中断框架把硬件差异隐藏在 irq_chip / irq_domain 后面,让驱动用统一 API 工作。
4.6 时间与定时器:time/
要解决的问题
内核必须同时处理:
- 当前时间是多少;
- 定时器什么时候到期;
- 调度 tick 如何产生;
- 高精度定时如何保证;
- NTP 如何校准时间;
- CPU idle/nohz 时如何减少 tick。
实现的功能
kernel/time/ 中:
timekeeping.c:维护墙钟、monotonic、boottime;clocksource.c:选择和管理时钟源;clockevents.c:管理定时事件设备;jiffies.c:传统 tick 计数;timer.c:低精度 timer wheel;hrtimer.c:高精度定时器;tick-sched.c:NO_HZ、调度 tick;posix-timers.c:POSIX timer;ntp.c:NTP 校准;namespace.c:time namespace。
核心实现机制
时间更新:
1 | clocksource 提供 cycle |
高精度定时:
1 | hrtimer_start() |
调度 tick:
1 | tick_sched_timer() |
用途
schedule_timeout();- socket 超时;
- block IO 超时;
- hrtimer 驱动;
- 用户态
clock_gettime(); - POSIX timer;
- 调度器 PELT 和 CPU 使用统计。
时间子系统解决的是“内核所有等待、超时、统计和调度节拍的共同时间基准”。
4.7 日志、panic 与重启:printk/ / panic.c / reboot.c
要解决的问题
内核出现异常时,用户态可能不可用,文件系统可能不可写,甚至中断和锁都可能异常。日志系统必须尽量可靠地记录信息,并输出到 console。
实现的功能
printk/:
printk.c:printk()、vprintk_emit()、console 输出;printk_ringbuffer.c:无锁/低锁日志环形缓冲;printk_safe.c:特殊上下文安全打印;sysctl.c:printk loglevel 等运行时控制。
panic.c:
panic();- oops/panic 控制;
- panic notifier;
- kmsg dump;
- panic timeout 和重启。
reboot.c:
kernel_restart();kernel_power_off();- reboot notifier;
- 系统关机/重启路径。
实现机制
1 | printk() |
panic 路径:
1 | panic() |
用途
- 驱动调试;
- boot log;
- crash 分析;
- watchdog/hung task 输出;
- 串口 early console;
- 生产环境故障定位。
4.8 工作队列、内核线程与异步执行:workqueue.c / kthread.c / async.c / task_work.c
要解决的问题
很多内核工作不能在当前上下文直接执行:
- 中断上下文不能睡眠;
- 持锁路径不能做耗时操作;
- 启动阶段硬件探测可以并行;
- 某些工作需要绑定 CPU 或允许 freezer。
工作队列如何实现
workqueue.c 提供通用异步执行机制。源码注释明确说明:work item 在进程上下文执行,worker pool 自动管理。每 CPU 通常有 normal/highpri 两类 pool,unbound workqueue 使用动态 worker pool。
调用链:
1 | INIT_WORK() |
它解决的问题是:驱动和子系统可以把“稍后执行、可睡眠、可能耗时”的任务交给统一 worker,而不是每个模块都创建自己的线程。
内核线程如何实现
kthread.c 提供:
kthread_create();kthread_run();kthread_should_stop();kthread_stop();- park/unpark;
- CPU 绑定。
典型模式:
1 | kthread_run(threadfn, data, name) |
async.c 如何提高启动速度
async.c 的目标是减少启动时间。它把一些独立硬件延迟和发现操作异步执行,同时用 cookie 保证外部可见顺序:
1 | async_schedule() |
如果某个阶段必须等待之前异步任务完成,则调用 async_synchronize_full() 或 async_synchronize_cookie()。
task_work 的用途
task_work.c 允许把回调挂到某个 task 上,在该 task 返回用户态或退出前执行。io_uring、文件关闭延后处理等会用到它。
4.9 内核模块装载框架:module/ + kmod.c + params.c
要解决的问题
驱动不可能全部静态编进内核。模块框架解决:
.ko如何加载到内核地址空间;- 符号如何解析;
- ELF relocation 如何处理;
- 模块参数如何解析;
- init/exit 如何调用;
- 模块是否可卸载;
- 依赖、引用计数、sysfs/proc 如何维护;
- 签名、vermagic、license 如何检查。
加载路径
1 | finit_module/init_module syscall |
卸载路径:
1 | delete_module syscall |
kmod.c 的用途
kmod.c 处理内核请求用户态加载模块的场景,例如设备热插拔后触发:
1 | request_module("xxx") |
它解决的是“内核发现需要某个驱动时,自动让用户态 modprobe 加载”。
params.c 的用途
模块参数和内核启动参数都需要解析。params.c 提供通用参数解析、类型转换、权限和 sysfs 暴露支持。
4.10 系统控制与参数接口:sysctl.c / ksysfs.c / resource.c
要解决的问题
内核运行后仍需要调节参数,例如 panic 策略、调度调试、pid 上限、printk 级别、perf 权限等。不能每次都重新编译内核,所以需要统一运行时配置入口。
sysctl 如何实现
sysctl.c 提供 /proc/sys 树:
1 | register_sysctl() |
常见 handler:
proc_dointvec():整数;proc_dointvec_minmax():带范围整数;proc_dostring():字符串;proc_doulongvec_minmax():unsigned long;proc_do_static_key():控制 static key。
其他接口
ksysfs.c:在 sysfs 暴露内核信息;resource.c:管理 I/O memory、I/O port 等资源树;ucount.c:用户级资源计数;range.c:范围管理辅助。
用途
/proc/sys/kernel/*;/proc/iomem;- 调试运行时行为;
- 给发行版和产品配置内核策略。
4.11 cgroup 与资源控制:cgroup/
要解决的问题
多进程、多容器场景中,需要按组管理资源和统计压力。cgroup 解决:
- 任务如何归属某个资源组;
- controller 如何挂接;
- fork/exit/migrate 时资源关系如何更新;
- 统计如何聚合;
- 用户态如何通过 cgroupfs 管理。
实现机制
核心概念:
cgroup:层级节点;css_set:任务关联的一组 subsystem state;cgroup_subsys_state:具体 controller 状态;cgroup_file:cgroupfs 文件;rstat:层级统计刷新。
任务 fork 时:
1 | copy_process() |
任务迁移时:
1 | 写 cgroup.procs |
用途
- 容器资源隔离;
- CPU/memory/io/pids 控制;
- PSI 压力统计;
- systemd slice/service 管理。
4.12 安全、凭证与审计:cred.c / capability.c / seccomp.c / audit*.c
凭证模型解决什么问题
内核不能只看数值 uid/gid,还要支持 capability、keyring、安全模块等。cred.c 把任务权限封装成不可随意原地修改的 struct cred。
典型修改流程:
1 | prepare_creds() |
这样解决了权限对象并发访问问题:读者可以稳定读取旧 cred,写者提交新 cred。
capability 的用途
capability.c 把 root 权限拆成多个能力位,例如:
CAP_SYS_ADMIN;CAP_NET_ADMIN;CAP_SYS_MODULE;CAP_SYS_PTRACE。
它解决的问题是避免所有特权操作都依赖 uid 0。
seccomp 如何实现 syscall 过滤
seccomp.c 支持:
- strict mode;
- filter mode;
- BPF filter;
- user notification;
- 违规时 kill、trap、errno、trace、log 等动作。
典型用途:
1 | 容器/沙箱 |
audit 的用途
audit*.c 记录安全相关事件,包括 syscall、文件访问、规则匹配、进程身份等。它主要服务合规、安全审计和入侵追踪。
4.13 系统调用公共层:sys.c / sys_ni.c / exec_domain.c
要解决的问题
系统调用入口通常在架构目录中完成寄存器保存和 syscall number 分发,但大量 syscall 的通用逻辑与架构无关,需要放在 kernel/。这部分解决“用户态请求如何转换成内核通用服务”的问题。
实现的功能
sys.c:实现大量通用 syscall,例如setpriority()、getpriority()、sysinfo()、reboot()辅助、进程组/session 相关接口等;sys_ni.c:为未实现或未启用的 syscall 提供sys_ni_syscall()占位,返回-ENOSYS;exec_domain.c:保留执行域兼容相关框架;umh.c:用户态 helper 执行基础,被kmod.c等使用。
实现机制
典型 syscall 路径:
1 | 用户态 svc/syscall 指令 |
sys_ni.c 的意义是让 syscall table 在配置裁剪后仍有明确占位,用户态调用未启用能力时得到稳定错误,而不是跳到未知地址。
用途
这部分是用户态进入内核的公共服务层。分析应用行为、strace 结果、seccomp 规则、权限检查时,经常需要从 SYSCALL_DEFINE* 找到 kernel/ 或其他子系统的实际实现。
4.14 namespace 与隔离:nsproxy.c / pid_namespace.c / user_namespace.c / utsname.c
要解决的问题
容器需要让不同进程看到不同的 PID 空间、主机名、用户权限映射、挂载视图、网络栈等。namespace 解决“同一个内核中提供多套隔离视图”的问题。
实现的功能
nsproxy.c:把一个任务关联到一组 namespace;pid_namespace.c:PID namespace 生命周期、PID 映射、init 进程语义;user_namespace.c:用户 namespace、uid/gid 映射、capability 隔离;utsname.c/utsname_sysctl.c:hostname/domainname namespace;- 其他 namespace 分布在对应子系统,例如 mount namespace 在
fs/,network namespace 在net/。
实现机制
任务创建和 namespace 关系:
1 | clone/unshare/setns |
PID namespace 的关键点是同一个 struct pid 可以在不同层级 namespace 中有不同数字。容器内看到的 PID 1,宿主机上可能是另一个 PID。PID namespace 的 init 进程退出时,该 namespace 内任务需要被清理。
User namespace 的关键点是 capability 作用域被限制在 namespace 内。容器内 uid 0 不等价于宿主机全局 root,除非映射和权限允许。
用途
- Docker/containerd/systemd-nspawn 等容器;
- Android 应用/服务隔离中的部分机制;
- 测试环境隔离;
- 安全沙箱。
namespace 不是单独完成隔离的,它通常与 cgroup、seccomp、capability、LSM、mount/network 子系统共同构成容器安全边界。
5. 其他重要功能模块
5.1 信号、ptrace 与进程控制:signal.c / ptrace.c
要解决的问题
普通函数调用是同步的,但操作系统需要支持异步事件:终端按 Ctrl-C、父进程杀子进程、定时器超时、非法访问内存、调试器暂停任务等。信号机制解决“异步通知进程并在安全时机交给用户态处理”的问题。
signal.c 实现进程间异步通知机制:
kill/tkill/tgkill/rt_sigqueueinfo等系统调用;- 普通信号与实时信号排队;
- 信号屏蔽、挂起、递送与用户态 handler 返回;
- fatal signal 触发进程退出。
实现机制
信号发送大致路径:
1 | kill/tgkill/内核异常 |
信号递送不是立即强行打断任意内核代码,而是在任务准备返回用户态、或从可中断睡眠中醒来时处理:
1 | 返回用户态前 |
这样解决了“异步事件”和“内核临界区安全”之间的矛盾。
ptrace.c 支撑调试器控制被调试进程:
PTRACE_ATTACH/PTRACE_SEIZE;- 读写寄存器、内存与信号注入;
- syscall enter/exit 跟踪;
- 与
seccomp、signal、wait紧密耦合。
这部分决定了 gdb、strace、crash dump、容器运行时调试等能力。
ptrace 的关键在于把被调试任务置于可观察、可控制的 stop 状态,并允许调试器读取寄存器、修改内存、拦截 syscall。它和信号、wait、seccomp 都有关:调试器看到的很多事件最终都通过 signal/wait 语义通知。
5.2 futex 用户态锁支撑:futex/
要解决的问题
如果每次 pthread mutex 加锁都进入内核,用户态同步会非常慢。futex 的目标是:无竞争时完全用户态,只有竞争时进入内核睡眠/唤醒。
用户态锁在无竞争时用原子指令完成;发生竞争时才进入内核:
1 | 用户态原子操作失败 |
它支撑 pthread mutex、condition variable、Java/Go/Rust 运行时锁等大量用户态同步机制。
因此 futex 是 Linux 用户态并发性能的关键基础。
实现机制
futex 以用户态地址为 key,把等待者挂到内核 hash bucket:
1 | futex_wait(uaddr, expected) |
这解决了经典 lost wakeup 问题:futex_wait() 入睡前必须再次确认用户态值仍符合预期,避免 unlock 已经发生但线程却睡下去。
高级能力
futex/ 还包含:
- PI futex:优先级继承,解决实时任务优先级反转;
- requeue:条件变量 broadcast 时把等待者从一个 futex 转移到另一个 futex;
- robust list:进程死掉时释放其持有的用户态锁;
- timeout:支持带超时等待。
5.3 perf events 与 tracing:events/ / trace/ / tracepoint.c
要解决的问题
内核需要可观测性:性能瓶颈在哪里、调度延迟来自哪里、中断是否频繁、函数调用路径如何、某个事件参数是什么。perf 和 tracing 解决“低开销、运行时可开启的内核观测”问题。
kernel/events/ 是 perf event 核心:
- CPU PMU 事件;
- software events;
- tracepoint 事件;
- perf ring buffer;
- task/cgroup 维度统计;
- 与 BPF、ftrace、scheduler、irq 等联动。
kernel/trace/ 是 ftrace/tracefs 主体:
- function tracer;
- function graph tracer;
- irqsoff/preemptoff/sched wakeup tracer;
- kprobe/uprobe/eprobe event;
- synthetic event 和 histogram trigger;
- ring buffer 和 trace_pipe;
- runtime verification(
trace/rv/)。
tracepoint.c 提供静态 tracepoint 注册与调用机制。
驱动、调度器、网络、块层等都通过 tracepoint 暴露可观测事件。
perf 实现机制
perf 把一个观测点抽象成 struct perf_event:
1 | perf_event_open() |
采样数据通过 perf ring buffer 传给用户态。对于硬件 PMU,底层由架构 PMU 驱动配置计数器;对于 tracepoint/software event,则由内核事件点主动输出样本。
ftrace/tracefs 实现机制
trace/ 维护 tracing ring buffer 和 tracefs 控制接口。典型路径:
1 | echo function > current_tracer |
tracepoint 则是静态埋点:
1 | TRACE_EVENT(sched_switch, ...) |
用途
perf top/perf record;trace-cmd/ftrace;- 调度延迟定位;
- 中断风暴定位;
- BPF 程序挂载 tracepoint/kprobe;
- Android/RK 平台性能和功耗调优。
5.4 BPF 运行时:bpf/
要解决的问题
BPF 解决的是“在不改内核、不加载传统模块的情况下,把受限制、可验证的小程序挂到内核事件点”。它兼顾可扩展性和安全性。
kernel/bpf/ 提供 eBPF 在内核中的基础设施:
- BPF map 生命周期;
- BPF program 加载、验证、运行;
- helper 调用;
- BTF、local storage、token 等扩展能力;
- 与 tracepoint、kprobe、cgroup、network、perf event 联动。
BPF 不是单独的调试工具,而是内核可编程扩展框架。
它让用户态可以在受验证的前提下,把小段逻辑挂到内核事件点上。
实现机制
典型加载路径:
1 | bpf(BPF_PROG_LOAD) |
verifier 是核心:它证明程序不会越界访问、不会无限循环、不会随意调用内核函数、不会泄露内核指针。
BPF map 是用户态和 BPF 程序之间、不同 BPF 程序之间共享状态的主要方式。
用途
- 网络过滤与转发;
- tracing 和性能分析;
- 安全策略;
- cgroup hook;
- 线上故障诊断。
5.5 DMA mapping 框架:dma/
要解决的问题
设备不能直接使用 CPU 虚拟地址。不同平台还可能有 IOMMU、cache 一致性限制、设备 DMA 地址宽度限制、CMA 连续内存限制。DMA mapping 框架把这些差异封装成统一 API。
kernel/dma/ 实现通用 DMA API 的架构无关部分:
- streaming DMA mapping;
- coherent DMA allocation;
- CMA contiguous allocation;
- SWIOTLB bounce buffer;
- IOMMU DMA ops;
- DMA debug;
- DMA pool。
驱动调用的 dma_map_single()、dma_alloc_coherent()、dma_map_sg() 等接口,最终会落到这里与架构层/设备 IOMMU 配合。
对 RK3588 这类 ARM64 SoC,DMA mask、IOMMU、CMA、cache 一致性都与该框架相关。
实现机制
coherent DMA:
1 | dma_alloc_coherent(dev, size, &dma_handle, gfp) |
streaming DMA:
1 | dma_map_single(dev, cpu_buf, len, dir) |
解决的问题
- 统一 PCIe、USB、网卡、存储、显示等驱动 DMA 编程;
- 避免驱动手写
virt_to_phys(); - 支持 IOMMU 隔离;
- 支持 32-bit 设备访问高内存时的 bounce;
- 统一 debug DMA API 检查 map/unmap 泄漏。
5.6 电源管理与 freezer:power/ / freezer.c / cpu_pm.c
要解决的问题
移动和嵌入式系统需要低功耗。系统 suspend/hibernate 时,必须协调用户进程、内核线程、驱动、文件系统、CPU、设备电源状态,避免正在运行的任务破坏休眠镜像或硬件状态。
kernel/power/ 负责 suspend/hibernate 相关流程:
- 系统 suspend;
- hibernation 镜像保存/恢复;
- wakelock;
- 用户态 suspend 接口;
- 测试与调试。
freezer.c 用于冻结可冻结任务,避免 suspend/hibernate 时用户态或内核线程继续修改状态。cpu_pm.c 提供 CPU 低功耗状态切换的 notifier 框架,供架构和驱动在 CPU suspend/resume 前后处理硬件状态。
suspend 大致流程
1 | 用户写 /sys/power/state |
freezer 的作用是把可冻结任务停在安全点。内核线程如果支持冻结,需要周期性检查 freezer 请求。
用途
- Android suspend/resume;
- 车机/工业设备低功耗;
- hibernation;
- 驱动 runtime PM 和系统 PM 协同调试。
5.7 CPU、SMP 与 hotplug:cpu.c / smp.c / smpboot.c
要解决的问题
多核系统需要 CPU 上下线、跨 CPU 函数调用、per-CPU 线程、CPU 拓扑管理。RK3588 这类 big.LITTLE 平台还涉及大小核调度和功耗控制。
这部分管理 CPU 生命周期和跨 CPU 协作:
- CPU online/offline;
- CPU hotplug state machine;
- SMP call function;
- stop_machine;
- per-CPU smpboot thread;
- CPU 亲和性和调度拓扑联动。
在 RK3588 这类 big.LITTLE ARM64 平台上,CPU hotplug、调度拓扑、cpufreq、EAS、PM 都会互相影响。
实现机制
CPU hotplug 不是简单设置一个标志,而是状态机:
1 | cpu_up() |
SMP call 用于让一个 CPU 请求另一个 CPU 执行函数:
1 | smp_call_function_single() |
stop_machine() 则用于极端场景:让大部分 CPU 停在受控状态,只执行指定回调,常用于 text patching、CPU hotplug、模块/内核修改敏感路径。
5.8 kexec、crash 与 reboot:kexec*.c / crash*.c / reboot.c
要解决的问题
系统重启、崩溃转储和无固件重启都需要内核级支持。普通 reboot 依赖平台固件或电源控制;kexec 则允许当前内核直接跳到另一个内核。
相关能力:
- 正常重启、关机、poweroff;
- kexec 直接跳转到新内核;
- crash kernel / kdump 捕获崩溃现场;
- panic 后保存 vmcore 所需信息。
这些功能主要用于生产环境故障恢复和内核崩溃分析。
实现机制
kexec:
1 | kexec_load/kexec_file_load |
kdump:
1 | 预留 crashkernel 内存 |
这解决了生产环境“内核崩溃后如何保留现场”的问题。
5.9 动态补丁与热路径优化:livepatch/ / jump_label.c / static_call*.c
要解决的问题
内核既需要大量可选功能和动态开关,又不能让热路径充满昂贵分支和间接调用;同时生产系统有时需要不停机修复内核函数。
livepatch/:运行时替换内核函数实现,用于安全修复和在线补丁;jump_label.c:静态分支,运行时把条件分支优化成近似 nop 或 jump;static_call*.c:静态调用,把函数指针式调用优化成可动态修改的直接调用。
这些机制让内核在保持可配置、可观测的同时,尽量降低热路径分支和间接调用开销。
实现机制
static key / jump label:
1 | 代码中 static_branch_unlikely() |
static call:
1 | 定义 static_call() |
livepatch:
1 | 加载 livepatch 模块 |
5.10 watchdog、hung task 与调试检测
要解决的问题
内核故障不一定立即 panic。常见问题是 CPU 长时间不响应、任务卡在 D 状态、锁竞争死锁、数据竞争、内存越界。调试检测模块用于尽早暴露问题。
代表文件:
watchdog.c/watchdog_hld.c:soft/hard lockup 检测;hung_task.c:长时间 D 状态任务检测;kcov.o:内核覆盖率采集;kcsan/:并发数据竞争检测;gcov/:代码覆盖率;debug/:KGDB/KDB 等调试设施;stacktrace.c/stackleak.c:栈回溯与栈清理。
这些不是业务功能,但对内核稳定性、测试覆盖和问题定位非常关键。
实现机制与用途
- soft lockup:watchdog 线程或 timer 检测 CPU 是否长时间未调度;
- hard lockup:通常依赖 NMI/perf event 检测 CPU 是否长时间不响应中断;
- hung task:周期扫描 D 状态任务,超过阈值打印堆栈;
- KCSAN:采样式数据竞争检测;
- KCOV:为 fuzzing 提供覆盖率反馈;
- stacktrace:为 lockdep、WARN、panic、perf 等提供调用栈。
这些能力解决“系统还活着但已经不正常”的诊断问题。
6. 典型跨模块主链路(理解“如何协同”)
6.1 进程创建链路
clone/fork
-> kernel_clone
-> copy_process
-> wake_up_new_task
-> sched 运行
关联:cred、cgroup、namespace、audit、perf、rcu
6.2 进程退出链路
exit/exit_group
-> do_exit
-> exit_mm/files/fs/task_work/...
-> cgroup_exit / perf_event_exit_task
-> exit_notify
-> do_task_dead -> schedule
6.3 中断到延后执行链路
硬中断
-> request_threaded_irq 注册的 handler
-> 快速路径仅做最小处理
-> queue_work / softirq / tasklet(视驱动)
-> worker 进程上下文执行重活
6.4 模块装载链路
finit_module
-> load_module(签名/ELF/重定位/参数/sysfs)
-> do_init_module
-> 模块功能上线
7. 调试与阅读建议(kernel/ 目录实战)
先看入口再看实现
- syscall 入口:
SYSCALL_DEFINE* - 调度入口:
schedule()/__schedule() - 模块入口:
init_module/finit_module/delete_module
- syscall 入口:
配合配置文件理解行为差异
Kconfig.preempt决定调度抢占语义Kconfig.hz影响 tick 频率与时延/功耗平衡Kconfig.locks影响锁实现路径
跨文件看生命周期
- 进程:
fork.c+exit.c+sched/core.c - 中断:
irq/manage.c+irq/irqdomain.c - 定时:
time/timekeeping.c+time/tick-sched.c
- 进程:
问题定位优先级
- 卡顿/时延:先看
sched/+time/+locking/ - 中断异常:先看
irq/+ 驱动 irq handler - 模块加载失败:看
module/main.c错误分支与签名/版本检查
- 卡顿/时延:先看
8. 总结
kernel/ 目录是 Linux 的“公共内核引擎层”,它不面向单一设备或协议,而是为全系统提供:
- 任务生命周期(fork/exit/sched)
- 并发原语(locking/rcu/workqueue)
- 中断与时间基座(irq/time)
- 运行期扩展与管控(module/sysctl/cgroup/seccomp/audit)
- 可观测性、热更新与安全治理(trace/perf/BPF/livepatch/watchdog)
理解 kernel/,本质上是在理解“Linux 如何作为一个可并发、可扩展、可治理的操作系统内核运行”。
正在加载留言…