一、写在前面

学习操作系统,绕不开一个核心对象——进程。但在真正理解进程之前,我们需要先弄清楚两件事:计算机硬件是如何组织起来的(冯诺依曼体系),以及操作系统在其中扮演什么角色。只有把这两层铺垫好,进程的概念、状态、调度才会顺理成章。

二、冯诺依曼体系结构

2.1 核心结论

冯诺依曼体系结构最核心的一句话可以概括为:所有设备都只能和内存打交道。

  • CPU 只能对内存进行读写,不能直接访问外设。
  • 外设(键盘、显示器、磁盘、网卡等)也只能把数据写入内存,或者从内存中读取数据。
  • CPU 与外设之间不直接通信,一切数据流动都要经过内存这个中转站。

2.2 为什么这样设计

CPU 的运算速度远高于外设,如果让 CPU 直接等待外设,性能会被严重拖累。引入内存作为缓冲层后,CPU 只需要和高速内存交互,外设的数据先落到内存,再由 CPU 处理,从而实现了速度上的解耦。

一句话理解:内存是 CPU 和外设之间的「缓冲区」和「中转站」,这也是为什么内存容量和速度会直接影响整机性能。

三、操作系统

3.1 组成

操作系统大致可以分为两部分:

  • 内核:负责进程管理、内存管理、文件系统、设备驱动等核心功能,运行在特权态。
  • 其他程序:如 shell、系统工具、库等,运行在用户态。

3.2 作用

操作系统的作用可以从上下两个方向理解:

  • 对上:为用户程序提供一个良好的执行环境,屏蔽硬件细节,让程序可以专注于业务逻辑。
  • 对下:实现与硬件的交互,管理 CPU、内存、磁盘、外设等资源。

3.3 管理方式

操作系统管理资源的基本套路是:用结构体描述,用数据结构组织。

例如,每个硬件设备、每个进程,在内核中都会有一个对应的结构体来描述它的属性;再通过链表、队列、树等数据结构把这些结构体组织起来,从而实现高效的查找、插入和删除。

四、进程的概念

4.1 什么是进程

进程可以简单理解为正在执行的程序,它是系统进行资源分配的基本实体。

进程 = 内核数据结构 + 自己的程序代码

程序代码是静态的,而内核数据结构记录了程序运行时的全部状态信息,两者结合才构成一个动态的进程。

4.2 PCB 与 task_struct

描述进程的内核数据结构称为 PCB(Process Control Block,进程控制块),在 Linux 中具体实现为 task_struct。它通常包含以下信息:

字段说明
标识符进程的唯一 ID,即 PID
状态运行、就绪、阻塞、僵尸等
优先级决定进程被调度的先后顺序
程序计数器记录下一条要执行的指令地址
内存指针指向进程的代码段、数据段等
上下文数据保存寄存器等现场信息,用于进程切换
I/O 状态记录进程的输入输出请求和分配的设备
记账信息记录 CPU 使用时间、资源占用等统计信息

此外,Linux 中进程还有 RSDT 等状态划分,用于描述进程在运行、睡眠、停止、僵尸等不同阶段的表现。

五、查看进程状态

5.1 ps 命令

ps aux 是最常用的进程查看命令之一,各参数含义如下:

  • a:显示所有终端下的进程。
  • u:以用户为中心的格式显示进程信息。
  • x:显示没有控制终端的进程,即后台守护进程。
  • j:显示与作业控制相关的信息。

5.2 常见字段

  • UID:进程所属用户。
  • PID:进程 ID。
  • PPID:父进程 ID。
  • PRI:进程优先级,数值越小优先级越高。
  • NI:nice 值,用于调整优先级,范围通常为 -20 到 19。

5.3 top 命令

top 可以动态查看进程状态。进入 top 后,按 r 可以修改某个进程的优先级,输入 PID 后再输入 nice 值即可调整。

六、僵尸进程与孤儿进程

6.1 僵尸进程

子进程结束后,如果父进程没有及时回收它的退出状态,子进程就会处于 Z(僵尸)状态。此时子进程的代码已经执行完毕,但 PCB 仍然保留,等待父进程读取退出信息。

僵尸进程本身不占用 CPU,但会占用 PID 等内核资源。如果父进程一直不回收,僵尸进程会越积越多,最终可能导致无法创建新进程。

6.2 孤儿进程

如果父进程提前结束,而子进程还在运行,这个子进程就称为孤儿进程。孤儿进程会被 init 进程(或 systemd)收养,由它负责后续的回收,因此不会造成资源泄漏。

七、进程的竞争性、独立性与并发并行

  • 竞争性:多个进程争用 CPU、内存、I/O 等有限资源。
  • 独立性:每个进程拥有独立的地址空间,互不干扰。
  • 并行:多个进程在同一时刻真正同时运行,通常需要多核 CPU。
  • 并发:多个进程在同一时间段内交替运行,宏观上像是同时进行。

八、进程切换与调度队列

8.1 进程切换

进程切换的核心是保存当前进程的上下文,并恢复下一个进程的上下文。上下文包括寄存器、程序计数器、栈指针等,这些信息都保存在 PCB 中。

8.2 活动队列与过期队列

Linux 的调度器使用活动队列和过期队列来组织进程:

  • 活动队列:存放当前可以运行的进程,内部按优先级划分为 140 个队列,其中前 99 个对应实时进程,后 40 个对应普通分时进程。
  • 过期队列:存放时间片用完的进程,等待下一轮调度。

调度器通过 bitmap 快速判断某个优先级队列是否为空,从而高效地选出下一个要运行的进程。

当活动队列中的进程全部耗尽时间片后,active 指针和 expired 指针会交换内容,过期队列变为活动队列,开始新一轮调度。这种设计保证了所有进程都能公平地获得 CPU 时间。

九、命令行参数与环境变量

9.1 常见环境变量

  • PATH:命令搜索路径,决定了 shell 去哪里查找可执行文件。
  • HOME:当前用户的主目录。
  • SHELL:当前使用的 shell 程序。

9.2 环境变量的特性

环境变量通常具有全局属性,因为子进程一般按照父进程为模板创建,会继承父进程的环境变量。在代码中可以通过 getenv 等函数获取环境变量的值。

十、进程地址空间

Linux 下每个进程都有一个 mm_struct 来描述它的地址空间。地址空间中的虚拟区(VMA)通过不同数据结构组织:

  • 虚拟区较少时,使用单链表组织。
  • 虚拟区较多时,使用红黑树(mm_rb)组织,以便快速查找。

虚拟地址最终通过分页机制映射到物理内存,这也是进程之间相互隔离、互不干扰的基础。

Logo

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

更多推荐