2.进程控制

execvp()函数.doc

fork()函数.doc

wait()函数.doc

一、系统调用功能概述

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)

说明

  1. 进程隔离性:父进程 PID ≠ 子进程 PID(5313 是子进程)
  2. 执行顺序不确定性:虽然这里先打印子进程输出,但不保证永远如此
  3. 资源回收wait() 成功回收子进程,避免僵尸进程
  4. 状态获取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()的同时,自己可以继续工作,提高了整体效率。

系统开销,多进程需要额外的创建和切换开销,每个进程都有独立的内存空间,比单进程消耗更多系统资源。

开发复杂度,多进程需要处理进程间通信和同步问题,开发调试更复杂。单进程逻辑简单,易于实现和维护。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐