1. 进程的基本概念
在 Linux 系统中,程序 和进程是两个紧密相关但又截然不同的概念。
- 程序:存放在外存(如硬盘)中的一段代码的集合,它是静态的,只是一个文件,本身不执行任何操作。
- 进程:程序动态执行的过程,包括进程的创建、调度和消亡。进程是系统进行资源分配和调度的基本单位。
一个程序可以对应多个进程。例如,当多个用户同时登录服务器并运行同一个软件时,系统会为每个用户创建独立的进程,它们共享同一份程序代码,但各自拥有独立的运行环境。
2. 进程管理常用命令
Linux 提供了丰富的命令来查看和管理进程。
2.1 top 命令
top 命令是系统管理员最常用的工具之一,它能够实时动态地查看系统运行状态,默认按照 CPU 占用率对进程进行排序。
在 top 输出中,有几个关键字段需要了解:
- PID:进程的 ID 号,是系统用来唯一区分进程的标识(注意:不是进程的名字)。
- PPID:父进程的 ID 号,表示创建该进程的父进程。
- USER:启动该进程的用户。
2.2 ps 命令
ps 命令用于静态查看当前系统的进程快照,它不会实时刷新,只打印执行瞬间的进程信息。
bash
# 查看当前终端下的进程
ps
# 查看系统所有进程的详细信息
ps -ef
# 结合 grep 搜索特定进程(例如查找 .out 文件对应的进程)
ps -ef | grep *.out
# 查看进程的详细状态(包括 CPU、内存占用等)
ps -aux
2.3 pstree 命令
pstree 命令以树状图的形式展示进程之间的父子关系,能够直观地看到进程的继承结构。
bash
pstree
2.4 kill 命令
kill 命令用于向进程发送信号,从而终止或控制进程的运行。
bash
# 查看 kill 支持的所有信号及其编号
kill -l
# 向指定 PID 的进程发送信号(默认发送 SIGTERM,编号 15)
kill 进程PID
# 向指定 PID 的进程发送指定编号的信号
kill -信号编号 进程PID
常见信号详解
SIGSEGV(段错误)
- 信号编号:11
- 含义:程序试图访问不允许访问的内存区域。
- 结果:程序崩溃,产生 "Segmentation Fault" 错误。
SIGKILL(强制终止信号)
- 信号编号:9
- 作用:立即终止进程,强制结束。
- 特点:无法被捕获、阻塞或忽略,是最强力的终止手段。
2.5 前后台任务管理
Linux 支持将任务放到后台执行,以便用户继续在前台进行其他操作。
bash
# 在后台执行任务(& 符号表示后台运行)
./a.out &
# 查看当前终端下的后台任务列表
jobs
# 查看终端下的前后台任务
ps
# 将指定编号的后台任务调回前台执行
fg 编号
2.6 进程优先级(NI)
Linux 中进程的优先级(Nice 值)决定了进程获得 CPU 时间片的优先程度。
- Linux 规则:NI 值越小,优先级越高。
- Windows 规则:与 Linux 相反,数值越大优先级越高。
- 默认值:进程的默认优先级为 10。
bash
# 以指定优先级(例如 5)启动进程
nice -n 5 ./a.out
# 更改运行中进程的优先级
renice -n 优先级 进程PID
3. 进程的创建与虚拟内存
3.1 虚拟地址空间
每个进程都拥有独立的虚拟地址空间,这个空间的大小取决于系统的位数:
- 32 位系统:4GB(2^32)
- 64 位系统:16EB(2^64,但实际通常使用 48 位,即 256TB)
虚拟地址空间从低地址到高地址可以划分为以下几个主要区域:
text
┌─────────────────────────────┐ 高地址 (0xFFFFFFFF)
│ 内核空间 │ ← 用户态无法访问,只读
│ (Kernel Space) │
├─────────────────────────────┤
│ │
│ 栈 (Stack) │ ← 向下增长(局部变量、函数调用)
│ ↓ │
│ │
├─────────────────────────────┤
│ 空洞 │
│ (未使用) │
├─────────────────────────────┤
│ │
│ 堆 (Heap) │ ← 向上增长(malloc 分配)
│ ↑ │
│ │
├─────────────────────────────┤
│ 字符串常量区(.rodata) │ ← 字符串常量
├─────────────────────────────┤
│ 未初始化数据段 (.bss) │ ← 未初始化的全局变量(加载后清零)
├─────────────────────────────┤
│ 已初始化数据段 (.data) │ ← 已初始化的全局变量
├─────────────────────────────┤
│ 代码段 (.text) │ ← 程序代码(只读)
└─────────────────────────────┘ 低地址 (0x00000000)
关键特性:
- 进程空间是独立的,每个进程独享自己的进程空间。
- 所有进程共享 0-4G 的虚拟内存布局。
- 采用分时复用机制,同一虚拟空间内不同进程的数据互不重叠。
- MMU(内存管理单元) 负责将虚拟地址空间映射为实际的物理空间。
3.2 进程的调度
当多个进程任务需要同时进行时,操作系统需要采用一定的调度算法来分配 CPU 资源。
调度算法的发展历程:
- 先来先服务:最早期的调度方式,先到达的任务先执行。
- 短作业优先:优先执行运行时间短的作业,提高系统吞吐量。
- 高优先级调度:根据进程的优先级决定执行顺序。
- 时间片轮转(当前主流):通过中断机制,将 CPU 时间划分为一个个时间片,轮流分配给各个进程。
时间片轮转的工作流程:
text
打断当前的操作 -> 保存现场 -> 切换任务 -> 保存现场 -> 切换任务 -> 恢复现场 -> 继续任务
- 时间片:CPU 在进程任务中执行的一小段时间。
- 宏观并行,微观串行:从宏观上看多个进程在同时运行,但从微观上看 CPU 在任一时刻只执行一个进程。
未来的发展方向包括多级队列反馈 和负载均衡等更高级的调度策略。
3.3 进程的状态
Linux 进程在其生命周期中会经历多种状态:
| 状态 | 名称 | 说明 |
|---|---|---|
| R | 就绪态 | 没有被 CPU 轮转到的正在等待执行的任务 |
| R | 运行态 | 正在被 CPU 执行的任务 |
| S | 可唤醒等待态 | 由于缺少资源让程序暂时挂起的状态 |
| D | 不可唤醒等待态 | 等待过程中不可被唤醒(人为延时、条件触发等) |
| T | 停止态 | 由于系统需要让进程任务暂停(如 debug 调试) |
| Z | 僵尸态 | 进程代码已执行完,但空间没有被回收(应尽量避免) |
| X | 结束态 | 代码执行结束,空间被回收的那一时刻 |
4. 进程相关函数接口:fork()
4.1 fork 函数原型
c
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:创建一个子进程空间,调用 fork 的进程被认为是子进程的父进程。子进程是父进程的完整副本(包括代码、数据、堆、栈、文件描述符、缓存等)。
参数:缺省。
返回值:
- 成功:父进程返回子进程的 PID,子进程返回 0。
- 失败:返回 -1。
4.2 fork 的核心特性
数据一样,但相互独立!
- 子进程复制了父进程的虚拟地址空间,但拥有独立的物理内存。
- 堆区地址一样(虚拟地址相同),但物理地址内存不一样。
- 可以这样理解:同一个教室,上午、下午、晚上都有同学,但同学不一样。
写时复制(Copy-on-Write, COW)机制:
- 刚 fork() 完,子进程直接读取父进程的物理内存地址(共享),并没有复制数据。
- 只有当子进程(或父进程)试图修改数据时,才会触发复制,复制出一份独立的物理页。
- 每个进程都有一个独立的页表,存储该进程虚拟地址到物理地址的映射。
- 进程切换时,CPU 加载对应进程的页表基址(CR3 寄存器),实现地址转换的切换。
- 因此,同一虚拟地址在不同进程中指向不同的物理地址,但不会混淆。
4.3 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
执行流程示意图:
#mermaid-svg-o21PJaQ2zufwI2dv{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-o21PJaQ2zufwI2dv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-o21PJaQ2zufwI2dv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-o21PJaQ2zufwI2dv .error-icon{fill:#552222;}#mermaid-svg-o21PJaQ2zufwI2dv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-o21PJaQ2zufwI2dv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-o21PJaQ2zufwI2dv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-o21PJaQ2zufwI2dv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-o21PJaQ2zufwI2dv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-o21PJaQ2zufwI2dv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-o21PJaQ2zufwI2dv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-o21PJaQ2zufwI2dv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-o21PJaQ2zufwI2dv .marker.cross{stroke:#333333;}#mermaid-svg-o21PJaQ2zufwI2dv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-o21PJaQ2zufwI2dv p{margin:0;}#mermaid-svg-o21PJaQ2zufwI2dv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-o21PJaQ2zufwI2dv .cluster-label text{fill:#333;}#mermaid-svg-o21PJaQ2zufwI2dv .cluster-label span{color:#333;}#mermaid-svg-o21PJaQ2zufwI2dv .cluster-label span p{background-color:transparent;}#mermaid-svg-o21PJaQ2zufwI2dv .label text,#mermaid-svg-o21PJaQ2zufwI2dv span{fill:#333;color:#333;}#mermaid-svg-o21PJaQ2zufwI2dv .node rect,#mermaid-svg-o21PJaQ2zufwI2dv .node circle,#mermaid-svg-o21PJaQ2zufwI2dv .node ellipse,#mermaid-svg-o21PJaQ2zufwI2dv .node polygon,#mermaid-svg-o21PJaQ2zufwI2dv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-o21PJaQ2zufwI2dv .rough-node .label text,#mermaid-svg-o21PJaQ2zufwI2dv .node .label text,#mermaid-svg-o21PJaQ2zufwI2dv .image-shape .label,#mermaid-svg-o21PJaQ2zufwI2dv .icon-shape .label{text-anchor:middle;}#mermaid-svg-o21PJaQ2zufwI2dv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-o21PJaQ2zufwI2dv .rough-node .label,#mermaid-svg-o21PJaQ2zufwI2dv .node .label,#mermaid-svg-o21PJaQ2zufwI2dv .image-shape .label,#mermaid-svg-o21PJaQ2zufwI2dv .icon-shape .label{text-align:center;}#mermaid-svg-o21PJaQ2zufwI2dv .node.clickable{cursor:pointer;}#mermaid-svg-o21PJaQ2zufwI2dv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-o21PJaQ2zufwI2dv .arrowheadPath{fill:#333333;}#mermaid-svg-o21PJaQ2zufwI2dv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-o21PJaQ2zufwI2dv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-o21PJaQ2zufwI2dv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o21PJaQ2zufwI2dv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-o21PJaQ2zufwI2dv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o21PJaQ2zufwI2dv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-o21PJaQ2zufwI2dv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-o21PJaQ2zufwI2dv .cluster text{fill:#333;}#mermaid-svg-o21PJaQ2zufwI2dv .cluster span{color:#333;}#mermaid-svg-o21PJaQ2zufwI2dv 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-o21PJaQ2zufwI2dv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-o21PJaQ2zufwI2dv rect.text{fill:none;stroke-width:0;}#mermaid-svg-o21PJaQ2zufwI2dv .icon-shape,#mermaid-svg-o21PJaQ2zufwI2dv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o21PJaQ2zufwI2dv .icon-shape p,#mermaid-svg-o21PJaQ2zufwI2dv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-o21PJaQ2zufwI2dv .icon-shape .label rect,#mermaid-svg-o21PJaQ2zufwI2dv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o21PJaQ2zufwI2dv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-o21PJaQ2zufwI2dv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-o21PJaQ2zufwI2dv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 返回 0
返回子进程 PID
父进程调用 fork()
fork() 返回值判断
子进程执行
getpid() 获取自身 PID
父进程执行
getpid() 获取自身 PID
子进程输出: PID = 1235, PPID = 1234
父进程输出: PID = 1234, 子进程 PID = 1235
父子进程从 fork() 返回后各自独立运行,互不干扰。
5. 总结
本文详细介绍了 Linux 进程管理的核心知识:
- 进程概念:程序是静态的代码集合,进程是动态的执行过程。
- 进程命令 :掌握
top、ps、pstree、kill等常用命令,以及前后台任务管理和进程优先级调整。 - 虚拟内存:理解进程独立的虚拟地址空间布局和 MMU 地址映射机制。
- 进程调度:了解从先来先服务到时间片轮转的调度算法演进。
- 进程状态:熟悉就绪、运行、等待、停止、僵尸、结束等七种状态。
- fork 函数:掌握进程创建的核心接口,理解写时复制(COW)机制。
掌握这些知识,是深入理解 Linux 系统编程和多进程开发的基础。