Linux 进程详解:从进程概念到进程控制,一文掌握操作系统核心机制
摘要
进程是操作系统中最重要的概念之一,也是 Linux 系统管理程序运行的核心。
很多初学者会把“程序”和“进程”混为一谈,但实际上程序只是存储在磁盘上的代码文件,而进程则是程序运行起来后,由操作系统进行管理的动态实体。
本文结合 Linux 操作系统原理,从操作系统为什么需要进程开始,逐步介绍:
- 进程的基本概念
- PCB(进程控制块)与 task_struct
- 进程创建 fork
- 写时拷贝机制
- 进程状态
- 僵尸进程与孤儿进程
- 进程调度与上下文切换
- 进程终止与等待
- exec 程序替换
帮助大家建立完整的 Linux 进程知识体系。
一、为什么需要进程?
现代计算机系统由硬件和软件共同组成。
硬件提供计算能力,而操作系统负责管理硬件资源,并为应用程序提供运行环境。
操作系统的核心定位可以概括为:
操作系统是一款负责管理的软件。
它主要完成:
- 管理 CPU 资源
- 管理内存资源
- 管理文件资源
- 管理设备资源
- 为用户程序提供运行环境
操作系统管理对象时,通常采用两个步骤:
先描述,后组织
例如:
管理一个学生:
- 描述:记录姓名、年龄、成绩等信息
- 组织:使用链表等结构管理所有学生
进程管理也是同样的思想。
操作系统首先需要描述进程,然后组织所有进程。
二、程序和进程的区别
1. 什么是程序?
程序是存储在磁盘上的一组指令和数据。
例如:
hello
这个文件本身只是一个程序。
它没有运行,也没有占用 CPU。
2. 什么是进程?
当程序被加载到内存,并且由操作系统调度执行时,就成为了进程。
Linux 内核角度认为:
进程 = task_struct + 程序代码和数据
也就是说:
- 程序是静态的
- 进程是动态的
程序描述:
要做什么
进程描述:
正在怎么做
三、进程的核心结构:PCB
操作系统需要管理大量进程。
例如:
- 当前有哪些进程?
- 每个进程运行到哪里?
- 每个进程占用了多少资源?
因此,操作系统需要保存进程信息。
这个数据结构叫:
PCB(Process Control Block,进程控制块)
Linux 中 PCB 的实现:
task_struct
task_struct 是 Linux 内核中的结构体,用于描述进程信息。
它包含:
- PID(进程编号)
- 进程状态
- 优先级
- 程序计数器
- 内存信息
- CPU 上下文
- IO 信息
- 文件信息
Linux 中所有进程最终都会通过 task_struct 组织起来。
四、进程创建:fork
Linux 创建进程最常用的方法:
fork()
fork 的作用:
从已有进程创建一个新的进程。
创建出来的新进程:
- 原进程:父进程
- 新进程:子进程
fork 返回值:
父进程:
返回子进程 PID
子进程:
返回 0
失败:
返回 -1
为什么 fork 有两个返回值?
因为 fork 调用后:
系统中产生两个执行流:
fork()
|
--------------
| |
父进程 子进程
返回PID 返回0
父子进程都会从 fork 后继续执行。
五、写时拷贝机制
fork 创建子进程后:
父子进程代码共享。
但是数据不会立即复制。
Linux 使用:
Copy On Write(写时拷贝)
过程:
- 父子进程共享数据
- 某个进程修改数据
- 操作系统复制一份数据
- 两个进程拥有独立空间
优点:
- 减少内存浪费
- 提高系统效率
- 保证进程独立性
六、进程状态
一个进程不是一直运行。
Linux 中常见状态:
1. R运行状态
表示:
- 正在运行
- 或等待 CPU 调度
2. S睡眠状态
等待某个事件完成。
例如:
- 等待输入
- 等待网络数据
3. D磁盘休眠状态
等待 IO 完成。
4. T停止状态
进程被暂停。
5. Z僵尸状态
特殊状态。
产生原因:
子进程退出
+
父进程没有回收
=
僵尸进程
僵尸进程已经结束,但是 PCB 仍然存在。
如果大量产生,会造成资源浪费。
七、孤儿进程
如果:
父进程先退出,
子进程仍然运行。
那么:
子进程称为孤儿进程。
Linux 会让孤儿进程被:
init/systemd
进程接管。
八、进程调度
系统中:
进程数量 >> CPU数量
因此多个进程需要竞争 CPU。
操作系统通过调度算法选择:
下一个运行的进程。
进程具有:
竞争性
多个进程竞争有限资源。
独立性
进程之间互不影响。
并行
多个 CPU 同时运行多个进程。
并发
一个 CPU 通过切换运行多个进程。
九、上下文切换
CPU 不可能一直执行一个程序。
当切换进程时:
需要保存当前进程状态。
这个过程叫:
Context Switch(上下文切换)
主要步骤:
- 保存当前 CPU 寄存器
- 保存进程运行状态
- 加载另一个进程状态
- 继续执行
十、进程终止
进程退出本质:
释放系统资源。
常见退出方式:
正常退出
- return
- exit()
- _exit()
异常退出
例如:
- Ctrl+C
- 信号终止
程序退出后,可以通过:
echo $?
查看退出码。
十一、进程等待 wait/waitpid
父进程为什么需要等待子进程?
原因:
- 回收子进程资源
- 获取子进程执行结果
- 避免僵尸进程
Linux 提供:
wait()
等待任意子进程。
waitpid()
等待指定子进程。
十二、程序替换 exec
fork 创建子进程后:
子进程默认执行父进程代码。
如果想运行新的程序:
需要:
exec
exec 的特点:
- 不创建新进程
- PID 不改变
- 替换当前进程代码和数据
例如:
Shell 执行:
ls
实际上就是:
fork()
|
创建子进程
|
exec()
|
执行ls程序
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)