目录
[1. 进程与程序的本质区别](#1. 进程与程序的本质区别)
[2. 进程的内存空间布局](#2. 进程的内存空间布局)
[3. 虚拟地址与物理地址的映射](#3. 虚拟地址与物理地址的映射)
[4. 多进程的调度机制](#4. 多进程的调度机制)
[5. 进程相关的命令](#5. 进程相关的命令)
[1. top 命令](#1. top 命令)
[2. ps -ef 命令](#2. ps -ef 命令)
[3. ps -aux 命令](#3. ps -aux 命令)
[4. 后台执行进程](#4. 后台执行进程)
[5. jobs 命令](#5. jobs 命令)
[6. fg 命令](#6. fg 命令)
[7. nice/renice 命令](#7. nice/renice 命令)
[8. kill/killall 命令](#8. kill/killall 命令)
[6. 进程的状态](#6. 进程的状态)
[3. 进程相关函数接口](#3. 进程相关函数接口)
[1. fork 函数](#1. fork 函数)
[2. getpid 和 getppid 函数](#2. getpid 和 getppid 函数)
[3. exit 与 _exit 函数](#3. exit 与 _exit 函数)
[4. 进程回收:wait / waitpid](#4. 进程回收:wait / waitpid)
[(1) wait 函数](#(1) wait 函数)
[(2) waitpid 函数(扩展)](#(2) waitpid 函数(扩展))
[4. 进程消亡](#4. 进程消亡)
[1. 孤儿进程](#1. 孤儿进程)
[2. 僵尸进程](#2. 僵尸进程)
[1. 线程与进程的核心区别](#1. 线程与进程的核心区别)
[2. 线程的创建与终止](#2. 线程的创建与终止)
[3. 线程同步机制](#3. 线程同步机制)
在操作系统的世界里,进程与线程是实现多任务处理的核心机制。无论是服务器后台的并发处理,还是嵌入式设备的任务调度,都离不开对进程和线程的深入理解。本文将基于 Linux 系统,详细解析进程的本质、生命周期及相关操作,并补充线程的核心知识,帮助开发者构建清晰的多任务编程认知。
一、进程:程序的动态执行实体
1. 进程与程序的本质区别
进程是程序的动态执行过程,包含创建、调度、消亡的完整生命周期。
《进程像一个教室有不同的学生上课,每个座位号可以对应不同的学生》
而程序则是存放在外存中的一段静态数据集合,是进程运行的 "蓝图"。
打个比方,程序就像乐谱,进程则是按照乐谱进行的演奏过程 ------ 同一乐谱可以被多个乐队同时演奏(多进程运行同一程序),每个演奏过程都是独立的进程。
2. 进程的内存空间布局
当进程运行时,操作系统会为其分配 0-4G 的虚拟内存空间,分为用户空间 和内核空间(内核空间不允许用户直接访问)。
- 进程空间:用户空间 + 内核空间(不允许用户访问)
- 用户空间:文本段(文本区) + 数据段(数据区) + 系统数据段(堆区、栈区)
用户空间又细分为以下几个部分:
- 文本段 :存放程序的代码和指令,是只读区域,确保代码不会被意外修改。
- 数据段 :包含字符串常量、已初始化的全局 / 静态变量、未初始化的全局 / 静态变量。
- 其特点是
- 编译时开辟空间,
- 程序结束时回收,
- 且未初始化的变量会被默认设为 0 值。
- 系统数据段 :包括堆区和栈区
- 堆区 :
- 由开发者通过
malloc
手动申请,free
释放,- 需注意避免内存泄露。
- 栈区 :自动存放局部变量 和函数运行状态 ,
- 变量超出作用域后自动回收,
- 执行到变量定义开辟内存空间
- 未初始化的变量值为随机值。

3. 虚拟地址与物理地址的映射

多个进程空间在操作系统中存储时,空间是独立的(物理地址是独立的)
多个进程在操作系统中共用同一个虚拟内存空间(虚拟地址是共享的)

- 虚拟地址:用户可见的地址,代表程序可寻址的范围,所有进程共享相同的虚拟地址空间。
- 物理地址:内存硬件实际存储数据的地址,每个进程的物理地址是独立的。
- MMU(内存映射单元):负责将虚拟地址转换为物理地址,实现进程对内存的安全访问。
4. 多进程的调度机制
多进程能 "同时" 运行,本质是宏观并行、微观串行:
- 宏观上,一个 CPU 似乎在同时处理多个进程;
- 微观上,CPU 通过调度算法在进程间高速切换,实现多任务并发。
常见的调度算法包括:
- 先来先执行:按进程到达顺序调度;
- 高优先级调度:优先级高的进程优先执行;
- 时间片轮转:每个进程分配固定时间片,超时后切换到下一个进程。
5. 进程相关的命令
1. top
命令
- 示例 :
top
- 功能:实时查看进程的 CPU、内存占用等信息,动态刷新。
- 字段说明 :
PID
:进程唯一 IDUSER
:进程所属用户PR NI
:优先级(PR
实时优先级,NI
nice 值)VIRT
:虚拟内存占用(含交换区)RES
:物理内存占用(常驻内存)SHR
:共享内存占用S
:进程状态(R
/S
/D
等)%CPU
:CPU 使用率%MEM
:内存使用率TIME+
:累计运行时间COMMAND
:进程命令
- 退出 :按
q
键
2. ps -ef
命令
- 示例 :
ps -ef
- 功能:查看某一时刻的全量进程信息(系统级视角)。
- 字段说明 :
UID
:进程所属用户 IDPID
:进程 IDPPID
:父进程 IDC
:CPU 利用率(简化值)STIME
:启动时间TTY
:关联终端(?
表示无终端)TIME
:累计 CPU 时间CMD
:进程命令
- 过滤用法 :
ps -ef | grep 进程名
(筛选目标进程)查找与进程名对应的进程信息
3. ps -aux
命令
- 示例 :
ps -aux
- 功能:查看某一时刻的全量进程信息(用户级视角,含资源细节)。
- 字段说明 :
USER
:进程所属用户PID
:进程 ID%CPU
:CPU 使用率%MEM
:内存使用率VSZ
:虚拟内存大小(KB)RSS
:常驻内存大小(KB)TTY
:关联终端STAT
:进程状态(如R
/S
/Z
等)START
:启动时间TIME
:累计 CPU 时间COMMAND
:进程命令
4. 后台执行进程
- 示例 :
./a.out &
- 功能 :让进程在后台运行(
&
符号标记后台任务)
5. jobs
命令
- 示例 :
jobs
- 功能:查看当前终端的后台进程列表
6. fg
命令
- 示例 :
fg %1
(%1
为jobs
查看的任务编号) - 功能:将后台进程调至前台运行
7. nice/renice
命令
- 示例 :
- 启动时设优先级:
nice -n 10 ./a.out
(优先级10
) - 修改已有进程:
renice -n 5 -p 1234
(PID1234
优先级改为5
)
- 启动时设优先级:
- 功能:设置 / 修改进程优先级
- 注意 :
- 优先级范围:
-20 ~ 20
(值越小,优先级越高,需权限)
- 优先级范围:
8. kill/killall
命令
- 示例 :
- 杀进程:
kill -9 1234
(-9
为强制终止信号SIGKILL
) - 杀同名进程:
killall -TERM nginx
(-TERM
为终止信号)
- 杀进程:
- 功能:通过信号终止进程
6. 进程的状态
进程状态 | 标识 | 含义描述 |
---|---|---|
就绪态 / 运行态 | R |
进程在 CPU 调度队列中,随时可执行(或正在执行) |
可唤醒等待态 | S |
等待资源(如 IO),资源就绪后可被唤醒;等待中可被信号打断 |
不可唤醒等待态 | D |
等待资源(如磁盘 IO),资源就绪后可被唤醒;等待中不可被信号打断(强阻塞) |
停止态 | T |
进程被暂停(如 Ctrl+Z ),需手动恢复 |
僵尸态 | Z |
进程已终止,但父进程未回收其 PCB,残留退出状态等信息 |
结束态 | X |
进程已终止,且内核已回收所有资源,无实际实体 |
3. 进程相关函数接口
1. fork
函数
- 原型 :
pid_t fork(void);
- 功能:创建子进程,父进程复制自身给子进程(写时复制)。
- 返回值 :
- 父进程:返回 子进程 PID (
>0
)。 - 子进程:返回 0。
- 出错:返回
-1
。
- 父进程:返回 子进程 PID (
- 核心特性 :
- 子进程拷贝父进程的代码段、数据段、系统数据(初始共享,修改时拷贝)。
- 父子进程空间独立,变量修改互不影响。
注意:
子进程拷贝父进程文本段、数据段、系统数据段
父进程与子进程空间独立,同一份代码中的变量和数据都会在父子进程中各有一份,父子进程
修改自己空间的数据不会影响对方的空间
进程的PID不一样
fork的返回值不一样,父进程中返回子进程的PID,子进程中返回0
PID:一定是 > 0

cpp
#include "../head.h"
int main(void)
{
pid_t pid;
pid = fork();
if(-1 == pid)
{
perror("fail to fork");
return -1;
}
if(0 == pid)
{
printf("====子进程=====\n");
printf("我是子进程\n");
printf("PID:%d PPID:%d\n",getpid(),getppid());
printf("====end=====\n\n");
}
if(pid > 0) //进程的pid一定是个 大于0 的数
{
printf("=====父进程====\n");
printf("我是父进程\n");
printf("自己PID: %d 子进程pid: %d\n",getpid(),pid);
printf("====end=====\n\n");
}
printf("hello world\n\n");
// while(1)
// {
// }
return 0;
}

cpp
#include "../head.h"
int main(void)
{
pid_t pid;
pid_t xpid;
pid = fork();
if(-1 == pid)
{
perror("fail to fork");
return -1;
}
if(0 == pid)
{
printf("1子进程\n");
printf("PID: %d PPID: %d\n",getpid(),getppid());
printf("======end=======\n");
}
else if(pid > 0)
{
printf("=====1======\n\n");
//(文本段)
printf("how are you"); //会一起在 2子进程里打印
//因为stdout 是标准io 遇到\n缓存
//子进程 拷贝文本段,数据段,系统数据段
//父子进程 "空间独立" ,同一份的代码和数据会在父子里面给有一份
//父子进程修改自己空间的数据不会互相影响
xpid = fork();
//从创建的时候开始 子进程2 就开始执行以下的程序
printf("======2=====\n\n");
if(-1 == xpid)
{
perror("fail to fork");
return -1;
}
if(0 == xpid)
{
printf("2子进程\n");
printf("PID: %d PPID: %d\n",getpid(),getppid());
printf("======end=======\n");
}
else if(xpid > 0)
{
printf("父进程\n");
printf("PID: %d 1子PID: %d ",getpid(),pid);
printf("2子PID: %d\n",xpid);
printf("======end=======\n");
}
while(1)
{
//不让父进程结束,防止2子进程成为孤儿进程
}
}
return 0;
}

- 子进程拷贝父进程的代码段、数据段、系统数据(初始共享,修改时拷贝)
cpp
#include "../head.h"
int main(void)
{
pid_t pid;
printf("hello world");
pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
printf("thank you!\n");
}
else if (pid > 0)
{
printf("how are you\n");
}
return 0;
}
- 父子进程空间独立,变量修改互不影响。
cpp
#include "../head.h"
int main(void)
{
pid_t pid;
int *pnum = NULL;
pnum = malloc(4);
if (NULL == pnum)
{
perror("fail to malloc");
return -1;
}
pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
*pnum = 100;
printf("pnum = %p, *pnum = %d\n", pnum, *pnum);
}
else if (pid > 0)
{
sleep(1);
printf("pnum = %p, *pnum = %d\n", pnum, *pnum);
}
return 0;
}
2. getpid
和 getppid
函数
getpid
:- 原型:
pid_t getpid(void);
- 功能:获取当前进程的 PID。
- 原型:
getppid
:- 原型:
pid_t getppid(void);
- 功能:获取当前进程父进程的 PID。
- 原型:
3. exit
与 _exit
函数
exit
:- 原型:
void exit(int status);
- 功能:终止进程,刷新缓冲区(如标准输出缓存)后退出。
- 原型:
_exit
:- 原型:
void _exit(int status);
- 功能:终止进程,不刷新缓冲区,直接退出。
- 原型:
- 差异 :
- 主函数中
return
等价于exit
(触发缓冲区刷新)。 - 非主函数中,
exit
终止整个进程,return
仅终止当前函数。
- 主函数中
cpp
#include "../head.h"
int main(void)
{
pid_t pid;
pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
while (1)
{
printf("PID:%d PPID:%d\n", getpid(), getppid());
sleep(1);
}
}
else if (pid > 0)
{
printf("父进程即将退出!\n");
printf("父进程即将退出!"); //没有 \n 会停滞在缓存区
_exit(0); //不会刷新缓存区所以不会打印
}
return 0;
}
- ++stdout 是标准IO 会缓存++


4. 进程回收:wait
/ waitpid
(1) wait
函数
- 原型 :
pid_t wait(int *wstatus);
- 功能 :阻塞等待任意子进程终止,回收其资源。
- 参数 :
wstatus
存储子进程退出状态(可通过WEXITSTATUS
等宏解析)。 - 返回值 :
- 成功:返回已回收子进程的 PID。
- 失败:返回
-1
(如无子进程)。
cpp
#include "../head.h"
int main(void)
{
pid_t pid;
pid_t ret;
pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
sleep(5);
printf("子进程即将退出!\n");
exit(0);
}
else if (pid > 0)
{
ret = wait(NULL);
printf("回收到子进程(PID:%d)空间\n", ret);
while (1)
{
}
}
return 0;
}
(2) waitpid
函数(扩展)
- 原型 :
pid_t waitpid(pid_t pid, int *wstatus, int options);
- 功能:指定回收目标子进程(更灵活,支持非阻塞)。
- 参数 :
pid
:目标 PID(-1
表示任意子进程,同wait
)。wstatus
:存储退出状态。options
:如WNOHANG
(非阻塞模式,子进程未结束时返回0
)。
- 返回值 :
- 成功回收:返回子进程 PID。
- 非阻塞且子进程未结束:返回
0
。 - 失败:返回
-1
。
4. 进程消亡
1. 孤儿进程
- 产生 :父进程先终止,子进程被
init
进程(系统初始化进程)收养。 - 特性 :
init
会负责回收孤儿进程的资源,因此不会产生僵尸进程。
2. 僵尸进程
- 特性:是每个进程结束必然会经历的阶段
- 产生原因 :
- 子进程结束后,父进程没有回收子进程空间,导致进程执行结束但资源(如 PCB )依然被占用的状态,称为僵尸进程
- 如何避免产生僵尸进程?
- 让父进程先结束:子进程成为孤儿进程,被
init
进程收养;子进程再结束时,init
进程会回收其空间。 - 父进程主动回收:子进程结束后,父进程调用
wait
/waitpid
回收子进程空间。
- 让父进程先结束:子进程成为孤儿进程,被
cpp
#include "../head.h"
int main(void)
{
pid_t pid;
pid_t ret;
pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
sleep(5);
printf("子进程即将退出!\n");
exit(0);
}
else if (pid > 0)
{
ret = wait(NULL);
printf("回收到子进程(PID:%d)空间\n", ret);
while (1)
{
}
}
return 0;
}
二、线程:进程内的轻量级执行单元
进程是资源分配的基本单位,而线程是CPU 调度的基本单位。一个进程可以包含多个线程,线程共享进程的内存空间(文本段、数据段、堆区等),但拥有独立的栈区和寄存器。
1. 线程与进程的核心区别
维度 | 进程 | 线程 |
---|---|---|
资源分配 | 独立地址空间,资源独占 | 共享进程资源 |
切换开销 | 大(需切换地址空间) | 小(仅切换栈和寄存器) |
通信方式 | 需 IPC(如管道、信号量) | 直接访问共享内存 |
独立性 | 高(一个崩溃不影响其他) | 低(共享资源,相互影响) |
2. 线程的创建与终止
- 创建线程 :通过
pthread_create()
函数,需指定线程执行的函数、参数等。 - 终止线程 :
pthread_exit()
主动结束线程,pthread_cancel()
可取消其他线程。 - 等待线程 :
pthread_join()
阻塞等待线程结束并回收资源,类似进程的wait()
。
3. 线程同步机制
由于线程共享资源,需通过同步机制避免数据竞争:
- 互斥锁(
pthread_mutex_t
):确保同一时间只有一个线程访问临界资源(如全局变量)。 - 条件变量(
pthread_cond_t
):让线程等待特定条件满足后再执行(如 "队列非空" 时消费数据)。 - 信号量:控制同时访问资源的线程数量(如限制 5 个线程同时读写文件)。
三、总结
进程是操作系统资源分配的基本单位,拥有独立的内存空间和完整的生命周期;线程是轻量级的执行单元,共享进程资源,适合高并发场景。理解进程的内存布局、调度机制和状态流转,掌握线程的同步方法,是编写高效 Linux 多任务程序的基础。