Unix 设计哲学教程 / 第 9 章:内核设计
第 9 章:内核设计
“The kernel is the core of the operating system. Everything else is a user.”
Unix 内核是整个操作系统的基石。它管理硬件资源,为用户空间程序提供统一的抽象接口。本章深入探讨 Unix 内核的设计哲学——从系统调用到进程管理,从虚拟文件系统到模块化架构。
9.1 内核架构概述
宏内核 vs 微内核
操作系统内核架构对比
宏内核(Monolithic Kernel) 微内核(Microkernel)
┌─────────────────────────┐ ┌─────────────────────────┐
│ 用户空间 │ │ 用户空间 │
│ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │ │ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │
│ │文件│ │网络│ │驱动│ │进程│ │ │ │文件│ │网络│ │驱动│ │进程│ │
│ └──┘ └──┘ └──┘ └──┘ │ │ └──┘ └──┘ └──┘ └──┘ │
├─────────────────────────┤ ├─────────────────────────┤
│ 内核空间(全部在一起) │ │ 内核空间(极小) │
│ ┌─────────────────────┐│ │ ┌─────────────────────┐│
│ │VFS│网络│驱动│调度│IPC││ │ │ IPC│调度│内存管理 ││
│ └─────────────────────┘│ │ └─────────────────────┘│
└─────────────────────────┘ └─────────────────────────┘
Linux, FreeBSD Mach, QNX, seL4
| 维度 | 宏内核 | 微内核 |
|---|---|---|
| 性能 | 高(直接函数调用) | 较低(IPC 开销) |
| 稳定性 | 一个驱动崩溃可能影响整个内核 | 驱动崩溃不影响内核 |
| 代码量 | 大(数百万行) | 小(数万行) |
| 扩展性 | 模块化加载 | 服务级别扩展 |
| 安全性 | 攻击面大 | 攻击面小 |
Linux 的折中方案
Linux 采用宏内核 + 可加载模块的混合架构:
┌─────────────────────────────────────┐
│ 用户空间 │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │bash│ │vim │ │nginx│ │app │ │
│ └────┘ └────┘ └────┘ └────┘ │
├─────────────────────────────────────┤
│ 系统调用接口 │
├─────────────────────────────────────┤
│ 内核空间 │
│ ┌─────────────────────────────────┐│
│ │ VFS │ 进程调度 │ 网络栈 │ 内存管理││
│ ├─────────────────────────────────┤│
│ │ 设备驱动 │ 文件系统 │ 安全模块 ││
│ │ (可动态加载的模块) ││
│ ├─────────────────────────────────┤│
│ │ 硬件抽象层 ││
│ └─────────────────────────────────┘│
├─────────────────────────────────────┤
│ 硬件 │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ CPU│ │内存│ │磁盘│ │网卡│ │
│ └────┘ └────┘ └────┘ └────┘ │
└─────────────────────────────────────┘
# 查看内核模块
lsmod | head -20
# 加载/卸载内核模块
sudo modprobe vfat # 加载 VFAT 文件系统模块
sudo rmmod vfat # 卸载模块
# 查看模块信息
modinfo ext4
# 查看内核配置
zcat /proc/config.gz | grep CONFIG_MODULES # 部分发行版
cat /boot/config-$(uname -r) | grep CONFIG_MODULES
9.2 系统调用(System Call)
系统调用接口
系统调用是用户空间程序与内核交互的唯一正规途径。
用户空间 → 内核空间的转换过程
用户程序: read(fd, buf, count)
│
▼
C 库 (glibc): 封装系统调用
│
▼
系统调用号加载到寄存器
│
▼
触发软中断/特殊指令 (syscall/sysenter)
│
▼
CPU 切换到内核模式
│
▼
内核: sys_read() 执行实际操作
│
▼
返回用户空间
核心系统调用
/* 进程管理 */
pid_t fork(void); // 创建子进程
int execve(const char *path, char *const argv[], char *const envp[]);
pid_t waitpid(pid_t pid, int *status, int options);
void _exit(int status);
pid_t getpid(void);
pid_t getppid(void);
/* 文件 I/O */
int open(const char *pathname, int flags, mode_t mode);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);
int close(int fd);
int dup(int oldfd);
int dup2(int oldfd, int newfd);
/* 文件系统 */
int stat(const char *pathname, struct stat *statbuf);
int mkdir(const char *pathname, mode_t mode);
int unlink(const char *pathname);
int rename(const char *oldpath, const char *newpath);
int chmod(const char *pathname, mode_t mode);
int chown(const char *pathname, uid_t owner, gid_t group);
/* 进程间通信 */
int pipe(int pipefd[2]);
int socketpair(int domain, int type, int protocol, int sv[2]);
int shmget(key_t key, size_t size, int shmflg); // 共享内存
int semget(key_t key, int nsems, int semflg); // 信号量
int msgget(key_t key, int msgflg); // 消息队列
/* 信号 */
int kill(pid_t pid, int sig);
sighandler_t signal(int signum, sighandler_t handler);
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/* 内存管理 */
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
int mprotect(void *addr, size_t length, int prot);
void *sbrk(intptr_t increment);
系统调用数量
# 查看系统支持的系统调用数量
ausyscall --dump | wc -l # 需要安装 auditd
# 或
cat /usr/include/asm-generic/unistd.h | grep __NR_ | wc -l
# Linux x86_64 约有 300+ 个系统调用
# 对比:Windows 有数千个 Win32 API
# Unix 的"少即是美"——用少量系统调用组合实现复杂功能
strace:追踪系统调用
# strace 是调试 Unix 程序的利器
# 基本用法
strace ls /tmp
# 统计系统调用
strace -c ls /tmp
# % time seconds usecs/call calls errors syscall
# ------ ----------- ----------- --------- --------- --------
# 45.23 0.001234 10 123 read
# 30.12 0.000823 5 164 write
# ...
# 跟踪特定系统调用
strace -e trace=open,read,write ls /tmp
# 跟踪正在运行的进程
strace -p PID
# 跟踪子进程
strace -f command
# 过滤输出
strace -e trace=file ls /tmp # 只显示文件相关
strace -e trace=network curl url # 只显示网络相关
strace -e trace=process bash -c "echo hello" # 只显示进程相关
# 输出到文件
strace -o trace.log ls /tmp
# 时间戳
strace -t ls /tmp # 显示时间
strace -T ls /tmp # 显示每个调用的耗时
# strace 实战:调试"为什么程序找不到配置文件"
strace -e trace=open,openat myapp 2>&1 | grep -i conf
# openat(AT_FDCWD, "/etc/myapp/config.ini", O_RDONLY) = -1 ENOENT (No such file or directory)
# openat(AT_FDCWD, "/home/user/.myapp/config.ini", O_RDONLY) = -1 ENOENT
# openat(AT_FDCWD, "./config.ini", O_RDONLY) = -1 ENOENT
# → 程序在三个位置查找配置文件,但都不存在
9.3 进程管理
进程的生命周期
进程状态转换图
┌──────────┐
│ 创建 │ fork()
│ (created)│
└────┬─────┘
│
▼
┌──────────┐ 调度器选择 ┌──────────┐
│ 就绪 │ ──────────────→ │ 运行 │
│ (ready) │ │(running) │
└──────────┘ └────┬─────┘
▲ │
│ 时间片用完 │
└────────────────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 等待 │ │ 僵尸 │ │ 终止 │
│(waiting) │ │ (zombie) │ │ (dead) │
└──────────┘ └──────────┘ └──────────┘
│
▼
I/O 完成/信号到达
│
▼
┌──────────┐
│ 就绪 │
│ (ready) │
└──────────┘
fork + exec 模型
/* Unix 进程创建的经典模型:fork + exec */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
/* fork 失败 */
perror("fork");
exit(1);
} else if (pid == 0) {
/* 子进程 */
printf("子进程 PID: %d, 父进程 PID: %d\n", getpid(), getppid());
execlp("ls", "ls", "-la", NULL);
/* 如果 exec 返回,说明执行失败 */
perror("exec");
exit(1);
} else {
/* 父进程 */
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("子进程以退出码 %d 结束\n", WEXITSTATUS(status));
}
}
return 0;
}
进程查看与管理
# 查看所有进程
ps aux # BSD 风格
ps -ef # System V 风格
# 进程树
pstree # 树形显示
ps auxf # 带缩进的森林视图
# 实时进程监控
top # 经典工具
htop # 增强版(需安装)
atop # 高级系统和进程监控
# 查看特定进程
ps -p 1 -o pid,ppid,comm,args # 查看 PID 1 的详细信息
ls -la /proc/1/exe # 查看进程的可执行文件
cat /proc/1/cmdline | tr '\0' ' ' # 查看进程的完整命令行
cat /proc/1/environ | tr '\0' '\n' # 查看进程的环境变量
# 进程优先级
nice -n 10 command # 以较低优先级运行
renice -n 5 -p PID # 修改运行中进程的优先级
ionice -c 2 -n 4 -p PID # 修改 I/O 调度优先级
# 发送信号
kill PID # 发送 SIGTERM(优雅终止)
kill -9 PID # 发送 SIGKILL(强制终止)
kill -HUP PID # 发送 SIGHUP(通常用于重新加载配置)
killall name # 按名称发送信号
pkill pattern # 按模式发送信号
进程关系
进程关系
├── 父子关系 —— fork 创建的进程继承父进程的属性
├── 进程组 —— 相关进程的集合(如管道中的命令)
├── 会话 —— 一个登录会话中的所有进程组
├── 控制终端 —— 会话关联的终端
└── 守护进程 —— 脱离控制终端,在后台运行
# 查看进程关系
ps -eo pid,ppid,pgid,sid,tty,comm
# 创建守护进程
# nohup 使进程忽略 SIGHUP
nohup command &
# 使用 systemd 管理守护进程
systemctl start nginx
systemctl status nginx
systemctl stop nginx
# 进程的生命周期管理
# 1. 前台进程: 直接在终端运行
# 2. 后台进程: command &
# 3. 脱离终端: nohup, disown
# 4. 守护进程: systemd, supervisord
9.4 虚拟文件系统(VFS)
VFS 的作用
虚拟文件系统(Virtual File System)是内核中的一个抽象层,它使得不同的文件系统实现可以使用统一的接口。
VFS 在文件系统栈中的位置
用户程序: open("/home/user/file.txt", O_RDONLY)
│
▼
┌─────────────────────────────────┐
│ VFS (虚拟文件系统层) │
│ 统一接口: open/read/write/close │
├─────────────────────────────────┤
│ 具体文件系统实现 │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ ext4 │ │ xfs │ │ btrfs│ │
│ └──────┘ └──────┘ └──────┘ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ nfs │ │ tmpfs│ │ proc │ │
│ └──────┘ └──────┘ └──────┘ │
├─────────────────────────────────┤
│ 块设备层 │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ sda │ │ nvme │ │ loop │ │
│ └──────┘ └──────┘ └──────┘ │
└─────────────────────────────────┘
VFS 的核心对象
/* VFS 的四个核心对象 */
struct super_block; /* 超级块 —— 文件系统元数据 */
struct inode; /* 索引节点 —— 文件元数据(权限、大小、时间戳) */
struct dentry; /* 目录项 —— 文件名到 inode 的映射 */
struct file; /* 文件对象 —— 打开文件的实例(读写位置等) */
/* 关系:
* super_block 1:N inode(一个文件系统有多个文件)
* inode 1:N dentry(一个文件可以有多个名字,即硬链接)
* inode 1:N file(一个文件可以被多次打开)
*/
Linux 支持的文件系统
# 查看内核支持的文件系统
cat /proc/filesystems
# nodev sysfs
# nodev tmpfs
# nodev bdev
# nodev proc
# nodev devtmpfs
# ext3
# ext4
# xfs
# btrfs
# ...
# 查看当前挂载的文件系统
df -Th
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/sda1 ext4 100G 45G 50G 48% /
# tmpfs tmpfs 7.8G 0 7.8G 0% /dev/shm
# /dev/sdb1 xfs 500G 200G 300G 40% /data
| 文件系统 | 特点 | 适用场景 |
|---|---|---|
| ext4 | 稳定可靠,广泛支持 | 通用 Linux 系统 |
| XFS | 高性能,大文件优化 | 数据库、大文件存储 |
| Btrfs | 快照、压缩、校验 | NAS、备份 |
| ZFS | 企业级特性,数据完整性 | 服务器、存储系统 |
| tmpfs | 内存文件系统 | /tmp, /dev/shm |
| procfs | 虚拟,进程信息 | /proc |
| sysfs | 虚拟,设备信息 | /sys |
| overlayfs | 联合文件系统 | Docker 容器 |
| FUSE | 用户空间文件系统 | sshfs, ntfs-3g |
9.5 内存管理
虚拟内存
虚拟内存的工作原理
每个进程看到的地址空间(虚拟地址)
┌──────────────────┐ 高地址
│ 内核空间 │ (对用户不可见)
├──────────────────┤
│ 栈 (Stack) │ ↓ 向下增长
│ ↓ │
│ │
│ 空闲区域 │
│ │
│ ↑ │
│ 堆 (Heap) │ ↑ 向上增长
├──────────────────┤
│ BSS (未初始化) │
├──────────────────┤
│ 数据 (已初始化) │
├──────────────────┤
│ 代码 (Text) │
└──────────────────┘ 低地址
通过页表映射到物理内存
虚拟地址 → 页表 → 物理地址
# 查看进程的内存映射
cat /proc/1/maps
# 或
pmap PID
# 查看系统内存使用
free -h
# total used free shared buff/cache available
# Mem: 15Gi 8.2Gi 2.1Gi 512Mi 5.0Gi 6.5Gi
# Swap: 8.0Gi 1.0Gi 7.0Gi
# 查看虚拟内存统计
vmstat 1 5
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa
# 1 0 1048576 2150400 512000 5120000 0 0 5 10 500 1000 5 2 92 1
# 查看 OOM Killer 日志
dmesg | grep -i "out of memory"
journalctl -k | grep -i "oom"
9.6 设备驱动
字符设备与块设备
/* 设备驱动的基本结构 */
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
static dev_t dev_num;
static struct cdev my_cdev;
/* 文件操作接口 —— 与用户空间相同! */
static int my_open(struct inode *inode, struct file *filp) {
return 0;
}
static ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *pos) {
/* 实现读取逻辑 */
return count;
}
static ssize_t my_write(struct file *filp, const char __user *buf,
size_t count, loff_t *pos) {
/* 实现写入逻辑 */
return count;
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.read = my_read,
.write = my_write,
};
static int __init my_init(void) {
alloc_chrdev_region(&dev_num, 0, 1, "mydevice");
cdev_init(&my_cdev, &my_fops);
cdev_add(&my_cdev, dev_num, 1);
return 0;
}
static void __exit my_exit(void) {
cdev_del(&my_cdev);
unregister_chrdev_region(dev_num, 1);
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
# 查看设备信息
ls -la /dev/ | head -20
cat /proc/devices # 已注册的字符和块设备
lspci # PCI 设备列表
lsusb # USB 设备列表
lshw # 硬件信息(需 root)
9.7 内核的可扩展性
可加载内核模块(LKM)
# 内核模块允许在运行时扩展内核功能
# 无需重新编译整个内核
# 查看已加载的模块
lsmod
# Module Size Used by
# nvidia 35438592 0
# ext4 1048576 1
# ...
# 加载模块
sudo modprobe nvidia
# 卸载模块
sudo rmmod nvidia
# 查看模块信息
modinfo nvidia
# 模块参数
cat /sys/module/ext4/parameters/
# 写一个简单的内核模块
cat > hello.c << 'EOF'
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init hello_init(void) {
printk(KERN_INFO "Hello, Kernel!\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, Kernel!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Hello World Kernel Module");
EOF
# Makefile
cat > Makefile << 'EOF'
obj-m += hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
EOF
make
sudo insmod hello.ko
dmesg | tail -5
sudo rmmod hello
9.8 内核参数调优
sysctl 接口
# 查看所有内核参数
sysctl -a | wc -l # 通常有数千个参数
# 查看特定参数
sysctl net.ipv4.ip_forward
sysctl vm.swappiness
sysctl kernel.pid_max
# 临时修改
sudo sysctl -w net.ipv4.ip_forward=1
# 持久化修改
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# 或使用 /etc/sysctl.d/ 目录
cat > /etc/sysctl.d/99-custom.conf << 'EOF'
# 网络优化
net.core.somaxconn = 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 1024 65535
# 内存优化
vm.swappiness = 10
vm.overcommit_memory = 1
vm.dirty_ratio = 20
# 文件系统
fs.file-max = 2097152
fs.inotify.max_user_watches = 524288
# 安全
kernel.randomize_va_space = 2
net.ipv4.conf.all.rp_filter = 1
EOF
sudo sysctl --system
常用内核参数
| 参数 | 含义 | 推荐值(服务器) |
|---|---|---|
vm.swappiness | 交换倾向 | 10 |
vm.dirty_ratio | 脏页比率 | 20 |
net.core.somaxconn | TCP 监听队列长度 | 65535 |
net.ipv4.tcp_tw_reuse | TIME_WAIT 重用 | 1 |
net.ipv4.tcp_fin_timeout | FIN 超时时间 | 15 |
fs.file-max | 最大文件描述符数 | 2097152 |
kernel.pid_max | 最大 PID | 4194304 |
注意事项
- 不要在生产环境随意修改内核参数:先在测试环境验证,了解参数的副作用。
- strace 有性能开销:不要在生产环境中对高频调用的进程使用 strace,除非必要。
- 内核模块是内核代码:有 bug 的内核模块可以让整个系统崩溃。编写和测试要非常谨慎。
- fork 不复制内存:现代 Linux 使用 Copy-on-Write(COW),fork 后父子进程共享内存页,只在写入时才复制。
- OOM Killer:当系统内存不足时,Linux 内核会主动杀死进程。可以通过
oom_score_adj调整进程被杀死的优先级。
扩展阅读
- Linux Kernel Documentation
- Understanding the Linux Kernel, 3rd Edition — Bovet & Cesati
- Linux Kernel Development, 3rd Edition — Robert Love
- strace(1) man page
- sysctl(8) man page
- The Linux Programming Interface — Michael Kerrisk