← 返回文章归档

操作系统基础:进程、内存与文件如何协作

从程序运行的过程出发,梳理进程与线程、调度、虚拟内存、同步、死锁和文件系统,理解操作系统在管理什么。

程序运行时发生了什么

磁盘上的可执行文件只是程序的一种存储形式。运行它时,操作系统建立进程,准备地址空间和执行环境,让 CPU 执行指令,并管理文件、设备与权限。

可以从三个问题开始学习:怎样让多个任务共享机器,怎样让它们安全协作,怎样让数据在程序退出后保留下来。Operating Systems: Three Easy Pieces 将这些学习主题组织为虚拟化、并发和持久化。

进程与线程

进程是运行中程序的实例,拥有地址空间等资源。线程是进程中的执行流,同一进程中的线程通常共享代码、全局数据和打开的文件,同时各自保留栈、寄存器状态等执行上下文。Linux 的共享与独立属性可以查阅 pthreads(7)。

对比项不同进程同一进程中的线程
地址空间通常相互隔离共享
交换数据常用管道、套接字、共享内存可直接访问共享对象
并发风险仍需要协调共享资源容易出现共享内存竞争

进程隔离不是绝对的:共享内存、继承的文件描述符和外部文件仍可能让多个进程访问同一资源。

Linux 上常见的启动方式是先 fork 创建子进程,再 exec 加载另一个程序。exec 替换当前进程的程序映像,并不额外创建进程;fork 则利用写时复制等机制,避免立即复制所有物理内存。参见 fork(2)。

调度、并发与并行

单个 CPU 核心可以在任务之间切换,让多个任务在一段时间内交错推进,这叫并发。多个核心同时执行不同任务,则是并行。

一个任务可能正在运行、已经准备好等待 CPU,或者正在等待 I/O 等事件。读取磁盘、等待网络响应时,让其他任务运行能够提高资源利用率。

调度需要权衡响应速度、公平性和吞吐量。时间片过短会增加切换成本,过长可能降低交互体验。线程数量也不是越多越好:锁竞争、内存占用和调度开销都可能成为负担。

虚拟内存不是额外的物理内存

程序使用虚拟地址,硬件与操作系统通过页表把它转换成物理地址。这样既方便组织地址空间,也能检查访问权限。分页把地址空间划成固定大小的页,页表记录映射,TLB 缓存一部分地址转换结果。

缺页异常表示当前访问需要由操作系统进一步处理。它可能触发分配页面、建立映射或从存储设备读取数据;也可能因为访问非法而导致进程收到错误信号。不能把所有缺页都等同于“内存不够”。

程序申请了 1 GB 地址空间,也不等于此刻占用了 1 GB 物理内存。观察程序时,要区分虚拟地址空间、常驻内存和共享页面等指标。

共享变量为什么需要同步

假设两个线程都执行 counter += 1。在没有同步保证的模型中,它可能拆成读取、加一、写回。如果两个线程都读到 5,最终就可能只写回 6,而不是 7。在 C/C++ 等语言中,无同步的数据竞争还可能导致未定义行为。

互斥锁让临界区中的操作按顺序执行;条件变量帮助线程等待某个条件;信号量可以管理资源数量。使用这些工具时,需要先说清楚“保护哪个共享状态”,再决定锁的范围。

死锁的一种常见情形是:线程 A 持有锁 X 等待锁 Y,线程 B 持有锁 Y 等待锁 X。统一锁的获取顺序,可以避免这类循环等待;超时和取消策略也有用途,但需要正确处理失败后的状态。

系统调用与文件

应用通过系统调用请求内核服务,例如打开文件、读写数据或创建进程。库函数与系统调用不是一回事:一个库函数可能封装多个系统调用,也可能完全在用户态运行。

文件描述符是进程使用的整数句柄。按惯例,0、1、2 分别是标准输入、标准输出和标准错误。它们可以指向终端,也可以重定向到文件或管道。

文件名属于目录组织的一部分,文件内容和元数据由文件系统管理。把数据写入缓冲区或成功调用一次 write,不代表所有数据已经抵达持久存储。要求断电后仍保留的场景,需要结合 fsync、目录同步及具体文件系统语义设计。

动手观察

在 Linux 终端中执行:

ps -eo pid,ppid,stat,comm
top
free -h
df -h

ps 观察进程与父子关系,top 观察运行状态,free 观察系统内存,df 观察已挂载文件系统的空间。Linux 会用空闲内存做缓存,判断内存压力时应结合 available 等指标。

练习解释:一个等待网络的进程为什么不一定占满 CPU?同一进程的线程为什么不能随意修改共享变量?文件已经写入,为什么还要考虑持久化?之后可沿着 OSTEP 的进程、分页、锁和文件系统章节继续学习。

✳

感谢阅读,下一篇再见。

回到文章开头 ↑