本篇为 Linux 系统开发学习笔记,梳理进程核心概念、实操命令、虚拟地址空间、进程创建、调度机制与 C 语言 API 实战,适合 C/C++ 后端、Linux 开发人员阅读。
1. 进程基础概念
1.1 程序与进程
- 程序 :存储在磁盘外存上的代码文件,属于静态的指令集合。
- 进程:程序运行起来的动态实例,包含创建、调度、销毁完整生命周期。
一个程序可以对应多个进程,例如服务程序支持多用户访问,会同时启动多个进程实例。
1.2 进程常用操作命令
表格
| 命令 | 功能说明 | |
|---|---|---|
top |
实时监控系统进程,默认按 CPU 使用率排序 | |
ps -ef |
输出全部进程信息,非实时刷新 | |
| `ps -ef | grep *.out` | 过滤查找指定进程 |
pstree |
树形展示进程父子继承关系 | |
ps -aux |
查看进程 CPU、内存、完整运行状态 | |
kill |
向进程发送信号,用于终止任务 | |
kill -l |
查看全部 kill 信号列表 | |
kill -信号编号 PID |
给指定进程发送对应信号 |
关键字段解析
PID:进程唯一标识符,区分进程,不是进程名称PPID:父进程 ID,代表当前进程由哪个进程创建USER:启动该进程的操作系统用户
1.3 常用信号
- SIGSEGV(编号 11)段错误 程序访问非法内存地址,直接崩溃,报错:
Segmentation Fault。 - SIGKILL(编号 9)强制终止 立刻强制杀掉进程;无法捕获、阻塞、忽略,属于最高权限信号。
✨实操建议:优先使用
kill PID(默认 15 号 SIGTERM,优雅退出);进程卡死无响应时,再使用kill -9 PID强制杀死。
1.4 前后台任务管理
表格
| 命令 | 作用 |
|---|---|
./a.out & |
将程序放到后台运行 |
jobs |
查看当前终端后台任务列表 |
fg 任务编号 |
把后台任务切回前台执行 |
1.5 进程优先级 NI
Linux 规则:NI 数值越小,优先级越高,和 Windows 规则相反。 进程默认 NI 值为 10。
表格
| 命令 | 作用 |
|---|---|
nice -n 10 ./a.out |
启动程序时指定优先级 |
renice -n 优先级 PID |
修改正在运行进程的优先级 |
2. 进程创建
2.1 虚拟地址空间
- 32 位系统:虚拟地址总空间 4GB
- 64 位系统:理论上限
2^64=16EB,内核实际使用 48 位地址,可用最大 256TB
虚拟地址空间分段
表格
| 段 | 说明 |
|---|---|
.text文本段 |
机器指令代码,只读属性 |
.data已初始化数据段 |
已经赋值的全局变量 |
.bss未初始化数据段 |
未初始化全局变量,程序加载后自动清零 |
.rodata常量区 |
字符串常量,只读 |
| Heap 堆 | 向上增长,malloc动态申请内存 |
| Stack 栈 | 向下增长,存放局部变量、函数调用栈帧 |
| 内核空间 | 用户态程序无权访问,归属操作系统内核 |
核心要点:
- 每个进程拥有独立虚拟地址空间;
- MMU 内存管理单元完成虚拟地址到物理地址映射;
- 不同进程即便虚拟地址相同,映射到的物理内存也互不干扰。
┌─────────────────────────────┐ 高地址 (0xFFFFFFFF)
│ 内核空间 │ ← 用户态无法访问
│ (Kernel Space) │ 只读
├─────────────────────────────┤
│ 栈 (Stack) │ ← 向下增长
│ ↓ │ 局部变量、函数调用
├─────────────────────────────┤
│ 空洞(未使用) │
├─────────────────────────────┤
│ 堆 (Heap) │ ← 向上增长
│ ↑ │ malloc分配
├─────────────────────────────┤
│ 字符串常量区(.rodata) │ ← 字符串常量
├─────────────────────────────┤
│ 未初始化数据段(.bss) │ ← 未初始化全局变量
├─────────────────────────────┤
│ 已初始化数据段(.data) │ ← 初始化全局变量
├─────────────────────────────┤
│ 代码段(.text) │ ← 程序代码(只读)
└─────────────────────────────┘ 低地址 (0x00000000)
3. 进程调度
3.1 调度算法演变
- 先来先服务 FCFS:按照任务到达顺序依次执行
- 短作业优先 SJF:耗时短的任务优先调度
- 高优先级调度:优先级高的任务优先运行
- 时间片轮转(Linux 当前核心调度)
执行流程:中断当前进程 → 保存上下文现场 → 切换进程 → 恢复现场继续运行 宏观上多个程序同时运行,微观 CPU 同一时刻只能执行一个进程。
- 多级反馈队列、负载均衡:现代 Linux 调度器优化方向
3.2 进程状态
表格
| 状态标识 | 状态名称 | 含义 |
|---|---|---|
| R | 就绪 / 运行态 | 就绪:等待 CPU 调度;运行:正在 CPU 执行 |
| S | 可唤醒等待态 | 等待 IO 或资源,收到信号可被唤醒 |
| D | 不可唤醒等待态 | 深度阻塞,信号无法打断,常见磁盘 IO 等待 |
| T | 停止态 | 进程被暂停,例如 gdb 调试场景 |
| Z | 僵尸态 | 进程执行完毕,但父进程没有回收资源,残留僵尸进程 |
| X | 结束态 | 进程全部资源回收,彻底销毁 |
⚠️注意:僵尸进程 Z,子进程结束父进程未调用 wait 回收产生,开发中需要尽量规避。
4. 进程系统调用接口
4.1 fork () 函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:创建子进程,调用 fork 的进程为父进程。
- 子进程会复制父进程全部内容:代码、数据、堆栈、文件描述符;
- fork 返回之后,父子进程相互独立运行。
返回值
- 成功:父进程返回子进程 PID ;子进程返回0
- 失败:返回
-1
4.2 fork 示例代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
return 1;
}
if (pid == 0) {
// 子进程逻辑
printf("子进程: PID = %d, 父进程 PID = %d\n",
getpid(), getppid());
} else {
// 父进程逻辑
printf("父进程: PID = %d, 子进程 PID = %d\n",
getpid(), pid);
}
return 0;
}
运行输出示例:
父进程: PID = 1234, 子进程 PID = 1235
子进程: PID = 1235, 父进程 PID = 1234
4.3 写时复制 COW (Copy‑on‑Write)
fork 不会直接拷贝全部物理内存,是 Linux 重要优化机制。
- fork 创建子进程,父子进程共享同一物理内存页,页面标记为只读;
- 父或子进程尝试修改内存,触发缺页异常;
- 内核复制一份全新物理页给写入进程,更新页表映射,完成内存隔离。
关键点:fork 刚完成,父子共享物理内存;只有发生写操作,才会复制内存,大幅提升 fork 执行效率。
简易逻辑流程:
父进程调用fork()
↓
创建子进程虚拟地址空间
↓
父子共享父进程物理内存(只读权限)
↓
是否有进程执行写操作?
├─否 → 继续共享物理页
└─是 → 触发COW,复制独立物理页,父子内存隔离
5. 总结
- 进程是程序动态运行实例,拥有独立虚拟地址空间;
- Linux 依靠时间片轮转调度,实现宏观并行、微观串行;
fork()创建子进程,借助写时复制 COW 兼顾性能与内存隔离;- 日常运维熟练使用
ps、top、kill、jobs等命令; - 开发中要重点防范僵尸进程、D 状态阻塞进程这类异常。