1. 进程概念
1.1 程序与进程
- 程序:存放在外存中的一段代码的集合,是静态的,只是一个文件。
- 进程:程序动态执行的过程,包括进程的创建、调度、消亡。
一个程序可以对应多个进程,例如各种软件支持多用户同时登录。
1.2 进程相关命令
| 命令 | 作用 |
|---|---|
top |
根据 CPU 占用率实时查看系统运行的进程 |
ps -ef |
打印当前进程信息(不实时刷新) |
| `ps -ef | grep *.out` |
pstree |
查看进程关系树 |
ps -aux |
查看进程状态 |
kill |
杀死进程任务(发送信号) |
kill -l |
查看 kill 支持的信号配置 |
kill -信号编号 进程PID |
向指定进程发送指定信号 |
进程关键字段:
- PID:进程的 ID 号,唯一区分进程的标识(不是名字)。
- PPID:父进程的 ID 号。
- USER:进程发起所使用的用户。
1.3 常用信号
SIGSEGV(段错误)
- 信号编号:
11 - 含义:程序试图访问不允许访问的内存区域。
- 结果:程序崩溃,产生
Segmentation Fault错误。
SIGKILL(强制终止信号)
- 信号编号:
9 - 作用:立即终止进程,强制结束。
- 特点:无法被捕获、阻塞或忽略。
1.4 前后台任务管理
| 命令 | 作用 |
|---|---|
./a.out & |
后台执行任务 |
jobs |
查看后台的任务 |
ps |
查看终端下的前后台任务 |
fg 编号 |
将后台任务放到前台 |
1.5 进程优先级(NI)
- Linux 中值越小优先级越高,Windows 中相反。
- 默认优先级为
10。
| 命令 | 作用 |
|---|---|
nice -n 10 ./a.out |
以指定优先级开启进程 |
renice -n 优先级 进程PID |
更改运行中进程的优先级 |
2. 进程的创建
2.1 虚拟地址空间
- 32 位系统:4GB(2^32)
- 64 位系统:16EB(2^64,实际通常用 48 位 = 256TB)
进程空间划分:
| 段 | 说明 |
|---|---|
| 文本段(.text) | 代码以及指令(只读) |
| 已初始化数据段(.data) | 已初始化的全局变量 |
| 未初始化数据段(.bss) | 未初始化的全局变量(加载后清零) |
| 字符串常量区(.rodata) | 字符串常量 |
| 堆(Heap) | 向上增长,malloc 分配 |
| 栈(Stack) | 向下增长,局部变量、函数调用 |
| 内核空间 | 用户态无法访问,只读 |
进程空间特性:
- 进程空间是独立的,每个进程独享进程空间。
- 所有进程共享 0-4G 虚拟内存。
- 分时复用,同一虚拟空间不同进程数据不重叠。
- MMU 将虚拟地址空间映射为实际的物理空间。
text
┌─────────────────────────────┐ 高地址 (0xFFFFFFFF)
│ 内核空间 │ ← 用户态无法访问
│ (Kernel Space) │ 只读
├─────────────────────────────┤
│ │
│ 栈 (Stack) │ ← 向下增长
│ ↓ │ 局部变量、函数调用
│ │
├─────────────────────────────┤
│ 空洞 │
│ (未使用) │
├─────────────────────────────┤
│ │
│ 堆 (Heap) │ ← 向上增长
│ ↑ │ malloc 分配
│ │
├─────────────────────────────┤
│ 字符串常量区(.rodata) │ ← 字符串常量
├─────────────────────────────┤
│ 未初始化数据段 (.bss) │ ← 未初始化的全局变量
├─────────────────────────────┤
│ 已初始化数据段 (.data) │ ← 初始化的全局变量
├─────────────────────────────┤
│ 代码段 (.text) │ ← 程序代码(只读)
│ │
└─────────────────────────────┘ 低地址 (0x00000000)
3. 进程的调度
3.1 调度算法演进
- 先来先服务:以前先来先执行,后来后执行。
- 短作业优先:后来短作业优先调度。
- 高优先级调度:后来高优先级调度。
- 时间片轮转(当前):中断当前操作 → 保存现场 → 切换任务 → 恢复现场 → 继续任务。
- 多级队列反馈:未来方向。
- 负载均衡:未来方向。
时间片 :CPU 在进程任务中执行的一小段时间。
宏观并行,微观串行。
3.2 进程状态
| 状态 | 说明 |
|---|---|
| 就绪态(R) | 没有被 CPU 轮转到的正在等待执行的任务 |
| 运行态(R) | 正在被 CPU 执行的任务 |
| 可唤醒等待态(S) | 由于缺少资源让程序暂时挂起的状态 |
| 不可唤醒等待态(D) | 等待过程中不可被唤醒(人为延时、条件触发等) |
| 停止态(T) | 系统需要让进程暂停(如 debug 调试) |
| 僵尸态(Z) | 进程代码已执行完,空间未被回收(应尽量避免) |
| 结束态(X) | 代码执行结束,空间被回收的那一时刻 |
4. 进程相关函数接口
4.1 fork 函数
c
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:
- 创建一个子进程空间,调用进程被认为是子进程的父进程。
- 子进程是父进程的完整副本(代码、数据、堆、栈、文件描述符、缓存等)。
- 数据一样,但相互独立。
- 父子进程从
fork()返回后各自独立运行。
参数: 缺省
返回值:
- 成功:父进程返回子进程的 PID,子进程返回 0。
- 失败:返回 -1。
4.2 fork 示例代码
c
#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;
}
运行结果示例:
text
父进程: PID = 1234, 子进程 PID = 1235
子进程: PID = 1235, 父进程 PID = 1234
4.3 写时复制(Copy-on-Write, COW)
- 每个进程都有一个独立的页表,存储该进程虚拟地址到物理地址的映射。
- 进程切换时,CPU 加载对应进程的页表基址(CR3 寄存器),实现地址转换的切换。
- 同一虚拟地址在不同进程中指向不同的物理地址,但不会混淆。
- 父地址与子地址相同,当子进程复制父进程虚拟地址后,物理地址权限更新为只读。
- 子进程按照虚拟地址写入时触发 COW,复制新的物理地址给子进程的独立页表。
- 子进程与父进程的地址一样,但指向不同的物理地址。
关键理解 :刚 fork() 完,子进程直接读取父进程的物理内存地址(共享的),并没有复制数据;只有当子进程(或父进程)试图修改时,才会触发复制,复制出一份独立的物理页。
#mermaid-svg-StK4roa51kIrM3t9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-StK4roa51kIrM3t9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-StK4roa51kIrM3t9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-StK4roa51kIrM3t9 .error-icon{fill:#552222;}#mermaid-svg-StK4roa51kIrM3t9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-StK4roa51kIrM3t9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-StK4roa51kIrM3t9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-StK4roa51kIrM3t9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-StK4roa51kIrM3t9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-StK4roa51kIrM3t9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-StK4roa51kIrM3t9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-StK4roa51kIrM3t9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-StK4roa51kIrM3t9 .marker.cross{stroke:#333333;}#mermaid-svg-StK4roa51kIrM3t9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-StK4roa51kIrM3t9 p{margin:0;}#mermaid-svg-StK4roa51kIrM3t9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-StK4roa51kIrM3t9 .cluster-label text{fill:#333;}#mermaid-svg-StK4roa51kIrM3t9 .cluster-label span{color:#333;}#mermaid-svg-StK4roa51kIrM3t9 .cluster-label span p{background-color:transparent;}#mermaid-svg-StK4roa51kIrM3t9 .label text,#mermaid-svg-StK4roa51kIrM3t9 span{fill:#333;color:#333;}#mermaid-svg-StK4roa51kIrM3t9 .node rect,#mermaid-svg-StK4roa51kIrM3t9 .node circle,#mermaid-svg-StK4roa51kIrM3t9 .node ellipse,#mermaid-svg-StK4roa51kIrM3t9 .node polygon,#mermaid-svg-StK4roa51kIrM3t9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-StK4roa51kIrM3t9 .rough-node .label text,#mermaid-svg-StK4roa51kIrM3t9 .node .label text,#mermaid-svg-StK4roa51kIrM3t9 .image-shape .label,#mermaid-svg-StK4roa51kIrM3t9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-StK4roa51kIrM3t9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-StK4roa51kIrM3t9 .rough-node .label,#mermaid-svg-StK4roa51kIrM3t9 .node .label,#mermaid-svg-StK4roa51kIrM3t9 .image-shape .label,#mermaid-svg-StK4roa51kIrM3t9 .icon-shape .label{text-align:center;}#mermaid-svg-StK4roa51kIrM3t9 .node.clickable{cursor:pointer;}#mermaid-svg-StK4roa51kIrM3t9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-StK4roa51kIrM3t9 .arrowheadPath{fill:#333333;}#mermaid-svg-StK4roa51kIrM3t9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-StK4roa51kIrM3t9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-StK4roa51kIrM3t9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-StK4roa51kIrM3t9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-StK4roa51kIrM3t9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-StK4roa51kIrM3t9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-StK4roa51kIrM3t9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-StK4roa51kIrM3t9 .cluster text{fill:#333;}#mermaid-svg-StK4roa51kIrM3t9 .cluster span{color:#333;}#mermaid-svg-StK4roa51kIrM3t9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-StK4roa51kIrM3t9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-StK4roa51kIrM3t9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-StK4roa51kIrM3t9 .icon-shape,#mermaid-svg-StK4roa51kIrM3t9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-StK4roa51kIrM3t9 .icon-shape p,#mermaid-svg-StK4roa51kIrM3t9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-StK4roa51kIrM3t9 .icon-shape .label rect,#mermaid-svg-StK4roa51kIrM3t9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-StK4roa51kIrM3t9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-StK4roa51kIrM3t9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-StK4roa51kIrM3t9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
父进程调用 fork()
创建子进程空间
子进程共享父进程物理内存(只读)
是否有进程尝试写入?
父子进程继续共享物理页
触发 COW,复制独立物理页
子进程拥有独立物理内存
父子进程独立运行
5. 总结
- 进程是程序动态执行的过程,拥有独立的虚拟地址空间。
- Linux 通过时间片轮转实现宏观并行、微观串行。
fork()创建子进程时采用写时复制技术,兼顾效率与隔离。- 掌握
ps、top、kill等命令是日常进程管理的基础。