操作系统原理:实验2.进程控制
本文介绍了Linux进程控制的三个核心系统调用:fork()用于创建子进程,会复制父进程的全部资源;execvp()用于加载新程序替换当前进程;wait()用于父进程等待子进程结束。通过实验展示了多进程并发执行的特性,包括进程隔离、资源回收和状态获取等。结果表明父进程和子进程可以并行运行,执行顺序由系统调度决定,体现了真正的并发特性。同时详细阐述了这三个系统调用的工作机制和多进程与单进程在资源独立
2.进程控制

一、系统调用功能概述
1. fork() - 创建子进程
fork() 通过复制当前进程创建新进程。调用一次但返回两次:
- 父进程:返回新建子进程的 PID(>0)
- 子进程:返回 0
- 失败:返回 -1
子进程是父进程的副本,拥有独立的地址空间、变量副本和文件描述符。两个进程从 fork() 调用后的下一条指令开始并行执行。
2. execvp() - 执行新程序
execvp() 替换当前进程的代码、数据和栈,加载并执行指定程序:
const char* command:要执行的命令(在 PATH 中搜索)char* argv[]:参数数组,必须以 NULL 结尾- 调用成功后不返回,进程完全被新程序接管
- 失败时返回 -1
通常与 fork() 配合使用(fork-exec 模型),让子进程执行新程序而父进程继续运行。
3. wait() - 等待子进程终止
wait() 阻塞父进程直到任一子进程结束:
int *status:存储子进程退出状态(可为 NULL)- 返回终止子进程的 PID,失败返回 -1
- 配合宏
WIFEXITED(status)和WEXITSTATUS(status)解析退出码 - 防止子进程成为僵尸进程(zombie)
二、实验代码实现
实验(2):创建两个子进程并发执行
代码:multi_process.c

编译链接

运行观察

parent: PID=5155 child: PID=5156, PPID=5155 child: PID=5157, PPID=5155 parent: PID=5155 child: PID=5157, PPID=5155 child: PID=5156, PPID=5155 child: PID=5157, PPID=5155 parent: PID=5155 child: PID=5156, PPID=5155 parent: PID=5155 child: PID=5156, PPID=5155 child: PID=5157, PPID=5155 ^[achild: PID=5157, PPID=5155 parent: PID=5155 child: PID=5156, PPID=5155 parent: 所有子进程已终止,父进程退出.
该实验结果就很好的说明了
进程并发执行
输出结果显示父进程(PID=5155)和两个子进程(PID=5156、5157)的输出信息交替混合出现,这说明:三个进程在同时运行,执行顺序不确定,由操作系统调度决定,体现了真正的并发执行特性。
进程家族关系
从PPID(父进程ID)可以看出:两个子进程的PPID都是5155,确认它们是同一父进程创建,形成了清晰的进程树结构:父进程→子进程1、子进程2
进程独立性
每个进程都有:独立的PID,独立的执行流,独立的输出时序,但共享相同的代码逻辑
资源隔离与共享
实验结果显示了:进程间内存空间隔离,但共享输出设备,所以输出会混合,需要同步机制来协调资源访问
进程生命周期
最后的提示"所有子进程已终止,父进程退出"说明:父进程使用wait()等待子进程结束,避免了僵尸进程的产生,体现了正确的进程回收机制。
实验(3):fork + execvp + wait 综合应用
需要创建两个文件:父程序和子进程要执行的新程序。
文件1:parent.c

文件2:new_program.c

编译与运行:
*# 编译两个程序* gcc new_program.c -o new_program gcc parent.c -o parent

*# 执行父程序* ./parent

结果为
new program. ← 子进程执行的新程序输出
Child process (pid: 5313) finished. ← 父进程检测到子进程终止
Exit status: 0 ← 子进程正常退出(返回值 0)
说明
- 进程隔离性:父进程 PID ≠ 子进程 PID(5313 是子进程)
- 执行顺序不确定性:虽然这里先打印子进程输出,但不保证永远如此
- 资源回收:
wait()成功回收子进程,避免僵尸进程 - 状态获取:
WIFEXITED()和WEXITSTATUS()正确解析退出码
三、思考题详细解答
(1) 系统调用 fork() 是如何创建进程的?
fork()通过复制当前进程(父进程)的代码、数据、堆栈、打开文件等资源创建一个新进程(子进程)。- 子进程与父进程几乎完全相同,只有少数属性不同(如 PID、PPID、资源使用统计等)。
fork()在父进程中返回子进程的 PID,在子进程中返回 0,出错时返回 -1。
(2) 当首次将 CPU 调度给子进程时,其入口在哪里?
- 子进程从
fork()返回处开始执行,即if (pid == 0)分支。 - 因为子进程复制了父进程的整个上下文,包括程序计数器(PC),所以它从
fork()调用后的指令继续执行。
(3) 系统调用 execvp() 是如何更换进程的可执行代码的?
execvp()会加载新的可执行文件到当前进程的地址空间,替换当前的代码段、数据段、堆栈等。- 进程的 PID 不变,但执行的程序完全更换。
- 如果
execvp()成功,则不会返回;只有失败时才返回 -1。
(4) 多进程并发 vs 单进程实现的主要区别
执行方式不同,实验1中,父进程和两个子进程的输出信息交替出现,说明三个进程在同时运行。而单进程只能顺序执行,所有任务必须一个一个完成。
资源独立性,实验中每个进程都有自己的计数变量和进程ID,互不干扰。一个进程的崩溃不会影响其他进程,提供了更好的稳定性。
效率表现,多进程可以同时执行多个任务,充分利用多核CPU。实验2中父进程在等待子进程执行execvp()的同时,自己可以继续工作,提高了整体效率。
系统开销,多进程需要额外的创建和切换开销,每个进程都有独立的内存空间,比单进程消耗更多系统资源。
开发复杂度,多进程需要处理进程间通信和同步问题,开发调试更复杂。单进程逻辑简单,易于实现和维护。
更多推荐


所有评论(0)