01 认识进程

1.1 进程是什么

原文定义

进程是操作系统中一次程序执行过程,是操作系统分配资源、CPU 调度的最小单位。 运行程序时,内核会创建task_struct结构体(PCB 进程控制块),这个结构体就是进程本身;进程所有信息(状态、PID、内存地址等)全部存在这里。 程序运行结束,内核回收task_struct;程序异常崩溃,内核清理资源(防止僵尸进程)。

通俗大白话

打开微信、浏览器,每一个正在运行的软件窗口,就是进程。 操作系统给每个运行的程序单独建一张 “档案卡”task_struct,卡上记录这个程序占用多少内存、CPU、现在是运行还是休眠、编号 PID 等。程序关掉,档案销毁;程序崩溃,系统自动清理档案。

运行调度原理(原文)

CPU 采用时间片轮转调度:每个进程分配固定时间片(示例 5ms),轮流占用 CPU;用完时间片就进入就绪队列排队等待下一次调度。 示例:QQ、WPS、钉钉多个进程排队,轮流使用 CPU。

进程四大核心特征(原文 + 通俗解释)

  1. 动态性 原文:动态产生、动态消亡。 通俗:双击软件才会出现进程,关闭软件进程消失,不是永久存在。
  2. 并发性 原文:多个进程可以同时一起执行。 通俗:一边放视频、一边聊微信、一边下载文件,看似同时运行。
  3. 独立性 原文:独立运行、独立分配资源,有专属内存空间。 通俗:微信崩溃不会让浏览器一起闪退,两者内存互不干扰。
  4. 异步性 原文:进程执行速度不可预测,互相不受控。 通俗:CPU 先跑微信还是先跑浏览器,由系统调度决定,顺序不固定。

02 程序与进程的关系

2.1 程序 vs 进程对比表

表格

对比维度 程序 (Program) 进程 (Process)
定义 磁盘上静态文件(指令集合,如 exe),不运行不占资源 程序动态运行实例,占用 CPU、内存、网卡等资源,有完整生命周期
存在形式 静态文件,永久保存在硬盘 动态,启动创建、结束销毁,内存临时存在
资源占用 只占硬盘存储空间,不占用运行内存、CPU 占用物理内存、CPU、文件句柄、端口,独立虚拟地址空间
状态 无状态,只是一堆代码 拥有多种运行状态(运行、休眠、暂停等),状态随时变化
独立性 文件之间互不影响 进程内存完全隔离,默认不能互相读写数据,必须靠特殊通信方式
举例 硬盘里的 chrome.exe 文件 双击浏览器后打开的窗口(1 个程序可以生成 N 个进程)

通俗总结

程序 = 硬盘里的安装包 / 软件文件(静态、死的); 进程 = 打开软件后正在跑的程序(动态、活的)。

2.2 程序与进程关联(原文 + 补充)

  1. 进程是程序的运行实例,一个程序可以对应多个进程 例:同时打开 3 个浏览器窗口,chrome 程序只有一份,但有 3 个独立进程。
  2. 程序是模板,进程是运行表现。
  3. 同程序多进程:只读代码段共享(节省内存),数据段、栈、堆完全独立。 补充:代码不会被修改,所有进程共用一份代码;但变量、数据每个进程单独一份。
  4. 关闭进程,磁盘上的程序文件还在;删除程序文件,已经打开的进程能继续运行,直到需要读取文件时报错。

03 进程组成与运行属性

3.1 进程三大组成部分

1)PCB 进程控制块(task_struct)

原文:操作系统管理进程的核心结构体,每个进程唯一,记录 PID、状态、内存、资源占用,是系统识别进程的唯一依据。 通俗:每个进程的身份证 + 档案,内核靠它管理所有进程。

2)文本段(代码段)

原文:存放程序编译后的机器指令,只读;同一程序多进程共享,运行期间大小固定。 通俗:软件的代码指令,禁止修改,多个浏览器共用同一份代码,省内存。

3)数据段(细分 4 块)

存放运行时数据,分为:

  1. 初始化全局数据段(定义并赋值的全局变量)
  2. 未初始化数据段(只声明不赋值的全局变量)
  3. 堆:程序动态申请的内存(malloc/new)
  4. 栈:局部变量、函数调用信息

3.2 虚拟内存与物理内存映射(原文补充)

Linux 3G/1G 划分:

  • 用户空间:0~3G,普通程序访问
  • 内核空间:3G~4G,操作系统内核使用 MMU 内存管理单元:负责把程序看到的虚拟地址,翻译成真实物理内存地址;内存不够时会把数据放到 swap 交换分区(硬盘)。

通俗:程序看到的内存是 “假地址”,MMU 做翻译,内存不够就临时存硬盘。

3.3 Linux 三种进程类型

1. 交互进程

定义:终端手动启动,和用户交互;依赖终端,前台运行。 例子:lsvim./a.out,关掉终端进程直接退出。

2. 批处理进程

定义:无需人工交互,后台批量执行,优先级低;脚本、定时任务。 例子:批量日志清理、数据备份脚本。

3. 守护进程(daemon)

定义:开机自启后台服务,完全脱离终端;开机启动、关机结束,持续提供服务。 例子:nginx、mysql、ssh、systemd。

3.4 Linux 六大基础进程状态(原文 + 通俗讲解)

  1. R 运行 / 就绪态 正在 CPU 上运行,或排队等待 CPU 调度;ps里看到 R。
  2. S 可中断睡眠 进程等待事件(等待输入、等待网络、等待子进程);可以被信号唤醒(kill、Ctrl+C 能打断)。
  3. D 不可中断睡眠(磁盘休眠) 等待硬件 IO(读写硬盘),任何信号都杀不死,包括kill -9;防止写入磁盘中途中断导致数据损坏。 补充:长期 D 状态代表磁盘故障 / IO 卡死。
  4. T 停止态 手动暂停,Ctrl+Zkill -19暂停;可用kill -18恢复运行。
  5. Z 僵尸态 程序已经执行完毕退出,但父进程没有调用wait()回收它的退出信息;PCB 残留,占用 PID 资源。 通俗:员工离职没办手续,档案还留在公司。
  6. X 死亡态 进程资源全部回收,生命周期彻底结束;瞬间状态,几乎看不到。

3.5 进程附加状态标记(ps 输出里的后缀符号)

  • +:前台进程,绑定当前终端
  • l:多线程进程
  • N:低优先级(NI>0)
  • <:高优先级(NI<0)
  • s:会话首进程(管理一组终端进程)
  • L:持有文件 / 资源锁

3.6 进程状态流转简图文字版

  1. fork()创建进程 → R 运行态
  2. 时间片用完 → 进入就绪队列等待调度
  3. 调用sleep()/ 等待 IO → S 可中断睡眠;等待磁盘硬件 → D 不可中断睡眠
  4. SIGSTOP信号 → T 停止态;SIGCONT恢复为 R
  5. 进程执行exit()退出 → Z 僵尸态
  6. 父进程调用wait()回收 → X 死亡态

3.7 PID 进程 ID

基础规则

PID 是进程唯一数字编号,从 1 开始;系统最大进程数由kernel.pid_max控制(默认 131072)。 所有进程信息存在/proc/[PID]文件夹,/proc/1/status查看 1 号进程详情。

三大特殊 PID

  1. PID 0:idle 空闲进程 系统最底层进程,CPU 无事可做时运行,负责调度空闲 CPU。
  2. PID 1:systemd/init 初始化进程 0 号进程创建;系统初始化、启动所有服务;自动回收所有孤儿进程。
  3. PID 2:kthreadd 内核线程管理进程 负责创建、管理所有内核后台任务(磁盘回收、内存清理等)。

04 进程相关命令大全(原文 + 补充用法)

4.1 ps 查看进程快照(静态,只打印一次)

表格

命令 参数作用 使用场景
ps -ef -e 所有进程,-f 完整格式 查看系统全部进程、父子 PID 关系
ps -aux -a 所有终端进程,-u 用户资源,-x 无终端守护进程 看 CPU、内存占用百分比
ps -eo pid,ppid,comm -e 全部进程,-o 自定义输出字段 只看需要的信息,精简输出
ps -l -l 长格式 查看当前终端进程优先级、NI、状态

4.2 top 实时进程监控(动态刷新)

功能:每秒刷新 CPU、内存、进程负载;可按 M 内存 / P CPU 排序; 常用操作:q退出,M按内存排序,P按 CPU 排序。

4.3 htop(top 升级版)

优势:彩色界面、支持鼠标、直观进程树、操作快捷键更简单; 补充:Ubuntu 默认不预装,安装命令:

bash

运行

sudo apt update
sudo apt install htop

4.4 kill 发送信号控制进程(按 PID 操作)

表格

命令 信号编号 作用
kill -l 列出全部系统信号
kill -2 PID SIGINT 等同于 Ctrl+C,正常中断程序
kill PID SIGTERM(15) 优雅终止,允许程序保存数据、释放资源(推荐优先用)
kill -9 PID SIGKILL 强制杀死,无法拦截,丢失缓存数据(万不得已使用)
kill -19 PID SIGSTOP 暂停进程运行
kill -18 PID SIGCONT 恢复暂停的进程

4.5 killall 按进程名批量杀进程

  • killall 进程名:正常关闭所有同名进程
  • killall -9 进程名:强制全部杀死
  • killall -i 进程名:逐个询问确认再关闭

4.6 pstree 进程树

以树形展示父子进程关系,直观看到谁创建了谁; 示例:pstreepstree -p(同时显示 PID)。

4.7 bg /fg 前后台任务管理

jobs

查看当前终端所有后台暂停任务(任务编号)

bg 任务号

把暂停的进程放到后台继续运行,不占用终端输入

fg 任务号

后台进程切回前台,占据终端,可交互

4.8 nice /renice 调整进程优先级

优先级范围:-20 ~ 19

  • -20:最高优先级,优先分配 CPU(仅 root 可设置)
  • 0:默认优先级
  • 19:最低优先级
  1. nice -n 数值 命令:启动程序时直接设置优先级 例:nice -n 10 ./test
  2. renice 数值 -p PID:修改正在运行进程的优先级 例:renice -5 -p 1234(root 提升优先级)

05 进程的创建 fork ()

5.1 fork 函数基础信息

头文件

c

运行

#include <sys/types.h>
#include <unistd.h>

函数原型

c

运行

pid_t fork(void);

功能

复制当前父进程,生成全新独立子进程。

返回值(重点,必背)

  1. 返回 >0(数字):当前是父进程,返回值 = 子进程 PID
  2. 返回 0:当前是子进程
  3. 返回 -1:创建失败(内存不足、进程数达到上限)

核心特性:调用一次,返回两次

执行一行fork(),代码分成两条执行流,父子进程同时往下跑。

5.2 父子进程内存机制:写时拷贝 COW(原文深度通俗讲解)

原文描述

fork 创建子进程不会立刻完整复制内存;只读时父子共享同一块物理内存;任意一方修改数据,才会单独拷贝一份内存给修改进程。

通俗类比

父子共用一本笔记(物理内存),只翻看不修改,共用同一本; 只要其中一方要写字修改,系统单独复印一页给他,两人各自一份,互不影响。

完整流程

  1. fork 后,父子页表指向同一物理内存,全部标记只读;
  2. 读变量:直接共享,不复制;
  3. 写变量(修改全局 / 局部变量):触发缺页异常;
  4. 内核分配新内存页,复制原有数据给写操作进程;
  5. 后续修改只作用在自己的独立内存,互不干扰。

补充拓展

代码段(只读指令)永久共享;数据、栈、堆只有修改才拷贝,大幅节省内存、加快 fork 创建速度。

5.3 课后练习:多进程分块拷贝文件

需求

  1. fork 创建父子进程协同复制文件
  2. 父进程拷贝文件前半段,子进程拷贝后半段,无重叠、无缺失

实现思路补充(原文拓展)

  1. 先用stat()获取源文件总大小,计算分割中点;
  2. 父子进程分别打开文件(共享文件指针会导致读写错乱);
  3. lseek()精准移动读写偏移,父从 0 开始,子从中间位置开始;
  4. 读写循环判断剩余字节,避免多读超出文件长度;
  5. 父进程使用wait()等待子进程拷贝完成,防止产生僵尸进程。

全文补充拓展知识点(原文缺失高频考点)

1. 僵尸进程产生与解决

产生原因:子进程 exit 退出,父进程未调用wait/waitpid回收。 危害:长期堆积占用 PID 资源,系统无法新建进程。 解决:父进程结尾加wait(NULL);等待子进程回收。

2. 孤儿进程

父进程提前退出,子进程失去父进程,会被 PID=1 的 systemd 收养,自动回收,不会变成僵尸。

3. fork+exec 配套使用

fork 只复制进程,exec 系列函数可以替换子进程为全新程序(shell 执行命令底层原理:bash fork 子进程,子进程 exec 加载 ls/rm 等程序)。

4. 进程与线程区别

进程有独立内存,隔离性强、开销大;线程共享同一进程内存,开销小、无隔离。

Logo

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

更多推荐