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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 用户态 clone/fork/vfork -> SYSCALL_DEFINE*() -> kernel_clone() -> copy_process() -> dup_task_struct() -> copy_creds() -> copy_namespaces() -> copy_files() -> copy_fs() -> copy_sighand() -> copy_signal() -> copy_mm() -> copy_thread() -> cgroup_can_fork() / cgroup_post_fork() -> sched_fork() -> wake_up_new_task() -> 新任务进入调度队列
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 用户态 exit/exit_group 或 fatal signal -> do_exit(code) -> exit_signals() -> io_uring_files_cancel() -> acct_update_integrals() -> perf_event_exit_task() -> exit_mm() -> exit_sem() -> exit_files() -> exit_fs() -> exit_task_namespaces() -> cgroup_exit() -> exit_notify() -> do_task_dead() -> schedule()
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 2 3 4 5 6 7 8 9 schedule() -> __schedule() -> rq_lock() -> pick_next_task() -> stop -> deadline -> rt -> fair -> idle -> context_switch() -> switch_mm_irqs_off() -> switch_to() -> rq_unlock()
唤醒主链路:
1 2 3 4 5 try_to_wake_up() -> select_task_rq() -> ttwu_queue() -> activate_task() -> check_preempt_curr()
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 2 3 4 5 mutex_lock() -> 快速路径尝试占有 owner -> 失败则加入 wait_list -> schedule() 睡眠 -> unlock 时唤醒等待者
spinlock 的基本流程:
1 2 3 4 spin_lock() -> 禁止抢占,必要时关中断 -> 原子操作竞争 lock word -> 持锁者释放后继续
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 2 3 4 5 6 7 8 9 10 读者: rcu_read_lock() p = rcu_dereference(ptr) 使用 p rcu_read_unlock() 写者: old = ptr rcu_assign_pointer(ptr, new) call_rcu(old, free_callback)
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 2 3 4 5 6 request_threaded_irq() -> __setup_irq() -> 分配 irqaction -> 挂到 irq_desc -> 如有 thread_fn 则创建 IRQ thread -> 启用中断线
硬件触发后:
1 2 3 4 5 6 arch/GIC 入口 -> generic_handle_domain_irq() -> irq_desc handler -> handle_irq_event() -> 驱动 primary handler -> 如返回 IRQ_WAKE_THREAD,唤醒 threaded handler
softirq 路径:
1 2 3 4 5 raise_softirq() -> 标记当前 CPU pending -> irq_exit 或 ksoftirqd -> __do_softirq() -> 调用 softirq_vec[nr].action
用途与解决的问题
设备驱动中断处理;
网络收包 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 2 3 4 clocksource 提供 cycle -> timekeeping 计算 ns -> update_wall_time() -> 更新 xtime / monotonic / raw time
高精度定时:
1 2 3 4 5 hrtimer_start() -> 插入 per-CPU rb-tree -> 设置 clockevent 下一次到期 -> 中断到期 -> 执行回调或唤醒任务
调度 tick:
1 2 3 4 tick_sched_timer() -> 更新 jiffies/timekeeping -> scheduler_tick() -> 触发时间片、负载统计、RCU 检查
用途
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 2 3 4 5 printk() -> vprintk_emit() -> 写入 printk ringbuffer -> 根据 loglevel 和 console 状态输出 -> 唤醒 printk kthread 或直接 console_unlock()
panic 路径:
1 2 3 4 5 6 panic() -> 禁止/限制其他 CPU 干扰 -> 打印 panic 信息和栈 -> 调用 panic notifier -> kmsg_dump() -> 等待或重启
用途
驱动调试;
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 2 3 4 5 6 7 8 9 INIT_WORK() -> queue_work() -> __queue_work() -> 选择 pool_workqueue -> work 加入 pool 链表 -> wake_up_worker() -> worker_thread() -> process_one_work() -> work->func()
它解决的问题是:驱动和子系统可以把“稍后执行、可睡眠、可能耗时”的任务交给统一 worker,而不是每个模块都创建自己的线程。
内核线程如何实现 kthread.c 提供:
kthread_create();
kthread_run();
kthread_should_stop();
kthread_stop();
park/unpark;
CPU 绑定。
典型模式:
1 2 3 4 5 6 7 8 9 10 kthread_run(threadfn, data, name) -> 创建 task -> wake_up_process() threadfn: while (!kthread_should_stop()) do_work() kthread_stop() -> 设置 stop 标志 -> 唤醒线程 -> 等待退出
async.c 如何提高启动速度 async.c 的目标是减少启动时间。它把一些独立硬件延迟和发现操作异步执行,同时用 cookie 保证外部可见顺序:
1 2 3 4 5 6 7 async_schedule() -> 分配 async_entry 和 cookie -> queue_work() -> async_run_entry_fn() -> func(data, cookie) -> 从 pending list 删除 -> wake_up 等待者
如果某个阶段必须等待之前异步任务完成,则调用 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 2 3 4 5 6 7 8 9 10 11 12 13 14 finit_module/init_module syscall -> load_module() -> copy module from user -> module_sig_check() -> ELF header/section 检查 -> layout_and_allocate() -> simplify_symbols() -> apply_relocations() -> post_relocation() -> complete_formation() -> mod_sysfs_setup() -> do_init_module() -> 执行 module init -> 成功后释放 init section
卸载路径:
1 2 3 4 5 6 7 delete_module syscall -> 查找 module -> 检查引用计数 -> 调用 module exit -> 从模块列表移除 -> RCU 同步 -> 释放 module memory
kmod.c 的用途 kmod.c 处理内核请求用户态加载模块的场景,例如设备热插拔后触发:
1 2 3 request_module("xxx") -> call_usermodehelper() -> /sbin/modprobe 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 2 3 4 5 6 7 register_sysctl() -> 注册 ctl_table 用户读写 /proc/sys/xxx -> proc_sys_call_handler() -> proc_dointvec/proc_dostring/proc_doulongvec_minmax... -> 校验范围 -> 修改内核变量
常见 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 2 3 4 copy_process() -> cgroup_can_fork() -> cgroup_css_set_fork() -> cgroup_post_fork()
任务迁移时:
1 2 3 4 5 写 cgroup.procs -> 找到目标 cgroup -> 校验权限/controller -> 更新 task css_set -> 触发 controller migrate 回调
用途
容器资源隔离;
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 2 3 4 5 prepare_creds() -> 复制当前 cred -> 修改 uid/gid/capability -> security hooks 检查 -> commit_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 2 3 4 容器/沙箱 -> 加载 seccomp BPF -> 每次 syscall 进入时检查 -> 不允许的 syscall 被拒绝或杀死进程
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 2 3 4 5 6 7 用户态 svc/syscall 指令 -> arch syscall entry -> syscall table 查表 -> SYSCALL_DEFINE*() 实现 -> copy_from_user/copy_to_user 访问用户态参数 -> 调用内核通用子系统 -> 返回 errno 或结果
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 2 3 4 clone/unshare/setns -> copy_namespaces() -> 根据 CLONE_NEW* 决定复用还是创建 namespace -> nsproxy 指向新的 namespace 集合
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 2 3 4 5 6 kill/tgkill/内核异常 -> 找到目标 task 或线程组 -> 检查权限 -> 构造 siginfo -> 挂入 pending 队列 -> signal_wake_up()
信号递送不是立即强行打断任意内核代码,而是在任务准备返回用户态、或从可中断睡眠中醒来时处理:
1 2 3 4 5 6 返回用户态前 -> arch_do_signal_or_restart() -> get_signal() -> 选择未屏蔽信号 -> fatal: do_group_exit() -> handler: 构造用户态 signal frame
这样解决了“异步事件”和“内核临界区安全”之间的矛盾。
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 2 3 4 5 用户态原子操作失败 -> futex_wait() -> 任务睡眠到 hash bucket 队列 -> futex_wake() -> 唤醒等待者
它支撑 pthread mutex、condition variable、Java/Go/Rust 运行时锁等大量用户态同步机制。 因此 futex 是 Linux 用户态并发性能的关键基础。
实现机制 futex 以用户态地址为 key,把等待者挂到内核 hash bucket:
1 2 3 4 5 6 7 8 9 10 11 futex_wait(uaddr, expected) -> 从用户地址读取当前值 -> 如果值不等于 expected,直接返回 -> 根据 uaddr 构造 futex_key -> 入队到 hash bucket -> schedule() 睡眠 futex_wake(uaddr, nr) -> 根据 uaddr 找到同一 hash bucket -> 取出最多 nr 个等待者 -> wake_up_q()
这解决了经典 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 2 3 4 5 6 7 perf_event_open() -> perf_event_alloc() -> 选择 PMU -> event 初始化 -> 安装到 task/cpu perf_event_context -> mmap ring buffer -> enable 后开始计数或采样
采样数据通过 perf ring buffer 传给用户态。对于硬件 PMU,底层由架构 PMU 驱动配置计数器;对于 tracepoint/software event,则由内核事件点主动输出样本。
ftrace/tracefs 实现机制 trace/ 维护 tracing ring buffer 和 tracefs 控制接口。典型路径:
1 2 3 4 5 6 7 echo function > current_tracer -> 启用 function tracer 函数入口 -> ftrace trampoline -> 写入 ring buffer cat trace_pipe -> 用户态读取实时事件
tracepoint 则是静态埋点:
1 2 3 4 TRACE_EVENT(sched_switch, ...) -> 编译生成 tracepoint -> 运行时注册 probe -> 事件发生时调用 probe
用途
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 2 3 4 5 6 7 8 9 10 11 12 bpf(BPF_PROG_LOAD) -> 复制用户态 insn -> verifier 检查控制流、类型、安全边界 -> JIT 或解释执行准备 -> 返回 prog fd bpf(BPF_MAP_CREATE) -> 创建 map -> 返回 map fd attach -> prog 挂到 tracepoint/kprobe/cgroup/socket/perf event
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 2 3 4 5 dma_alloc_coherent(dev, size, &dma_handle, gfp) -> 根据 dev 的 dma ops 选择 direct/IOMMU/CMA 路径 -> 分配 CPU 可访问内存 -> 建立设备可访问 DMA 地址 -> 返回 cpu_addr + dma_addr_t
streaming DMA:
1 2 3 4 5 6 7 dma_map_single(dev, cpu_buf, len, dir) -> cache sync -> IOMMU 映射或 direct 物理地址转换 -> 返回 dma_addr_t 设备 DMA 完成 -> dma_unmap_single() -> cache sync / 释放 IOVA
解决的问题
统一 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 2 3 4 5 6 7 用户写 /sys/power/state -> pm_suspend() -> freeze_processes() -> device_suspend() -> platform suspend / CPU suspend -> resume 后 device_resume() -> thaw_processes()
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 2 3 4 5 6 7 8 9 10 cpu_up() -> cpuhp state machine -> arch bringup CPU -> 初始化 per-CPU 资源 -> 通知调度、RCU、timer、irq 子系统 cpu_down() -> 迁移任务和中断 -> 停止 per-CPU timer/work -> arch shutdown CPU
SMP call 用于让一个 CPU 请求另一个 CPU 执行函数:
1 2 3 4 smp_call_function_single() -> 放入 call_single_data -> 发送 IPI -> 目标 CPU 中断中执行回调
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 2 3 4 5 6 7 8 kexec_load/kexec_file_load -> 校验新内核镜像 -> 分配/准备段 -> 保存跳转信息 reboot 或 panic -> machine_kexec() -> 关闭中断/设备 -> 跳转到新内核入口
kdump:
1 2 3 4 5 预留 crashkernel 内存 -> panic -> 跳转 crash kernel -> crash kernel 读取旧内核内存 -> 生成 vmcore
这解决了生产环境“内核崩溃后如何保留现场”的问题。
5.9 动态补丁与热路径优化:livepatch/ / jump_label.c / static_call*.c 要解决的问题 内核既需要大量可选功能和动态开关,又不能让热路径充满昂贵分支和间接调用;同时生产系统有时需要不停机修复内核函数。
livepatch/:运行时替换内核函数实现,用于安全修复和在线补丁;
jump_label.c:静态分支,运行时把条件分支优化成近似 nop 或 jump;
static_call*.c:静态调用,把函数指针式调用优化成可动态修改的直接调用。
这些机制让内核在保持可配置、可观测的同时,尽量降低热路径分支和间接调用开销。
实现机制 static key / jump label:
1 2 3 4 5 代码中 static_branch_unlikely() -> 默认编译为 nop 或 jump 运行时开关改变 -> text patching -> 修改指令
static call:
1 2 3 4 定义 static_call() -> 调用点可被 patch 成直接 call 运行时更新目标 -> 修改调用目标
livepatch:
1 2 3 4 加载 livepatch 模块 -> 注册替换函数 -> 检查任务一致性 -> ftrace/text patch 重定向旧函数到新函数
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
配合配置文件理解行为差异
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 如何作为一个可并发、可扩展、可治理的操作系统内核运行”。