rk3588/kernel-6.1 kernel 内核模块架构详解

rk3588/kernel-6.1 kernel 内核模块架构详解

本文聚焦 rk3588/kernel-6.1/kernel 目录。该目录不是单一功能模块,而是 Linux 内核“通用核心层”聚合区:进程/调度/同步/中断/时间/模块装载/系统控制等都在这里落地实现。


1. kernel/ 在内核全局中的位置

可以把内核粗略分成:

  • init/:早期启动与 initcall 驱动初始化
  • mm/:内存管理
  • fs/:VFS 与文件系统
  • net/:网络协议栈
  • drivers/:设备驱动
  • kernel/跨子系统通用核心机制

kernel/Makefile 显示其既包含单文件核心(如 fork.oexit.osysctl.oworkqueue.o),也包含关键子目录(sched/locking/irq/rcu/time/module/printk/cgroup/ 等)。

从职责上看,kernel/ 更像“操作系统运行时公共层”。drivers/ 负责具体硬件,fs/ 负责文件系统抽象,net/ 负责协议栈,mm/ 负责内存;而 kernel/ 负责把这些模块串成一个可运行的系统:

  • 进程由 fork.c 创建后交给 sched/ 调度;
  • 驱动通过 irq/ 接收硬件中断,再通过 softirq.cworkqueue.c 延后处理;
  • 文件系统、网络、驱动都依赖 locking/rcu/ 做并发保护;
  • 模块驱动由 module/ 加载,参数由 params.c 解析,运行时策略由 sysctl.c 调整;
  • 故障时由 printk/panic.cwatchdog.ctrace/ 提供诊断能力。

所以阅读 kernel/ 不能只按目录理解,还要按“生命周期”理解:任务创建、调度运行、中断唤醒、同步保护、定时超时、退出回收、日志诊断,这些路径会跨越多个文件。


2. 构建与配置组织方式(Kbuild/Kconfig)

2.1 Kbuild 组织(kernel/Makefile

obj-yobj-$(CONFIG_*) 决定了模块装配:

  • 始终编译的核心fork.oexit.osignal.osys.opid.okthread.oworkqueue.ocred.oreboot.o
  • 按配置启用
    CONFIG_MODULES -> module/ + kmod.o
    CONFIG_AUDIT -> audit*.o
    CONFIG_KPROBES -> kprobes.o
    CONFIG_SECCOMP -> seccomp.o
    CONFIG_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.cexit.cpid.c 创建进程/线程、分配 PID、退出回收、父子关系维护
调度 sched/ CFS/RT/DL/idle 调度类、上下文切换、负载均衡、CPU 亲和性
信号与 ptrace signal.cptrace.c 信号发送/递送/处理、调试器跟踪进程
凭证与权限 cred.ccapability.cgroups.c UID/GID、capability、进程凭证复制与提交
系统调用公共实现 sys.csys_ni.cexec_domain.c 通用 syscall 支撑、未实现 syscall 占位、执行域兼容
内核线程与异步执行 kthread.cworkqueue.casync.ctask_work.c 内核线程、工作队列、异步任务、返回用户态前回调
中断与下半部 irq/softirq.cirq_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.cparams.c .ko 加载/卸载、ELF 重定位、符号解析、模块参数
日志与 panic printk/panic.creboot.c 内核日志、console 输出、panic、重启/关机
系统配置接口 sysctl.cutsname_sysctl.cksysfs.c /proc/sys、sysfs 内核信息、运行时参数调优
namespace nsproxy.cpid_namespace.cuser_namespace.cutsname.c PID/user/UTS namespace 及进程 namespace 关联
cgroup/资源控制 cgroup/ucount.cuser.c cgroup 层级、任务归属、用户资源计数与限制
安全与审计 seccomp.caudit*.c syscall 过滤、审计规则、审计事件生成
futex futex/ 用户态锁的内核等待/唤醒基础
tracing/观测 trace/tracepoint.cevents/ ftrace、tracepoint、kprobe/uprobe、perf events、ring buffer
BPF bpf/ eBPF 程序、map、helper、验证器与运行时支撑
DMA 映射 dma/ DMA mapping/coherent/CMA/SWIOTLB/IOMMU 抽象
电源管理 power/freezer.ccpu_pm.c suspend/hibernate、wakelock、freezer、CPU PM 通知
CPU/SMP/hotplug cpu.csmp.csmpboot.cstop_machine.c CPU 上下线、SMP call、stop_machine、启动辅助线程
崩溃与热重启 crash_core.ckexec*.ccrash_dump.c kexec、kdump、崩溃内核准备
动态补丁/优化 livepatch/jump_label.cstatic_call*.c livepatch、静态分支、static call 热路径优化
调试检测 watchdog.chung_task.cdebug/kcov.okcsan/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.cSCHED_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.cqspinlock.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.crcuscale.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.cirq_desc 分配和管理;
  • manage.crequest_threaded_irq()free_irq()__setup_irq()
  • handle.chandle_irq_event() 等事件分发;
  • chip.cirq_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.cprintk()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 跟踪;
  • seccompsignalwait 紧密耦合。

这部分决定了 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 运行

关联:credcgroupnamespaceauditperfrcu

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/ 目录实战)

  1. 先看入口再看实现

    • syscall 入口:SYSCALL_DEFINE*
    • 调度入口:schedule()/__schedule()
    • 模块入口:init_module/finit_module/delete_module
  2. 配合配置文件理解行为差异

    • Kconfig.preempt 决定调度抢占语义
    • Kconfig.hz 影响 tick 频率与时延/功耗平衡
    • Kconfig.locks 影响锁实现路径
  3. 跨文件看生命周期

    • 进程:fork.c + exit.c + sched/core.c
    • 中断:irq/manage.c + irq/irqdomain.c
    • 定时:time/timekeeping.c + time/tick-sched.c
  4. 问题定位优先级

    • 卡顿/时延:先看 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 如何作为一个可并发、可扩展、可治理的操作系统内核运行”。

文章互动

阅读 --

留言

0 条留言

正在加载留言…