摘要

进程是操作系统中最重要的概念之一,也是 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(写时拷贝)

过程:

  1. 父子进程共享数据
  2. 某个进程修改数据
  3. 操作系统复制一份数据
  4. 两个进程拥有独立空间

优点:

  • 减少内存浪费
  • 提高系统效率
  • 保证进程独立性

六、进程状态

一个进程不是一直运行。

Linux 中常见状态:

1. R运行状态

表示:

  • 正在运行
  • 或等待 CPU 调度

2. S睡眠状态

等待某个事件完成。

例如:

  • 等待输入
  • 等待网络数据

3. D磁盘休眠状态

等待 IO 完成。


4. T停止状态

进程被暂停。


5. Z僵尸状态

特殊状态。

产生原因:

子进程退出
+
父进程没有回收
=
僵尸进程

僵尸进程已经结束,但是 PCB 仍然存在。

如果大量产生,会造成资源浪费。


七、孤儿进程

如果:

父进程先退出,

子进程仍然运行。

那么:

子进程称为孤儿进程。

Linux 会让孤儿进程被:

init/systemd

进程接管。


八、进程调度

系统中:

进程数量 >> CPU数量

因此多个进程需要竞争 CPU。

操作系统通过调度算法选择:

下一个运行的进程。

进程具有:

竞争性

多个进程竞争有限资源。

独立性

进程之间互不影响。

并行

多个 CPU 同时运行多个进程。

并发

一个 CPU 通过切换运行多个进程。


九、上下文切换

CPU 不可能一直执行一个程序。

当切换进程时:

需要保存当前进程状态。

这个过程叫:

Context Switch(上下文切换)

主要步骤:

  1. 保存当前 CPU 寄存器
  2. 保存进程运行状态
  3. 加载另一个进程状态
  4. 继续执行

十、进程终止

进程退出本质:

释放系统资源。

常见退出方式:

正常退出

  • return
  • exit()
  • _exit()

异常退出

例如:

  • Ctrl+C
  • 信号终止

程序退出后,可以通过:

echo $?

查看退出码。


十一、进程等待 wait/waitpid

父进程为什么需要等待子进程?

原因:

  1. 回收子进程资源
  2. 获取子进程执行结果
  3. 避免僵尸进程

Linux 提供:

wait()

等待任意子进程。

waitpid()

等待指定子进程。


十二、程序替换 exec

fork 创建子进程后:

子进程默认执行父进程代码。

如果想运行新的程序:

需要:

exec

exec 的特点:

  • 不创建新进程
  • PID 不改变
  • 替换当前进程代码和数据

例如:

Shell 执行:

ls

实际上就是:

fork()
  |
创建子进程
  |
exec()
  |
执行ls程序
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐