【Linux 系列·第 02 篇】操作系统原理:进程·内存·文件系统·I/O——Linux 怎么工作 【Linux 系列·第 02 篇】操作系统原理进程·内存·文件系统·I/O——Linux 怎么工作系列回顾第 01 篇我们绘制了 Linux 的全景图——四代演进、三层架构、三大哲学。本篇深入 Linux 的核心操作系统原理——Linux 怎么工作操作系统是计算机的大管家——它管理四大资源CPU进程管理、内存内存管理、磁盘文件系统、设备I/O 系统。进程管理决定谁用 CPU、用多久——fork() 创建进程CFS 调度器公平分配 CPU 时间上下文切换在进程间切换。内存管理决定谁用内存、用多少——虚拟内存给每个进程 128TB 的地址空间页表TLB 加速地址翻译按需分配Swap 用磁盘扩展内存。文件系统决定数据怎么存、怎么找——VFS 统一所有文件系统接口ext4 是默认文件系统Page Cache 用内存加速磁盘访问。I/O 系统决定怎么和设备交互——中断DMA 减少CPU参与epoll 实现高并发 I/O。今天我们从进程管理、内存管理到文件系统与 I/O彻底拆解 Linux 操作系统的工作原理。 文章目录 一、进程管理CPU 的调度员 二、内存管理内存的分配器 三、文件系统与 I/O数据的管家 一、进程管理CPU 的调度员1.1 进程是什么进程Process是操作系统中最核心的抽象——一个正在运行的程序的实例。你在终端输入ls系统就创建了一个 ls 进程你打开浏览器系统就创建了一个浏览器进程。每个进程都有自己的地址空间、文件描述符表、信号处理等资源。Linux 中进程的表示task_struct进程控制块PCB。每个进程在内核中都有一个 task_struct 结构体记录了进程的所有信息PID进程 ID、状态运行/就绪/阻塞、优先级、地址空间、打开的文件、信号处理、调度信息等。task_struct 是 Linux 进程管理的核心数据结构——内核通过它了解和控制每个进程。Linux 的独特设计进程和线程统一为 task_struct。在 Linux 中线程只是共享地址空间的进程——它们用同样的 task_struct 表示通过 clone() 系统调用的参数控制共享程度。这种统一抽象简化了内核设计也使得 Linux 的线程创建比其他操作系统更高效。1.2 进程的生命周期fork → exec → exitLinux 进程的创建遵循 Unix 的经典模式fork() exec()。fork()创建子进程。fork() 创建当前进程的副本——子进程获得父进程地址空间、文件描述符表、信号处理等的拷贝。fork() 的返回值父进程返回子进程的 PID子进程返回 0——通过返回值区分父子进程。fork() 的关键优化写时复制Copy-on-Write, COW。fork() 时并不真正复制父进程的内存页而是让父子进程共享同一份物理页标记为只读。当任一方尝试写入时才触发缺页异常内核复制该页——这就是写时复制。COW 大幅降低了 fork() 的开销如果子进程立即 exec() 加载新程序父进程的内存页根本不需要复制。exec()加载新程序。exec() 用新的程序替换当前进程的代码和数据——保留 PID 和部分资源但代码段、数据段、堆栈全部替换。这就是为什么 fork()exec() 是创建新进程的标准模式fork() 创建进程壳exec() 加载新程序。exit()终止进程。进程调用 exit() 终止自己进入僵尸状态Zombie保留 task_struct 供父进程查询。父进程调用 wait() 回收子进程的资源——如果父进程不 wait子进程就永远停留在僵尸状态占用 PID 等资源。这就是僵尸进程问题——大量僵尸进程会耗尽 PID 资源。1.3 进程调度CFS 完全公平调度器Linux 的默认调度器是CFSCompletely Fair Scheduler, 2007——它的目标很简单公平地分配 CPU 时间给所有进程。CFS 的核心思想vruntime虚拟运行时间。每个进程维护一个 vruntime——它已经运行了多少虚拟时间。权重高的进程优先级高vruntime 增长慢权重低的进程 vruntime 增长快。CFS 每次选择 vruntime 最小的进程运行——这样权重高的进程自然获得更多 CPU 时间但不会饿死低权重进程。CFS 的数据结构红黑树。所有可运行进程按 vruntime 排列在红黑树中——选择下一个进程只需取最左节点 O(1)插入和删除 O(log N)。红黑树保证了调度的效率。CFS 的时间分配sched_period。CFS 保证在一个调度周期内每个可运行进程至少运行一次。时间分配 sched_period × (进程权重 / 总权重)。权重由 nice 值决定nice 值越低权重越高CPU 时间越多。nice 值范围 -20 到 19默认 0。1.4 进程状态与上下文切换Linux 进程的五种状态Running正在 CPU 上运行或等待 CPU、Interruptible可中断阻塞等待事件可被信号唤醒、Uninterruptible不可中断阻塞等待磁盘 I/O 等不可被信号唤醒、Stopped被暂停如 SIGSTOP、Zombie已终止等待父进程 wait 回收。上下文切换Context Switch从一个进程切换到另一个进程——保存当前进程的寄存器、栈指针等 CPU 状态恢复下一个进程的 CPU 状态。上下文切换的开销1-10 微秒——看起来很短但频繁切换如大量进程竞争 CPU会显著影响性能。减少上下文切换是性能调优的重要方向。1.5 进程 vs 线程 vs 协程进程独立地址空间隔离性好创建开销大fork 需要复制页表等切换开销大需要切换地址空间。适用于需要隔离的任务。线程共享地址空间通信高效直接共享变量创建开销中pthread_create 需要分配栈等切换开销中不需要切换地址空间。适用于并行计算。协程用户态调度创建开销极低只需分配栈切换开销极低不需要内核参与。适用于高并发 I/O如 Go 的 goroutine、Python 的 asyncio。Linux 的统一抽象进程和线程都是 task_struct通过 clone() 的参数控制共享程度。这种设计使得 Linux 的线程创建比 Windows 更高效——Windows 线程是专门的内核对象而 Linux 线程只是共享地址空间的进程。 二、内存管理内存的分配器2.1 虚拟内存每个进程的独栋别墅虚拟内存Virtual Memory是现代操作系统最重要的发明之一——给每个进程一个独立的、连续的、私有的地址空间。32 位系统每个进程 4GB64 位系统每个进程 128TB。进程以为自己独占整个内存空间实际上物理内存可能远小于虚拟地址空间。虚拟内存的三大好处隔离每个进程的地址空间独立一个进程崩溃不会影响其他进程、简化编程程序员不需要关心物理内存布局直接使用虚拟地址、扩展容量物理内存不够时用磁盘Swap补充让每个进程都能使用超过物理内存的空间。进程地址空间的布局从低到高代码段.text可执行代码只读→数据段.data已初始化全局变量.bss未初始化全局变量→堆Heap向上增长malloc/new 分配→内存映射区mmap共享库、文件映射→栈Stack向下增长局部变量、函数调用→内核空间高地址用户不可访问。2.2 页表与 TLB虚拟地址到物理地址的翻译虚拟内存的核心问题虚拟地址怎么翻译为物理地址答案是页表Page Table。Linux 采用多级页表64 位系统使用 4 级页表PGD → PUD → PMD → PTE。虚拟地址被分为 5 部分页全局目录索引、页上级目录索引、页中间目录索引、页表项索引、页内偏移。每次地址翻译需要 4 次内存访问——太慢了TLBTranslation Lookaside Buffer页表的缓存。TLB 存储最近使用的虚拟地址→物理地址映射CPU 先查 TLB命中则直接获得物理地址1 个时钟周期未命中则查页表4 次内存访问。TLB 命中率通常在 95-99%是内存性能的关键。大页Huge Pages默认页大小 4KB大页 2MB 或 1GB。大页减少页表项数量减少 TLB Miss提高性能。深度学习训练常用大页——因为模型参数占用大量内存4KB 页导致频繁 TLB Miss。2.3 按需分配与缺页中断Linux 的内存分配策略按需分配Demand Paging——malloc() 只分配虚拟地址不分配物理内存。当进程首次访问该地址时触发缺页中断Page Fault内核才分配物理页。缺页中断的处理流程CPU 访问虚拟地址 → MMU 查页表 → 页表项标记不存在 → 触发缺页中断 → 内核分配物理页 → 更新页表 → 重新执行指令。这种用到了才分配的策略避免了浪费——malloc(1GB) 不会立即占用 1GB 物理内存只有实际访问的页才分配。2.4 Swap 与 OOM KillerSwap交换空间当物理内存不足时内核将不常用的内存页写入磁盘的 Swap 空间释放物理内存给需要的进程。Swap 让系统可以使用超过物理内存的内存——但代价是速度磁盘访问比内存慢 1000 倍以上。Swap 频繁使用Swap Thrashing会导致系统极度缓慢。OOM KillerOut-Of-Memory Killer当物理内存和 Swap 都耗尽时内核启动 OOM Killer——选择一个进程杀死释放内存。OOM Killer 的选择标准oom_score 越高越容易被杀——占用内存多、优先级低、子进程多的进程得分高。可以通过/proc/[pid]/oom_adj调整分数保护关键进程不被杀。2.5 内存分配brk/mmap vs kmalloc用户空间内存分配brk()调整堆顶指针用于小内存分配malloc 128KBmmap()在内存映射区分配用于大内存分配malloc 128KB。free() 时brk 分配的内存可能不会立即归还内核内存池mmap 分配的内存会立即归还。内核空间内存分配kmalloc()分配物理连续的内存DMA 等需要vmalloc()分配虚拟连续但物理不连续的内存大块内存。kmalloc 速度快但有碎片风险vmalloc 灵活但需要页表映射。 三、文件系统与 I/O数据的管家3.1 VFS一切文件系统的统一接口VFSVirtual File System虚拟文件系统是 Linux 文件系统的核心抽象层——为所有文件系统提供统一的接口。无论底层是 ext4、XFS、Btrfs、NTFS 还是 /proc用户程序都用同样的 open()/read()/write()/close() 操作它们。这就是一切皆文件的实现基础。VFS 的四大核心对象Superblock超级块文件系统的元信息——大小、类型、状态、Inode索引节点文件的元数据——权限、大小、数据块位置、时间戳、Dentry目录项文件名到 Inode 的映射——加速路径查找、File打开的文件实例——读写偏移、访问模式、引用计数。VFS 的工作流程用户调用 open(“/home/user/file.txt”) → VFS 逐级解析路径/ → home → user → file.txt→ 每级查找 Dentry 缓存未命中则读取磁盘 Inode → 找到文件的 Inode → 创建 File 对象 → 返回文件描述符 fd。后续 read(fd)/write(fd) 都通过 fd 找到 File 对象再找到 Inode最终操作数据块。3.2 ext4Linux 的默认文件系统ext4Fourth Extended Filesystem是 Linux 的默认文件系统——自 2008 年引入以来一直是大多数 Linux 发行版的默认选择。ext4 的前身ext1992→ ext21993→ ext32001添加日志→ ext42008大文件支持、延迟分配。ext4 的磁盘布局磁盘被划分为块组Block Group每个块组包含超级块备份文件系统元信息的副本、块位图记录哪些数据块空闲、Inode 位图记录哪些 Inode 空闲、Inode 表存储所有 Inode、数据块存储文件内容。ext4 的三大关键特性日志JBD2——写操作先记录日志再写入数据块。如果写入过程中崩溃重启后可以通过日志恢复保证文件系统一致性。延迟分配Delayed Allocation——写数据时先写入 Page Cache不立即分配磁盘块等到刷回磁盘时才分配。这减少了碎片提高了写入性能。Extent——用起始块长度表示连续的数据块而不是逐块映射。一个大文件可能只需要几个 Extent而不是几千个块映射。3.3 Page Cache用内存加速磁盘Page Cache 是 Linux 文件系统性能的关键——将磁盘数据缓存在内存中。读操作先查 Page Cache命中则直接返回纳秒级未命中则读磁盘微秒级并缓存。写操作先写 Page Cache标记为脏页Dirty Page后台线程pdflush定期刷回磁盘或 fsync() 强制刷回。Page Cache 的关键特性空闲内存就是缓存——Linux 会将所有空闲内存用于 Page Cache当应用程序需要内存时自动回收。这意味着 Linux 的可用内存 空闲内存 可回收缓存——不要看到内存使用率高就慌大部分可能是 Page Cache。3.4 I/O 系统中断、DMA 与 epoll中断Interrupt设备通过中断通知 CPU 事件——“数据准备好了”、“操作完成了”。CPU 收到中断后暂停当前任务执行中断处理程序然后恢复。中断让 CPU 不需要轮询设备状态大幅提高效率。DMADirect Memory Access让设备直接读写内存不需要 CPU 参与。没有 DMACPU 发指令 → 设备处理 → CPU 拷贝数据到内存。有 DMACPU 发指令 → 设备处理 → DMA 直接将数据写入内存 → 中断通知 CPU 完成。DMA 解放了 CPU让它做更有用的事。I/O 多路复用一个线程同时监控多个文件描述符的 I/O 事件。演进selectO(n)最多 1024 个 fd→ pollO(n)无限制但遍历→ epollO(1)事件驱动只返回就绪的 fd。epoll 是高并发的基石——Nginx、Redis、Node.js 都基于 epoll 实现高并发。epoll 的两种触发模式LTLevel Triggered水平触发——只要 fd 有数据可读epoll_wait 就返回。ETEdge Triggered边缘触发——只有 fd 状态变化时才返回需要一次性读完所有数据。ET 模式效率更高但编程更复杂。3.5 /proc 与 /sys内核信息的窗口/proc 和 /sys 是一切皆文件哲学的极致体现——虚拟文件系统不占磁盘空间读取时动态生成。/proc进程和系统信息。/proc/[pid]/status进程状态、/proc/[pid]/maps内存映射、/proc/[pid]/fd/打开的文件、/proc/cpuinfoCPU 信息、/proc/meminfo内存信息、/proc/loadavg负载均值。/sys设备和内核参数。/sys/class/设备分类、/sys/devices/设备树、/sys/kernel/内核参数。通过读写 /sys 的文件可以动态调整内核参数——不需要重启。 总结对比进程 vs 线程 vs 协程维度进程线程协程地址空间独立(开销大)共享(开销小)共享创建成本高(fork)中(pthread)低(用户态)切换成本高(1-10us)中(0.5-5us)低(0.1-0.5us)通信IPC(管道/共享内存)共享变量共享变量调度内核调度内核调度用户调度适用隔离任务并行计算高并发I/OI/O 多路复用维度selectpollepoll最大连接1024无限制无限制复杂度O(n)O(n)O(1)内存拷贝每次拷贝每次拷贝mmap共享触发方式水平触发水平触发水平边缘适用场景少量连接中量连接海量连接一句话总结Linux 操作系统四大核心原理进程管理进程是运行中的程序实例task_struct统一表示进程和线程/fork()exec()创建新进程写时复制优化/CFS完全公平调度器vruntime红黑树O(logN)/五种状态Running/Interruptible/Uninterruptible/Stopped/Zombie/上下文切换1-10us是性能杀手/进程vs线程vs协程隔离性递减并发性递增、内存管理虚拟内存每个进程128TB独立地址空间隔离简化编程扩展容量/4级页表PGD→PUD→PMD→PTETLB缓存加速/按需分配Demand Paging用到了才分配/Swap用磁盘扩展内存但慢1000x/OOM Killer内存耗尽时杀进程保命/brk小内存mmap大内存kmalloc物理连续vmalloc虚拟连续、文件系统VFS四大对象Superblock/Inode/Dentry/File统一所有文件系统接口/ext4默认文件系统日志延迟分配Extent/Page Cache用内存加速磁盘空闲内存缓存/proc和/sys虚拟文件系统内核信息的窗口、I/O系统中断通知CPU事件DMA解放CPU/epoll O(1)事件驱动高并发基石Nginx/Redis都用它/LT水平触发ET边缘触发。进程管理公平分配CPU时间/内存管理用空间换时间用磁盘换内存/文件系统一切皆文件缓存加速/I/O系统中断DMA事件驱动。参考链接CFS Scheduler (Linux Kernel Docs)Linux Virtual Memory (kernel.org)VFS (Linux Kernel Docs)epoll (man7.org)Understanding the Linux Kernel (Bovet Cesati)系列预告第 03 篇将深入常用命令——文件操作·进程管理·网络工具·文本处理掌握 Linux 的日常使用。