Skip to main content

Linux 是怎样工作的 - [日] 武内觉

pFL0uA

本书结合大量实验程序和图表,通俗易懂地介绍了 Linux 操作系统的运行原理和硬件的基础知识,涉及进程管理、进程调度器、内存管理、存储层次、文件系统和外部存储器等。

关于作者

武内觉 是日本知名的系统编程专家和技术作家:

  • 系统软件工程师:长期从事操作系统和底层系统开发
  • 技术作家:著有多本 Linux 和系统编程相关书籍
  • 教育家:擅长用通俗易懂的方式讲解复杂的系统概念

武内觉以其"透过现象看本质"的写作风格著称,通过大量实验程序和图表,将抽象的操作系统概念具象化。

核心内容

1. 进程与线程

// 进程创建 - fork()
#include <unistd.h>

pid_t pid = fork();

if (pid == 0) {
// 子进程
printf("Child process, PID: %d\n", getpid());
execvp("/bin/ls", args); // 执行新程序
} else if (pid > 0) {
// 父进程
printf("Parent process, Child PID: %d\n", pid);
waitpid(pid, &status, 0); // 等待子进程
} else {
// fork 失败
perror("fork failed");
}

// 进程状态
// R (Running): 运行中或可运行
// S (Sleeping): 可中断睡眠
// D (Disk Sleep): 不可中断睡眠
// Z (Zombie): 僵尸进程
// T (Stopped): 停止状态

// 查看进程
ps aux // 查看所有进程
ps -ef | grep nginx // 查找特定进程
top // 实时查看进程状态
htop // 增强版 top
// 线程创建 - pthread
#include <pthread.h>

void* thread_func(void* arg) {
int* value = (int*)arg;
printf("Thread running, value: %d\n", *value);
return NULL;
}

pthread_t thread;
int value = 42;

// 创建线程
pthread_create(&thread, NULL, thread_func, &value);

// 等待线程结束
pthread_join(thread, NULL);

// 互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock(&mutex);
// 临界区代码
pthread_mutex_unlock(&mutex);

2. 进程调度

调度策略:

1. FIFO (SCHED_FIFO)
- 实时进程
- 先到先得,高优先级可抢占低优先级

2. Round Robin (SCHED_RR)
- 实时进程
- 时间片轮转,公平调度

3. CFS (Completely Fair Scheduler)
- 普通进程
- 根据 vruntime (虚拟运行时间) 调度
- 保证每个进程获得公平的 CPU 时间

查看调度信息:
chrt -p <pid> # 查看进程调度策略
chrt -f 10 ./program # 以 FIFO 策略运行程序

niceness (优先级):
nice -n 10 ./program # 以 nice 值 10 运行
renice 5 -p <pid> # 修改运行中进程的 nice 值

3. 内存管理

// 内存布局
/*
高地址
+------------------+
| 内核空间 |
+------------------+
| 栈 (Stack) | ← 向下增长
| ↓ |
| 堆 (Heap) | ← 向上增长
| ↑ |
| BSS 段 | 未初始化全局变量
| 数据段 | 已初始化全局变量
| 代码段 | 程序代码
+------------------+
低地址
*/

// 内存分配
#include <stdlib.h>

int* arr = (int*)malloc(100 * sizeof(int)); // 分配
arr = (int*)realloc(arr, 200 * sizeof(int)); // 重新分配
free(arr); // 释放

// 查看内存使用
cat /proc/meminfo # 详细内存信息
free -h # 人类可读格式
ps aux --sort=-%mem # 按内存使用排序进程

// 虚拟内存
// - 每个进程有独立的虚拟地址空间
// - 通过页表映射到物理内存
// - 支持内存超卖 (overcommit)

4. 文件系统

# 文件系统层次结构
/
├── bin/ # 基本命令
├── boot/ # 启动文件
├── dev/ # 设备文件
├── etc/ # 配置文件
├── home/ # 用户主目录
├── lib/ # 库文件
├── media/ # 可移动媒体
├── mnt/ # 挂载点
├── opt/ # 可选软件包
├── proc/ # 进程信息 (虚拟文件系统)
├── root/ # root 用户主目录
├── run/ # 运行时数据
├── sbin/ # 系统管理命令
├── sys/ # 系统信息 (虚拟文件系统)
├── tmp/ # 临时文件
├── usr/ # 用户程序
└── var/ # 可变数据 (日志、缓存等)

# 查看文件系统的信息
df -h # 磁盘使用量
du -sh * # 目录大小
inode # inode 信息
ls -li # 查看 inode 号

# 文件类型
# - : 普通文件
# d : 目录
# l : 符号链接
# c : 字符设备
# b : 块设备
# p : 管道
# s : 套接字

5. 系统调用

// I/O 系统调用
#include <fcntl.h>
#include <unistd.h>

int fd = open("file.txt", O_RDONLY); // 打开文件
char buf[1024];
read(fd, buf, sizeof(buf)); // 读取
write(fd, buf, len); // 写入
close(fd); // 关闭

// 文件操作
unlink("file.txt"); // 删除文件
link("old", "new"); // 硬链接
symlink("target", "link"); // 软链接
rename("old", "new"); // 重命名

// 进程控制
pid_t getpid(void); // 获取进程 ID
pid_t getppid(void); // 获取父进程 ID
pid_t fork(void); // 创建进程
int execvp(...); // 执行程序
int wait(...); // 等待进程

// 查看系统调用
strace -p <pid> # 跟踪进程的系统调用
lsof -p <pid> # 查看进程打开的文件

6. 信号处理

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void signal_handler(int sig) {
printf("Received signal: %d\n", sig);
}

// 注册信号处理函数
signal(SIGINT, signal_handler); // Ctrl+C
signal(SIGTERM, signal_handler); // 终止信号
signal(SIGHUP, signal_handler); // 挂起信号

// 发送信号
kill -SIGTERM <pid> # 优雅终止
kill -SIGKILL <pid> # 强制终止
kill -SIGHUP <pid> # 重新加载配置
kill -SIGUSR1 <pid> # 用户定义信号

// 常见信号
// SIGINT (2): 中断信号 (Ctrl+C)
// SIGTERM (15): 终止信号
// SIGKILL (9): 强制杀死 (不可捕获)
// SIGHUP (1): 挂起信号
// SIGSEGV (11): 段错误
// SIGPIPE (13): 管道破裂
// SIGCHLD (17): 子进程状态改变

7. 管道与重定向

# 管道
ls -la | grep ".txt" | wc -l

# 标准输入/输出/错误
# 0: stdin (标准输入)
# 1: stdout (标准输出)
# 2: stderr (标准错误)

# 重定向
command > file.txt # 覆盖输出
command >> file.txt # 追加输出
command < file.txt # 从文件读取
command 2>&1 # stderr 重定向到 stdout
command &> file.txt # 所有输出重定向

# xargs: 将输入转换为参数
find . -name "*.log" | xargs rm
find . -name "*.jpg" | xargs -I {} cp {} /backup/

# tee: 同时输出到文件和屏幕
command | tee output.log
command | tee -a output.log # 追加模式

经典摘录

操作系统是硬件和软件之间的桥梁。理解操作系统的工作原理,是成为优秀工程师的基础。

进程是资源分配的最小单位,线程是 CPU 调度的最小单位。

虚拟内存让每个进程都以为自己独占整个内存空间。这是操作系统最伟大的谎言。

一切皆文件。这是 UNIX/Linux 哲学的核心。

不要重复造轮子,但要了解轮子是怎么转的。

读书心得

《Linux 是怎样工作的》是一本非常适合入门的操作系统书籍。作者用通俗易懂的语言和大量图表,将抽象的操作系统概念具象化。

书中对我帮助最大的是进程管理部分。通过 fork() 实验,直观地理解了进程创建的机制。通过观察 ps 输出,了解了进程的各种状态。这些知识对于理解服务器程序的运行原理非常重要。

内存管理部分也讲解得非常清晰。虚拟内存、页表、堆栈等概念,通过图表和实验变得容易理解。理解这些概念,有助于编写更高效的程序,也有助于排查内存相关问题。

文件系统部分让我对"一切皆文件"的 UNIX 哲学有了更深理解。不仅普通文件,设备、管道、套接字都是文件。这种统一的设计思想,让 Linux 的 I/O 操作变得简洁优雅。

对于后端开发者来说,Linux 是主要的工作平台。理解 Linux 的工作原理,能帮助我们:

  • 更好地调试程序(strace、lsof 等工具)
  • 优化程序性能(CPU、内存、磁盘 I/O)
  • 理解容器技术(namespace、cgroup)
  • 排查线上问题(进程、文件、网络)

这本书是很好的起点。它不会让你成为系统专家,但会帮你建立正确的知识框架。