你每天都在使用 Linux——敲命令、写代码、部署服务。但你是否想过:当你输入 ls 按下回车的那一刻,从键盘驱动捕获按键,到 Shell 进程被唤醒,再到文件系统遍历目录项、最终将结果渲染到终端——这短短几十毫秒之间,内核究竟做了多少事?
本章是整个系列的”地图”,不深入任何一个子系统的细节(那是后续章节的任务),而是从宏观视角俯瞰 Linux 内核的整体架构:它是如何把用户空间和内核空间隔离开的?它由哪些子系统组成?内核代码在什么上下文中执行?它使用了哪些通用数据结构?以及——它如何通过 /proc 和 /sys 向用户空间”开口说话”?
理解了这幅全景图,后续每一章的学习就有了锚点。
一、用户空间与内核空间:一道看不见的墙
1.1 为什么需要隔离?
想象你住在一栋公寓楼里。你的房间是用户空间——你可以随意布置家具、看书、休息,但你不能去动大楼的供电系统、电梯控制、消防管道——那是内核空间,只有物业(内核)才有权操作。
这种隔离不是”建议”,而是 CPU 硬件强制的。x86 架构定义了多个特权级(Privilege Ring),Linux 只使用其中两个:
- Ring 0(内核态):完全的硬件访问权限,可以执行任何指令、访问任何内存地址
- Ring 3(用户态):受限的执行环境,不能直接访问硬件、不能访问内核内存
x86 架构实际上定义了 Ring 0 ~ Ring 3 四个特权级,但 Linux 从诞生之初就只使用 Ring 0 和 Ring 3 两个级别。中间的 Ring 1 和 Ring 2 在 Linux 中从未被使用——这是”机制而非策略”哲学的体现:硬件提供了四级的机制,Linux 选择了最简的策略。
1.2 地址空间划分
在 32 位 x86 系统上,Linux 采用经典的 3G/1G 划分:
- 虚拟地址
0x00000000~0xBFFFFFFF(低 3 GB):用户空间,每个进程独占 - 虚拟地址
0xC0000000~0xFFFFFFFF(高 1 GB):内核空间,所有进程共享
在 64 位 x86_64 系统上,地址空间大幅扩展,但划分逻辑不变:
- 用户空间:虚拟地址的低位部分(典型为 128 TB)
- 内核空间:虚拟地址的高位部分(典型为 128 TB)
- 两者之间有巨大的”空洞”(canonical hole),任何访问都会触发段错误
用户空间的代码绝对不能直接读写内核空间的内存。任何越界访问都会被 MMU(内存管理单元)拦截,CPU 产生一个页错误(Page Fault),内核随后向违规进程发送 SIGSEGV 信号——这就是你常见的”段错误(Segmentation Fault)“。
1.3 用户态与内核态的切换
用户程序需要访问硬件资源时(读写文件、创建进程、网络通信等),必须通过系统调用(System Call) 主动陷入内核态。切换过程如下:
- 用户程序执行
syscall指令(x86_64)或int 0x80(x86 32 位) - CPU 从 Ring 3 切换到 Ring 0,切换到内核栈
- 内核根据系统调用号查找
sys_call_table,执行对应的内核函数 - 执行完毕后,通过
sysret或iret返回用户态
这个切换是有代价的:涉及寄存器保存/恢复、TLB 刷新、CPU 流水线冲刷等。将在第 2 章:系统调用中详细分析这一过程的开销。
二、内核的七大子系统概览
Linux 内核是一个庞大的工程——6.x 版本的源码已超过 3000 万行。但万变不离其宗,它由七大核心子系统组成:
2.1 进程管理(Process Management)
进程是操作系统资源分配的基本单位。Linux 内核的进程管理子系统负责:
- 进程的创建与销毁:
fork()、exec()、exit()系统调用 - 进程调度:决定哪个进程在哪个 CPU 核心上运行、运行多久
- 进程状态管理:运行、就绪、阻塞、僵尸等状态的转换
- 进程间关系:父子关系、进程组、会话
核心数据结构是 task_struct(定义在 include/linux/sched.h),每个进程(和线程)都对应一个 task_struct 实例。它包含了内核管理进程所需的全部信息:PID、状态、优先级、打开的文件列表、内存描述符、信号处理等。
在 Linux 中,线程和进程使用相同的 task_struct 结构——线程本质上是共享地址空间的进程。这种统一设计被称为”轻量级进程(LWP)“模型,是 Linux 内核的一大特色。
2.2 内存管理(Memory Management)
内存管理子系统是内核中最复杂的部分之一,负责:
- 物理内存管理:通过伙伴系统(Buddy System)和 Slub 分配器管理物理页帧
- 虚拟内存管理:为每个进程维护独立的虚拟地址空间(
mm_struct+vm_area_struct) - 页缓存(Page Cache):缓存磁盘数据,减少 I/O 操作
- 内存回收:
kswapd内核线程在内存不足时回收页面 - OOM Killer:内存耗尽时选择”牺牲”进程
2.3 文件系统(Filesystem)
Linux 的文件系统子系统通过 VFS(Virtual File System,虚拟文件系统) 实现了”一切皆文件”的 Unix 哲学:
- VFS 抽象层:定义了
super_block、inode、dentry、file四大核心对象 - 具体文件系统:ext4、XFS、Btrfs 等磁盘文件系统;tmpfs、procfs、sysfs 等内存文件系统
- 页缓存与脏页写回:文件 I/O 的性能优化核心
2.4 网络协议栈(Network Stack)
Linux 内核的网络子系统实现了完整的 TCP/IP 协议栈:
- 套接字层:
socket()系统调用的内核端实现 - 协议处理:TCP、UDP、ICMP、IP 等协议的实现
- 数据包收发:
sk_buff结构贯穿整个协议栈 - Netfilter/iptables:网络包过滤与 NAT 框架
- NAPI:中断与轮询结合的高性能网络驱动接口
2.5 设备驱动(Device Drivers)
设备驱动是内核中代码量最大的部分(约占 60%+),负责与具体硬件交互:
- 字符设备:按字节流访问(串口、终端)
- 块设备:按块访问(硬盘、SSD)
- 网络设备:数据包收发(网卡)
- 设备驱动模型:
kobject、sysfs提供统一的设备管理框架
2.6 进程间通信(IPC)
Linux 支持多种 IPC 机制,满足不同场景的通信需求:
- 管道(Pipe):父子进程间的字节流通信
- 消息队列:System V 和 POSIX 消息队列
- 共享内存:最快的 IPC 方式,进程直接读写同一块内存
- 信号量:用于 IPC 的同步与互斥
- 信号(Signal):异步通知机制
- Unix 域套接字:本地进程间的全双工通信
2.7 安全子系统(Security)
安全子系统贯穿内核各层,提供多层次的访问控制:
- DAC(自主访问控制):传统的文件权限(rwx)和属主/属组
- Capabilities:将 root 权限细分为数十种独立能力
- LSM(Linux Security Module):安全模块框架,SELinux、AppArmor 均基于此
- Seccomp-BPF:限制进程可使用的系统调用集合
- Audit:安全审计框架,记录敏感操作
三、内核代码的执行上下文
理解内核代码在什么上下文中执行,是理解内核编程约束的关键。Linux 内核代码主要在两种上下文中运行:
3.1 进程上下文(Process Context)
当用户进程通过系统调用进入内核时,内核代码代表该进程执行——这就是进程上下文。此时:
- 有明确的
current指针,指向当前进程的task_struct - 可以睡眠(调用
schedule()让出 CPU、等待信号量等) - 可以访问用户空间地址(通过
copy_from_user()/copy_to_user()) - 可以被调度器抢占(CONFIG_PREEMPT)
进程上下文是内核中最”舒适”的执行环境——你拥有进程的完整上下文信息,可以使用大部分内核 API。
3.2 中断上下文(Interrupt Context)
当硬件中断触发时,CPU 立即打断当前执行流,跳转到中断处理函数——此时内核代码在中断上下文中执行。此时:
- 没有进程上下文:
current指针虽然存在,但不指向与中断相关的进程 - 绝对不能睡眠:因为没有一个”进程”可以被调度出去——如果睡眠,调度器无法恢复执行
- 不能访问用户空间:没有用户地址空间的映射
- 执行时间必须尽可能短
在中断上下文中调用任何可能睡眠的函数(如 kmalloc(GFP_KERNEL)、mutex_lock()、schedule())都是严重错误,会导致内核死锁或崩溃。这是内核编程中最常见的陷阱之一。
3.3 可睡眠 vs 不可睡眠——一张速查表
| 操作 | 进程上下文 | 中断上下文 |
|---|---|---|
kmalloc(GFP_KERNEL) | 可以 | 禁止 |
kmalloc(GFP_ATOMIC) | 可以 | 可以 |
mutex_lock() | 可以 | 禁止 |
spin_lock() | 可以 | 可以(但需谨慎) |
schedule() | 可以 | 禁止 |
copy_from_user() | 可以 | 禁止 |
printk() | 可以 | 可以 |
complete() | 可以 | 可以 |
3.4 从中断上下文回到进程上下文
Linux 内核将中断处理分为两半:
- Top Half(上半部):在中断上下文中执行,只做最紧急的硬件操作(如从网卡 DMA 缓冲区复制数据),然后立刻唤醒下半部
- Bottom Half(下半部):延迟处理,通过 Softirq、Tasklet 或 Workqueue 实现。其中 Workqueue 运行在内核线程中,回到了进程上下文,因此可以睡眠
这种”上半部/下半部”的分割设计,是内核在响应速度和处理完整性之间的精妙平衡。
四、Linux 内核的设计哲学
理解 Linux 内核的设计哲学,能帮助你理解很多”为什么是这样”的问题。
4.1 宏内核架构(Monolithic Kernel)
操作系统内核的架构之争由来已久。宏内核与微内核是两种根本不同的设计理念:
| 维度 | 宏内核(Linux) | 微内核(MINIX、seL4) |
|---|---|---|
| 设计 | 所有子系统运行在同一地址空间 | 只保留最核心功能,其余作为用户态服务 |
| 性能 | 函数直接调用,开销极小 | 服务间通过 IPC 通信,上下文切换开销大 |
| 可靠性 | 一个子系统崩溃,整个内核崩溃 | 服务崩溃可重启,系统更健壮 |
| 代码量 | 庞大(3000 万行+) | 精简(seL4 约 1 万行) |
Linux 选择了宏内核架构,理由很务实:性能。在宏内核中,进程管理调用内存管理函数只是一个函数调用;而在微内核中,这需要一次 IPC——涉及上下文切换、消息序列化/反序列化,开销可能高出一个数量级。
description: ”### 4.2 动态加载模块——宏内核的”逃生舱”
纯宏内核的一个痛点是:所有功能编译进内核,导致内核镜像臃肿、扩展性差。Linux 通过可加载内核模块(Loadable Kernel Module, LKM) 解决了这个问题:
// 一个最简内核模块示例#include <linux/module.h>#include <linux/kernel.h>
static int __init hello_init(void){ printk(KERN_INFO "Hello, kernel world!\n"); return 0;}
static void __exit hello_exit(void){ printk(KERN_INFO "Goodbye, kernel world!\n");}
module_init(hello_init);module_exit(hello_exit);
MODULE_LICENSE("GPL");MODULE_AUTHOR("Linux Explorer");MODULE_DESCRIPTION("A minimal kernel module");模块在运行时通过 insmod 加载、rmmod 卸载,运行在内核空间,拥有完整的内核权限——这使得 Linux 兼具宏内核的性能和微内核的灵活性。设备驱动、文件系统、网络协议都可以作为模块动态加载。
4.3 “机制而非策略”
这是 Unix/Linux 最重要的设计原则之一,Linus Torvalds 反复强调:
内核应该提供机制(Mechanism),而不是强制策略(Policy)。
具体含义:
- 进程调度:内核提供 CFS、实时调度等多种调度器(机制),但让用户通过
nice、chrt、cgroup决定如何使用(策略) - 文件系统:内核提供 VFS 框架(机制),但支持 ext4、XFS、Btrfs 等多种文件系统(策略由用户选择)
- I/O 调度:内核提供 mq-deadline、bfq、kyber 等调度器(机制),用户可根据存储设备类型选择(策略)
- 安全模块:内核提供 LSM 框架(机制),SELinux、AppArmor、Smack 是不同的安全策略实现
这种设计让 Linux 内核保持了极强的通用性——从嵌入式设备到超级计算机,同一套内核代码可以适应截然不同的使用场景。
五、内核数据结构巡礼
Linux 内核不使用标准 C 库(glibc),因此也不能使用 glibc 提供的数据结构。内核自己实现了一套精巧、高效的通用数据结构,它们是内核各子系统的”基础设施”。
5.1 list_head——侵入式双向链表
这是内核中使用最广泛的数据结构。与用户态链表不同,list_head 采用侵入式设计——链表节点嵌入到数据结构内部,而不是将数据结构包在链表节点里:
struct list_head { struct list_head *next, *prev;};
// 使用方式:将 list_head 嵌入到自定义结构中struct task_struct { int pid; char comm[16]; struct list_head tasks; // 嵌入链表节点! struct list_head children; // 可以嵌入多个链表节点 // ... 更多字段};侵入式设计的优势:
- 零额外分配:链表节点已经嵌入数据结构中,不需要额外分配内存
- 一个对象可以同时属于多个链表:只需嵌入多个
list_head - 缓存友好:遍历链表时,链表节点与数据在同一个缓存行中
内核提供了丰富的链表操作宏:
// 初始化INIT_LIST_HEAD(&task->tasks);
// 插入list_add(&new_task->tasks, &prev_task->tasks); // 头插list_add_tail(&new_task->tasks, &prev_task->tasks); // 尾插
// 遍历struct task_struct *pos;list_for_each_entry(pos, &task_list, tasks) { printk("PID: %d\n", pos->pid);}
// 通过链表节点获取包含它的结构体struct task_struct *t = list_entry(ptr, struct task_struct, tasks);list_entry 宏是侵入式链表的核心魔法——它通过 container_of 宏,从结构体成员的地址反推出结构体本身的地址。container_of 是 Linux 内核中最著名的宏之一,后续你会反复遇到它。
5.2 hlist——散列表的链表
hlist(哈希链表)是 list_head 的变体,专门用于散列表的桶链表。与 list_head 的双向循环链表不同,hlist 的设计目标是减少散列表的内存开销:
struct hlist_head { struct hlist_node *first; // 只有一个指针!};
struct hlist_node { struct hlist_node *next, **pprev; // pprev 是指向前一个节点 next 字段的指针};为什么 hlist_head 只有一个指针?因为散列表的桶数组可能非常大(如 PID 散列表可能有数万个桶),每个桶省一个指针就能节省大量内存。pprev 是指向指针的指针——这样在删除节点时不需要判断是否是链表头,统一了操作逻辑。
5.3 rbtree——红黑树
红黑树在内核中用于需要快速查找、插入、删除的场景,保证最坏情况下操作复杂度为 :
struct rb_root { struct rb_node *rb_node;};
struct rb_node { unsigned long __rb_parent_color; // 父节点指针 + 颜色位(巧妙复用) struct rb_node *rb_right; struct rb_node *rb_left;} __attribute__((aligned(sizeof(long))));内核中红黑树的典型应用:
| 子系统 | 用途 |
|---|---|
| 进程调度(CFS) | 以 vruntime 为键组织可运行进程 |
| 内存管理 | 管理 VMA(虚拟内存区域) |
| I/O 调度(CFQ) | 按请求扇区号排序 I/O 请求 |
| Timer | 管理定时器 |
| ext4 文件系统 | 目录项的快速查找 |
__rb_parent_color 字段是一个经典的内核优化——它将父节点指针和颜色位打包在一个 unsigned long 中。因为 rb_node 结构体是对齐到 sizeof(long) 的,其地址的最低两位永远是 0,所以可以用最低位来存储红黑树的颜色(0 = 红,1 = 黑)。这种”位窃取”技巧在内核中随处可见。
5.4 Radix Tree——基数树
基数树是一种空间优化的前缀树,在内核中主要用于从整型索引快速定位指针:
// include/linux/xarray.h(现代内核用 XArray 替代了 Radix Tree 的部分用途)// 传统 Radix Tree API 仍在 lib/radix-tree.c 中可用基数树在内核中的典型应用:
- 页缓存(Page Cache):从文件偏移量(页索引)快速查找对应的
page结构——这是页缓存最核心的查找结构 - IDR 机制:分配和管理唯一的整型 ID,并将 ID 映射到指针
- 网络子系统:路由表的快速查找
与红黑树相比,基数树在”以整数为键”的场景下更高效,因为不需要逐位比较,而是按固定步长(如 6 位)直接索引,缓存局部性更好。
六、/proc 和 /sys 文件系统:内核向用户空间暴露信息的窗口
内核是一个”黑盒”——它的数据结构都在内核空间,用户程序无法直接访问。但系统管理员和开发者需要观察内核的运行状态。Linux 的解决方案是:用文件系统作为接口。
6.1 /proc——进程与内核信息
/proc 是一个伪文件系统(procfs),它不占用磁盘空间,文件内容由内核动态生成。主要提供两类信息:
进程相关信息(每个进程一个目录):
# 查看当前 Shell 进程的信息ls /proc/$$/# 输出:# cmdline comm cwd environ exe fd# maps mem mountinfo ns oom_score root# sched smaps stat statm status ...
# 查看进程的命令行参数cat /proc/$$/cmdline | tr '\0' ' '# 输出:/bin/zsh
# 查看进程的内存映射head -5 /proc/$$/maps# 输出:# 55a1c2000000-55a1c2027000 r--p 00000000 08:01 262210 /usr/bin/zsh# 55a1c2027000-55a1c20de000 r-xp 00027000 08:01 262210 /usr/bin/zsh# ...
# 查看进程的状态cat /proc/$$/status | head -10# 输出:# Name: zsh# State: S (sleeping)# Tgid: 12345# Pid: 12345# PPid: 1# ...系统全局信息:
# 内核版本cat /proc/version# 输出:Linux version 6.12.7-generic ...
# CPU 信息cat /proc/cpuinfo | head -15
# 内存使用情况cat /proc/meminfo | head -5# 输出:# MemTotal: 16384000 kB# MemFree: 8234567 kB# MemAvailable: 12000000 kB# Buffers: 234567 kB# Cached: 3456789 kB
# 中断统计head -5 /proc/interrupts
# 文件系统挂载信息cat /proc/mounts | head -5
# 内核命令行参数cat /proc/cmdline6.2 /sys——设备与驱动模型
/sys 是 sysfs 文件系统的挂载点,它将内核的设备驱动模型以目录树的形式暴露给用户空间:
# 查看 sysfs 的顶层结构ls /sys/# 输出:# block bus class dev devices firmware fs kernel module power
# 查看块设备信息ls /sys/block/# 输出:sda sr0 ...
# 查看某个块设备的详细信息ls /sys/block/sda/# 输出:# alignment_offset capability device ext_range hidden inflight# power queue range removable ro size slaves stat subsystem uevent
# 查看已加载的内核模块ls /sys/module/ | head -10
# 查看某个模块的参数ls /sys/module/nvme/parameters/
# 查看内核热插拔事件udevadm monitor --environment6.3 /proc 与 /sys 的分工
| 维度 | /proc(procfs) | /sys(sysfs) |
|---|---|---|
| 定位 | 进程信息 + 内核统计 | 设备/驱动/总线模型 |
| 结构 | 相对扁平,历史遗留较多 | 严格的层次结构 |
| 规则 | 每个 PID 一个目录 + 杂项文件 | 按 bus/class/device 组织 |
| 写入 | 部分文件可写(如 /proc/sys/) | 大部分属性可读写 |
| 趋势 | 新信息优先放 /sys | 内核开发者的推荐选择 |
/proc/sys/ 目录下的文件对应 sysctl 参数——你可以通过 sysctl 命令或直接写入这些文件来在运行时修改内核参数。例如 echo 1 > /proc/sys/vm/drop_caches 可以清空页缓存,sysctl -a 可以查看所有可调参数。
七、内核启动流程概览
从按下电源键到出现登录提示符,Linux 内核经历了一个精密的启动序列。这里给出概览,详细分析将在第 19 章:Linux 启动流程中展开。
关键步骤简述:
- BIOS/UEFI 阶段:硬件自检(POST),找到启动设备,加载引导程序
- Bootloader 阶段:GRUB 加载内核镜像(
vmlinuz)和 initramfs 到内存,传递内核命令行参数 - 内核初始化:
start_kernel()(定义在init/main.c)是内核的 C 语言入口,依次完成:- 架构相关初始化(
setup_arch()) - 内存管理初始化(
mm_init()) - 调度器初始化(
sched_init()) - 中断和时钟初始化
- 设备驱动初始化
- 架构相关初始化(
rest_init():创建内核线程kernel_init(最终执行/sbin/init,成为 PID 1)和kthreadd(内核线程守护者)- PID 1:通常是 systemd,负责挂载最终根文件系统、启动用户空间服务
八、内核线程(kthread):内核自己的”进程”
内核不只是被动地响应系统调用和中断——它也有自己的”后台任务”需要持续运行。这些任务以内核线程(Kernel Thread) 的形式存在。
8.1 什么是内核线程?
内核线程是只在内核空间运行的线程——它没有用户空间地址空间(mm_struct 为 NULL 或共享 init_mm),永远在 Ring 0 执行。从调度器的角度看,内核线程和用户进程没有本质区别——它们都是 task_struct,参与同样的调度。
8.2 常见的内核线程
# 查看系统中的内核线程(方括号标记的进程)ps -eo pid,comm | grep '\[' | head -15# 输出:# 2 [kthreadd]# 3 [rcu_gp]# 4 [rcu_par_gp]# 5 [slub_flushwq]# 6 [netns]# 8 [kworker/0:0-events]# 10 [kworker/0:1-events]# 11 [ksoftirqd/0]# 12 [rcuc/0]# 13 [migration/0]# 14 [idle_inject/0]# 15 [cpuhp/0]# 17 [kdevtmpfs]# 18 [inet_frag_wq]几个重要的内核线程:
| 内核线程 | 职责 |
|---|---|
kthreadd | 内核线程守护者,所有内核线程的父进程(PID 2) |
ksoftirqd/N | 每个 CPU 一个,处理延迟的软中断 |
kworker/N | 工作队列的worker线程,执行延迟工作 |
kswapd | 内存回收守护线程,在内存不足时回收页面 |
jbd2/sdX-Y | ext4 文件系统的日志线程 |
migration/N | 负责在 CPU 间迁移进程(负载均衡) |
8.3 创建内核线程
内核通过 kthread_create() 和 kthread_run() 创建内核线程:
// 创建但不启动struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ...);
// 创建并启动(最常用)struct task_struct *kthread_run(int (*threadfn)(void *data), void *data, const char namefmt[], ...);
// 示例:创建一个内核线程struct task_struct *my_thread;my_thread = kthread_run(my_thread_fn, NULL, "my_kernel_thread");if (IS_ERR(my_thread)) { pr_err("Failed to create kernel thread\n"); return PTR_ERR(my_thread);}
// 线程函数的实现static int my_thread_fn(void *data){ while (!kthread_should_stop()) { // 执行内核工作... set_current_state(TASK_INTERRUPTIBLE); schedule(); // 主动让出 CPU,可睡眠 } return 0;}
// 停止内核线程kthread_stop(my_thread);内核线程虽然运行在内核空间,但它可以睡眠——因为它有完整的 task_struct,调度器可以正常地切换它。这与中断上下文形成鲜明对比。
九、动手实践
本章的实践操作不需要任何编程——只需一个 Linux 终端。通过这些操作,你将亲手”触摸”内核暴露给用户空间的接口。
实践 1:观察用户/内核空间划分
# 查看内核版本和编译信息cat /proc/version
# 查看进程的虚拟内存映射,观察用户空间和内核空间的分界cat /proc/self/maps | tail -5# 高地址区域就是内核空间的映射
# 查看系统的物理内存和虚拟内存配置cat /proc/meminfo | grep -E "MemTotal|MemFree|VmallocTotal|VmallocUsed"实践 2:探索 /proc 文件系统
# 查看当前 Shell 进程的详细信息ls -la /proc/$$/
# 查看进程状态cat /proc/$$/status
# 查看进程打开的文件描述符ls -la /proc/$$/fd/
# 查看进程的内存映射(理解 VMA)cat /proc/$$/maps | head -10
# 查看进程的命令行参数和环境变量cat /proc/$$/cmdline | tr '\0' ' 'cat /proc/$$/environ | tr '\0' '\n' | head -10
# 查看系统中所有进程的 PID 和命令ls /proc/ | grep -E '^[0-9]+$' | head -10实践 3:探索 /sys 文件系统
# 查看 sysfs 的顶层结构ls /sys/
# 查看块设备信息ls /sys/block/
# 查看第一个 CPU 的信息cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list
# 查看已加载的内核模块ls /sys/module/ | wc -lls /sys/module/ | head -10
# 查看某个模块的详细信息(以 ext4 为例)ls /sys/module/ext4/cat /sys/module/ext4/parameters/inode_readahead_blks 2>/dev/null || echo "参数不可读"实践 4:观察内核线程
# 列出所有内核线程ps -eo pid,ppid,comm | grep '\[' | head -20
# 观察 kthreadd(所有内核线程的父进程)ps -eo pid,ppid,comm | grep kthreadd# PID 2, PPID 0 —— kthreadd 是内核直接创建的
# 观察内核线程的树状关系pstree -p 2 | head -20
# 查看 ksoftirqd 的运行统计cat /proc/softirqs实践 5:通过 sysctl 修改内核参数
# 查看所有可调内核参数sysctl -a | wc -l
# 查看与内存相关的参数sysctl -a | grep vm | head -10
# 查看内核的 PID 最大值cat /proc/sys/kernel/pid_max
# 查看文件描述符限制cat /proc/sys/fs/file-max
# 临时修改一个内核参数(重启后失效)sudo sysctl vm.swappiness=10cat /proc/sys/vm/swappiness实践 6:观察系统调用(预告第 2 章)
# 使用 strace 跟踪一个简单命令的系统调用strace -c ls /tmp 2>&1 | tail -20
# 观察系统调用的详细过程strace -e trace=openat,read,write cat /proc/version小结
本章从宏观视角俯瞰了 Linux 内核的整体架构:
- 用户空间与内核空间通过 CPU 特权级(Ring 0 / Ring 3)和地址空间划分实现隔离,系统调用是两者之间的唯一桥梁
- 七大子系统(进程管理、内存管理、文件系统、网络协议栈、设备驱动、IPC、安全)各司其职,又相互协作
- 执行上下文决定了内核代码能做什么、不能做什么——进程上下文可睡眠,中断上下文绝对不能
- 宏内核 + 可加载模块的设计让 Linux 兼顾性能与灵活性,“机制而非策略”的哲学贯穿始终
- 侵入式数据结构(
list_head、hlist、rbtree、radix tree)是内核的基础设施,理解它们是阅读内核源码的前提 /proc和/sys是内核向用户空间暴露信息的窗口,也是观察和调优内核的利器- 内核线程是内核的”后台工人”,在进程上下文中执行内核的持续性任务
参考资料
经典教材
- 《Linux 内核设计与实现》(Robert Love)第 2 章——对 Linux 内核架构的精炼概述
- 《深入理解 Linux 内核》(Daniel P. Bovet 等)第 1 章——从体系结构角度剖析内核设计
- 《Linux 设备驱动程序》(Jonathan Corbet 等)第 2 章——内核模块与执行上下文的权威讲解
- 《操作系统导论》(OSTEP)(Remzi H. Arpaci-Dusseau)——建立操作系统宏观认知的最佳入门书
内核源码
include/linux/sched.h——task_struct结构体定义include/linux/list.h——list_head和hlist的完整实现include/linux/rbtree.h—— 红黑树接口定义include/linux/xarray.h—— XArray(radix tree 的现代替代)init/main.c——start_kernel()和rest_init(),内核的 C 语言入口kernel/kthread.c—— 内核线程的创建与管理
在线资源
- Linux 内核官方文档 — 内核各子系统的权威文档
- Bootlin Elixir Cross Referencer — 在线内核源码交叉引用,可按版本浏览
- Linux Kernel Newbies — 内核开发入门资源
- kernel.org 源码仓库 — 内核源码的官方 Git 仓库
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






