Linux 服务器高阶编程(5):进程基础与地址空间
一、什么是进程:与程序有何区别
操作系统进行执行与调度的基本单位。
启动一个程序时,系统为这次运行建立相应的进程,使程序里的任务能够被调度执行。
| 对比 | 程序 | 进程 |
|---|---|---|
| 状态 | 存放在磁盘上的静态代码和数据 | 程序的一次动态运行实例 |
| 是否正在执行 | 不一定 | 正处于运行生命周期中,可能在运行、等待或暂停 |
| 占用资源 | 至少占磁盘空间 | 运行时涉及地址空间、CPU 调度及其他系统资源 |
| 关系 | 同一个程序可被多次启动 | 每次启动可产生不同的进程实例 |
二、单进程、多进程与 CPU 时间片
1. 单进程与多进程
单进程按顺序处理任务,遇到等待时,后续工作也可能跟着等待。多进程让不同进程分别承担工作:一个进程等待时,CPU 可以去执行另一个进程的任务。不过多进程并非必然更快,因为创建进程、切换、通信和内存占用都有成本。
2. 分时复用、并发与并行
在单核上,多个就绪进程可以轮流获得 CPU 的一段使用时间
- 并发:一段时间内多个任务都在推进;在单核上通常表现为交替执行
- 并行:多个任务在同一时刻分别占用不同执行单元,真正同时运行;多核 CPU 可以提供这种条件
- 切换开销:切换进程不是零成本;进程过多时,调度成本可能抵消收益
三、进程的地址空间与 PCB
32 位 Linux 进程:低地址一侧约 0—3 GiB 为用户空间,高地址一侧约 3—4 GiB 为内核空间
| 区域 | 职责 |
|---|---|
| 用户空间 | 存放进程执行任务用的代码、数据、堆、栈、映射等 |
| 内核空间 | 由内核管理进程、调度、内存等系统信息,普通用户程序不能直接任意读写 |
PCB:进程控制块
PCB记录进程的管理信息
PID:进程标识
PID 是进程 ID。在同一 PID 命名空间里,同时存在的进程不会共用同一 PID;进程退出并被回收后,PID 将来可以再次分配。因此 PID 是运行期间的标识,不是永久身份证号。
四、用户空间里放什么
| 区域 | 说明 |
|---|---|
| 命令行参数、环境变量 | 程序启动时由外部传入的信息;可通过 main 参数等使用 |
| 栈 | 函数调用、局部变量、临时数据等 |
| 映射区 | 动态库、文件映射等 |
| 堆 | 程序运行时动态申请与释放的数据 |
| 数据段 | 已初始化的全局变量、静态变量 |
| BSS 段 | 未显式初始化或初始化为零的全局变量、静态变量 |
| 代码段 | 程序机器指令,即要执行的代码 |
内核保存“怎样识别、管理这个进程”的信息;用户空间主要容纳进程要执行的代码及其工作数据。动态库通常可以映射到进程地址空间;静态库则一般在链接时并入可执行文件,不能简单说两种库都以同样方式单独映射。
五、物理内存、虚拟内存、页与交换
1. 为什么引入虚拟地址
磁盘与内存:磁盘适合持久保存数据,内存访问通常快得多,但断电后普通 RAM 中的数据不能继续保留。
随后引出进程使用的虚拟地址与机器上的物理内存:程序通常操作自己的虚拟地址,系统通过页表等机制将它映射到物理页。不同进程可使用相同的虚拟地址数值,却映射到不同物理页。
可以把它理解为两层:
进程看到的虚拟地址 → 页表映射 → 物理内存中的页框这样既便于隔离不同进程,也使内核能够更灵活地安排物理内存。虚拟地址空间的大小不等于实际已分配、已驻留的物理内存大小。
2. 页与地址范围
页表级数、页大小会随架构和配置变化
3. 交换(swap)
内存中暂时不活跃的数据可转移到磁盘,需要时再取回,它可以缓解物理内存压力,但磁盘访问慢,频繁换入换出会影响性能。并非每份进程数据都会被交换;是否能够交换、何时交换由具体系统策略决定。
4. 进程隔离与共享
不同进程默认有各自的用户态虚拟地址空间,不能拿一个进程里的普通指针直接访问另一个进程的私有变量
“进程隔离”不等于物理页永远不能共享。
共享库、共享内存以及写时复制等机制,都可能让多个进程在受控条件下映射同一物理页;写权限、同步与隔离规则由系统保证。
六、内存访问权限
Linux 中在 mmap 等接口上常见的标志如下:
| 标志 | 含义 |
|---|---|
PROT_READ | 允许读 |
PROT_WRITE | 允许写 |
PROT_EXEC | 允许执行 |
PROT_NONE | 不允许访问 |
需要同时读写时,可以按位或组合:PROT_READ | PROT_WRITE
七、进程状态及转换
| 状态 | 解释 | 典型原因 |
|---|---|---|
| 就绪 | 已具备运行条件,正在等待 CPU | 新任务准备完毕,或时间片用完后再排队 |
| 运行 | 获得 CPU,正在执行指令 | 调度器选中 |
| 阻塞/睡眠 | 等待事件、I/O 或定时到期,暂不能继续执行 | sleep() |
| 挂起 | 暂时不参与正常运行,需要恢复后再进入就绪 | 内存内容换出到外存 |
| 终止 | 执行完毕、异常退出或被结束 | 生命周期结束 |
主要转换关系
新生 → 就绪 ⇄ 运行 → 终止 ↑ │ ├── 阻塞/睡眠(事件到来或定时结束后回就绪) └── 挂起(恢复后回就绪)具体说:就绪获得 CPU 后进入运行;运行用完时间片可回到就绪;运行时等待某事可进入阻塞;等待条件满足后先回就绪,而不是直接占用 CPU;进程终止后不能再继续执行。
Linux 的僵尸进程与孤儿进程:
- 僵尸:子进程已经退出,但父进程尚未通过
wait等方式回收其退出信息,仍保留少量进程记录;不是每个退出的进程都会长期变成僵尸。 - 孤儿:父进程先退出、子进程还活着;子进程会被
init或其他负责收养的进程接管。“孤儿”描述亲子关系,不是与运行/睡眠并列的调度状态。
八、为何切换进程要保存和恢复现场
若单核 CPU 让进程 A、B 交替执行,寄存器属于 CPU,并不是每个进程各有一套物理寄存器。
A 做 count 加一时,可能先把旧值读入寄存器、执行加法,再写回;如果此时切换给 B,B 改写了同一个寄存器,而 A 恢复时继续把 B 的值当成自己的中间结果,计算就会出错。
因此,调度切换需要保存 A 的执行现场,包括必要的寄存器状态及继续执行所需的控制信息;运行 B;等 A 再次获调度时,恢复 A 的现场,它才能从中断的位置继续。
A 正在运行 → 保存 A 的上下文 → B 运行 ↓A 再获 CPU ← 恢复 A 的上下文 ← B 让出 CPU分时复用 + 上下文保存/恢复,共同使多个进程能够可靠地交替使用一个 CPU。
如果这篇文章对你有帮助,欢迎分享给更多人!





