第07节-进程是什么
第 7 节:进程是什么
所属课程:操作系统自学路线:面向网络安全、逆向工程与漏洞分析
所属周次:第 4 周
课程主题:进程模型
本节目标:理解程序和进程的区别,掌握 PID、父子进程、进程状态、PCB、上下文切换等基本概念,并通过 Linuxfork实验观察进程创建行为。
1. 本节课你要学会什么
学完这一节,你应该能回答下面几个问题:
- 程序和进程有什么区别?
- 为什么同一个程序可以运行多个实例?
- PID 是什么?PPID 是什么?
- 父进程和子进程是什么关系?
- 进程有哪些常见状态?
- PCB 是什么?操作系统为什么需要它?
- 上下文切换是什么?为什么有成本?
- Linux 下
fork做了什么? - 僵尸进程是什么?
- 为什么进程模型对恶意代码分析和逆向很重要?
本节课的主线是:
1 | |
前面几节你主要从“程序内部”看代码、内存和调试;从这一节开始,你要从“操作系统外部”看程序如何作为进程被管理。
2. 程序和进程的区别
这是操作系统中最重要的基础概念之一。
2.1 程序是什么
程序通常指磁盘上的可执行文件。
例如 Linux 下:
1 | |
Windows 下:
1 | |
程序是静态的。
它放在磁盘上,不运行时只是一个文件。
2.2 进程是什么
进程是正在运行的程序实例。
当你执行:
1 | |
操作系统会:
- 读取可执行文件
- 创建进程结构
- 分配虚拟地址空间
- 加载代码和数据
- 初始化栈、堆、寄存器
- 把它交给 CPU 调度执行
这时它才是一个进程。
所以可以总结为:
1 | |
3. 一个程序可以对应多个进程
同一个可执行文件可以被运行多次。
例如你打开三个终端,分别执行:
1 | |
它们运行的是同一个程序:
1 | |
但操作系统会创建三个不同进程。
每个进程都有自己的:
- PID
- 虚拟地址空间
- 寄存器上下文
- 栈
- 堆
- 打开的文件描述符
- 当前工作目录
- 环境变量
- 权限信息
所以:
1 | |
Windows 下同理。
你打开三个记事本:
1 | |
4. PID 和 PPID
操作系统需要区分不同进程。
所以每个进程都有一个编号,叫:
1 | |
父进程 ID 叫:
1 | |
Linux 下可以查看:
1 | |
示例输出:
1 | |
这表示:
1 | |
在进程树中,父子关系非常重要。
5. 父进程和子进程
一个进程可以创建另一个进程。
创建者叫父进程。
被创建者叫子进程。
例如你在 shell 中运行:
1 | |
大致过程是:
1 | |
这里:
1 | |
Linux 下常见进程创建方式是:
1 | |
Windows 下常见方式是:
1 | |
下一节会专门对比 Linux 和 Windows 的进程创建机制。
6. 进程树
系统中的进程通常形成一棵树。
Linux 下可以使用:
1 | |
或者:
1 | |
你可能看到类似:
1 | |
这说明:
systemd是很多进程的祖先sshd创建了 shell- shell 又启动了
vim
Windows 下可以用:
- Process Explorer
- Process Hacker
- Task Manager 的详细信息视图
Process Explorer 可以清楚看到进程树。
恶意代码分析时,进程树非常重要。
例如:
1 | |
这类链条就很可疑。
7. 进程有哪些资源
一个进程不是只有代码。
操作系统会为进程维护一整套资源。
常见包括:
- PID
- 父进程 ID
- 用户 ID / 权限
- 虚拟地址空间
- 代码段
- 数据段
- 堆
- 栈
- 寄存器状态
- 打开的文件描述符
- 当前工作目录
- 环境变量
- 信号处理方式
- 线程列表
- 调度信息
你可以把进程理解成:
1 | |
这就是为什么进程比程序复杂得多。
8. PCB 是什么
PCB 全称是:
1 | |
中文常译为:
1 | |
PCB 是操作系统内核中用来描述和管理进程的数据结构。
它保存了进程的关键信息,例如:
- PID
- 进程状态
- 程序计数器 / 指令位置
- CPU 寄存器
- 内存管理信息
- 打开的文件
- 调度优先级
- 父子关系
- 权限信息
- 资源使用统计
注意:
PCB 是操作系统内部概念。
普通用户程序不能直接随便访问内核中的 PCB。
但我们可以通过系统工具间接观察进程信息,例如:
1 | |
9. Linux 中 PCB 对应什么
Linux 内核中,进程和线程的核心描述结构常和:
1 | |
相关。
task_struct 中保存了大量进程或线程状态。
你现在不需要阅读内核源码,只要知道:
1 | |
以后学习内核、驱动、Rootkit、内核漏洞时,task_struct 会再次出现。
10. 进程状态
进程在生命周期中会处于不同状态。
常见状态包括:
1 | |
不同教材和系统命名略有差异。
10.1 Running:正在运行
进程正在 CPU 上执行。
如果系统只有一个 CPU 核心,同一瞬间真正运行的进程数量有限。
但因为调度切换很快,我们感觉很多程序在“同时运行”。
10.2 Ready:就绪
进程已经准备好运行,但暂时没有拿到 CPU。
它在等待调度器选择它。
10.3 Blocked / Waiting:阻塞或等待
进程暂时不能继续执行,因为它在等待某个事件。
例如:
- 等待用户输入
- 等待读文件完成
- 等待网络数据
- 等待锁
- 等待子进程退出
阻塞进程不会一直占用 CPU。
10.4 Stopped:停止
进程被暂停。
例如:
- 被调试器暂停
- 收到
SIGSTOP - 用户按
Ctrl+Z
10.5 Zombie:僵尸进程
僵尸进程是已经结束执行,但父进程还没有读取它退出状态的进程。
它不再运行,也不占用普通运行资源,但进程表中还保留一条记录。
为什么要保留?
因为父进程可能需要知道:
1 | |
父进程调用 wait 或 waitpid 后,僵尸进程会被回收。
11. Linux ps 中的状态码
Linux 下可以用:
1 | |
常见状态码:
| 状态 | 含义 |
|---|---|
| R | Running 或 Runnable,正在运行或可运行 |
| S | Sleeping,可中断睡眠,等待事件 |
| D | Uninterruptible sleep,不可中断睡眠,常见于 I/O |
| T | Stopped,停止或被跟踪 |
| Z | Zombie,僵尸进程 |
| I | Idle,内核空闲线程 |
可能还会看到附加符号:
| 符号 | 含义 |
|---|---|
| s | 会话 leader |
| + | 前台进程组 |
| l | 多线程进程 |
| < | 高优先级 |
| N | 低优先级 |
这些状态在观察系统行为时很有用。
12. 上下文是什么
进程执行时,CPU 中有很多当前状态。
例如:
rip:当前执行位置rsp:当前栈顶- 通用寄存器
- 标志寄存器
- 内存映射信息
- 调度相关信息
这些运行状态统称为上下文。
可以粗略理解为:
1 | |
13. 上下文切换是什么
操作系统需要让多个进程共享 CPU。
当 CPU 从进程 A 切换到进程 B 时,需要:
- 保存进程 A 的上下文
- 更新进程 A 的状态
- 选择进程 B
- 恢复进程 B 的上下文
- 让 CPU 从进程 B 上次暂停的位置继续执行
这叫上下文切换。
1 | |
上下文切换有成本。
因为它需要保存和恢复状态,还可能影响 CPU 缓存、TLB 等硬件结构。
所以线程和进程切换都不是免费的。
14. 为什么一个 CPU 看起来能运行很多程序
如果只有一个 CPU 核心,同一瞬间只能真正执行一个执行流。
但操作系统会快速切换不同进程。
例如:
1 | |
因为切换速度很快,人感觉它们在同时运行。
这叫并发。
如果有多个 CPU 核心,多个进程可以真的同时在不同核心上运行。
这叫并行。
区别:
1 | |
15. Linux 下的 fork
Linux 中,创建进程的经典方式是:
1 | |
fork 会创建当前进程的一个子进程。
子进程从 fork 返回的位置继续执行。
这点非常重要:
1 | |
区别在于返回值不同。
16. fork 的返回值
fork 的返回值用于区分父子进程。
| 返回值 | 含义 |
|---|---|
| 小于 0 | 创建失败 |
| 等于 0 | 当前在子进程中 |
| 大于 0 | 当前在父进程中,返回值是子进程 PID |
示例:
1 | |
这是 Linux 进程编程最基本的模式。
17. 第一个 fork 程序
创建 fork_demo.c:
1 | |
编译:
1 | |
运行:
1 | |
你可能看到:
1 | |
为什么 after fork 打印两次?
因为 fork 之后,有两个进程继续执行后面的代码:
- 父进程
- 子进程
18. fork 后父子进程内存是否一样
从程序视角看,fork 后子进程像是父进程的一份拷贝。
它继承了很多东西:
- 代码
- 数据
- 堆
- 栈
- 打开的文件描述符
- 当前工作目录
- 环境变量
但父子进程是两个独立进程。
它们有不同 PID。
现代操作系统通常使用 Copy-on-Write。
也就是:
1 | |
这样可以提高效率。
入门阶段先记住:
1 | |
19. fork 后变量变化实验
创建 fork_var_demo.c:
1 | |
编译运行:
1 | |
你可能看到:
1 | |
这说明:
1 | |
20. wait 是什么
父进程通常需要等待子进程结束。
Linux 中可以用:
1 | |
或者:
1 | |
作用:
- 等待子进程退出
- 获取子进程退出状态
- 回收子进程资源
- 避免僵尸进程
如果父进程不调用 wait,子进程结束后可能暂时变成僵尸进程。
21. 僵尸进程演示
创建 zombie_demo.c:
1 | |
编译运行:
1 | |
另开终端查看:
1 | |
你可能看到子进程状态是:
1 | |
这就是僵尸进程。
注意:
这个实验是安全的,父进程睡眠结束后僵尸进程会被回收。
22. 孤儿进程是什么
如果父进程先退出,子进程还在运行,这个子进程就变成孤儿进程。
孤儿进程会被系统中的某个进程接管。
在很多 Linux 系统上,通常由 systemd 或 init 类进程接管。
孤儿进程本身不一定是问题。
但在恶意代码分析中,如果一个进程故意脱离父进程、隐藏运行,就值得关注。
23. 进程和线程的初步区别
本节主要讲进程,后面会专门讲线程。
先简单区分:
| 对比 | 进程 | 线程 |
|---|---|---|
| 资源拥有 | 拥有独立地址空间和资源 | 属于进程,和同进程线程共享资源 |
| 隔离性 | 较强 | 较弱 |
| 创建开销 | 较大 | 较小 |
| 通信 | 进程间通信较复杂 | 共享内存更方便 |
| 崩溃影响 | 一个进程崩溃通常不直接毁掉另一个进程 | 一个线程崩溃可能导致整个进程崩溃 |
先记住:
1 | |
不同教材说法可能略有差异,但这个理解对入门很有帮助。
24. Windows 中的进程初步
Windows 中也有进程和线程。
创建进程常用:
1 | |
Windows 进程也有:
- PID
- 父进程
- 虚拟地址空间
- 句柄表
- Token
- 线程
- DLL 模块
- 环境变量
可以用 Process Explorer 查看:
- 进程树
- PID
- PPID
- 命令行
- 当前用户
- 线程数量
- 打开的句柄
- 加载的 DLL
Windows 安全分析中,进程树非常重要。
例如:
1 | |
这些父子关系可能体现用户行为,也可能体现攻击链。
25. 进程行为和恶意代码分析
恶意代码经常会创建新进程。
常见目的包括:
- 启动系统工具执行命令
- 创建子进程隐藏行为
- 注入其他进程
- 启动持久化组件
- 逃避简单检测
- 执行下载的 payload
常见可疑进程链:
1 | |
这不是说这些链条一定恶意,而是需要进一步分析。
工具:
- Windows Process Explorer
- Windows Process Monitor
- Sysmon
- Linux ps / pstree / auditd
26. 进程行为和逆向工程
逆向一个程序时,你不只看代码,还要看它运行后创建了什么进程。
例如:
- 是否调用
fork - 是否调用
execve - 是否调用
system - 是否调用
CreateProcessW - 是否启动 shell
- 是否把参数传给子进程
- 是否等待子进程结束
如果一个程序表面上什么都不做,但运行后启动了另一个程序,那么真正逻辑可能在子进程中。
所以动态分析时要观察进程树。
27. 进程行为和漏洞分析
进程模型也和漏洞分析有关。
例如:
- 一个网络服务可能为每个连接创建子进程
- 一个崩溃只影响当前 worker 进程
- 父进程可能负责重启子进程
- 子进程权限可能比父进程低
- setuid 程序创建子进程时可能引入风险
- 子进程继承文件描述符可能造成信息泄露
真实漏洞分析中,你经常要搞清楚:
1 | |
28. Linux 实验:观察进程和 PID
执行:
1 | |
再执行:
1 | |
另开一个终端:
1 | |
观察:
sleep的 PID 是什么- 它的 PPID 是谁
- 它的状态是什么
也可以用:
1 | |
观察进程树。
29. Linux 实验:fork 创建子进程
创建 fork_demo.c:
1 | |
编译运行:
1 | |
观察:
before fork打印几次?parent打印几次?child打印几次?- 父进程中
fork返回值是什么? - 子进程中
fork返回值是什么?
30. Linux 实验:用 strace 观察 fork
执行:
1 | |
你可能看到类似:
1 | |
在现代 Linux 中,fork 底层可能通过 clone 系统调用实现。
这说明:
1 | |
如果输出太多,可以尝试:
1 | |
-f 表示跟踪子进程。
观察父子进程输出对应的系统调用。
31. Windows 实验:Process Explorer 观察进程树
在 Windows 下:
- 打开 Process Explorer
- 启动
notepad.exe - 查看它的 PID
- 查看父进程
- 查看线程数量
- 查看加载的 DLL
- 关闭 notepad,观察进程消失
再打开 cmd.exe,执行:
1 | |
观察:
- notepad 的父进程是否是 cmd
- 如果从 Explorer 双击启动,父进程是否不同
这能帮助你理解:
1 | |
32. 本节重点总结
你需要记住这些核心结论:
- 程序是磁盘上的静态文件,进程是运行中的程序实例。
- 同一个程序可以对应多个进程。
- PID 是进程 ID,PPID 是父进程 ID。
- 进程之间可以形成父子关系和进程树。
- 操作系统通过 PCB 管理进程状态和资源。
- 进程常见状态包括运行、就绪、阻塞、停止、僵尸等。
- 上下文切换是操作系统保存一个进程状态并恢复另一个进程状态的过程。
- Linux 下
fork会创建子进程,父子进程从fork后继续执行。 fork在父进程和子进程中返回值不同。- 进程树和进程行为是恶意代码分析、逆向分析和漏洞分析的重要线索。
33. 本节课后作业
作业 1:观察系统进程
执行:
1 | |
提交内容:
1 | |
作业 2:fork 程序实验
写并运行:
1 | |
提交内容:
1 | |
作业 3:fork 后变量是否共享
运行 fork_var_demo.c。
提交内容:
1 | |
作业 4:观察僵尸进程
运行 zombie_demo.c。
在父进程 sleep 期间执行:
1 | |
提交内容:
1 | |
作业 5:Windows 进程树观察
如果你有 Windows 环境:
- 用 Process Explorer 打开进程树
- 从 cmd 启动 notepad
- 从资源管理器双击启动 notepad
- 对比父进程
提交内容:
1 | |
34. 自测题
题 1
程序和进程有什么区别?
题 2
为什么同一个程序可以运行多个进程?
题 3
PID 和 PPID 分别是什么?
题 4
PCB 的作用是什么?
题 5
什么是上下文切换?
题 6
fork 后为什么父子进程都会继续执行后面的代码?
题 7
fork 在父进程和子进程中的返回值有什么区别?
题 8
什么是僵尸进程?
题 9
为什么进程树对恶意代码分析重要?
35. 自测题参考答案
答 1
程序是磁盘上的静态可执行文件;进程是程序被操作系统加载后正在运行的实例,包含代码、内存、寄存器状态、打开文件、权限等运行时资源。
答 2
因为可执行文件只是模板。每次运行它,操作系统都可以创建一个新的进程实例,每个实例有自己的 PID、地址空间和运行状态。
答 3
PID 是进程 ID,用来唯一标识一个进程;PPID 是父进程 ID,用来表示该进程由哪个父进程创建。
答 4
PCB 是操作系统内核用于描述和管理进程的数据结构,保存进程状态、寄存器、内存管理信息、打开文件、调度信息、权限等。
答 5
上下文切换是 CPU 从一个进程切换到另一个进程时,操作系统保存当前进程运行状态并恢复另一个进程运行状态的过程。
答 6
因为 fork 创建子进程后,子进程从 fork 返回的位置开始继续执行,父进程也继续执行,所以父子进程都会执行 fork 后面的代码。
答 7
fork 在子进程中返回 0;在父进程中返回子进程的 PID;如果失败返回负数。
答 8
僵尸进程是已经结束执行,但父进程还没有读取其退出状态的进程。它保留在进程表中,等待父进程调用 wait 或 waitpid 回收。
答 9
因为恶意代码常通过创建子进程、启动系统工具、注入其他进程等方式执行行为。进程树能帮助分析程序的启动链、父子关系和可疑行为路径。
36. 下一节预告
下一节课会讲:
1 | |
你会学习:
- Linux 的
fork、execve、wait - Windows 的
CreateProcess - 为什么 Windows 没有传统
fork - 父子进程继承了什么
- 恶意代码为什么经常创建子进程
- 如何用
strace和 Process Monitor 观察进程创建