3-实验三 进程管理1 fork调用
文章目录
直链: https://github.com/QINGFENG6666/lab_os-2025
或 https://pan.baidu.com/s/1QxZZgDtzx5Vw8BBwN8aaAg?pwd=tjju
实验目的
1、 加深对进程概念的理解,尤其是进程的动态性,并发性;
2、 了解父进程和子进程之间的关系;
3、 查看进程管理命令;
实验内容:
1.练习在shell环境下编译执行程序:
① 在vi编辑器中编写c语言源程序
② 用编译器gcc编译程序
③ 运行编译后生成的可执行文件
2.进程的创建:编写一段程序,使用系统调用fork()创建两个子进程。
① 当此程序运行时,在系统中有一个父进程和两个子进程活动。
② 让每一个进程在屏幕上显示一个字符:父进程显示“a”,子进程分别显示字符“b”和“c”。
③ 观察记录屏幕上的显示结果,并分析原因。
3.分析程序:在给出的例子程序基础上,根据要求进行修改,对执行结果进行分析。
实验步骤和实验数据记录:
1. 编译/执行练习
步骤:
vi sample.c→录入课本最简printf代码→:wq
gcc -o sample sample.c
./sample
结果:终端正常打印hello语句。
gcc –o指定输出文件名可避免a.out被覆盖。
2. 进程创建与输出字符
源程序fork_abc.c关键代码:
// fork_abc.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
pid_t p1, p2;
p1 = fork();
if (p1 == 0){
usleep(1000); // 子进程稍微等待
putchar('b');
fflush(stdout);
return 0;
}
p2 = fork();
if (p2 == 0){
usleep(1000); // 子进程稍微等待
putchar('c');
fflush(stdout);
return 0;
}
/* 父进程 */
usleep(2000); // 父进程等待更久,让子进程先执行
putchar('a');
fflush(stdout);
usleep(10000);
return 0;
}

这个报错的意思是:编译器在文件末尾没有遇到匹配的右花括号},导致return 0;这一行“悬在空中”。
修改之后即可运行。
经过增加进程间延迟后,观察到不同的输出顺序:cba,abc,bac,bca等。
abc:父进程→子进程1→子进程2
bac:子进程1→父进程→子进程2
bca:子进程1→子进程2→父进程
cba:子进程2→子进程1→父进程
结果分析:
1. 修改后的程序真正体现了进程执行的并发性和不确定性
2. 三个进程的执行顺序每次都可能不同,取决于操作系统的实时调度
3. 这说明进程调度是动态的,受系统状态影响
4. 验证了进程的并发执行特性
结论:实验成功验证了使用fork()创建的子进程与父进程之间是并发执行的关系,执行顺序具有不确定性。
3. 子进程对存储空间的复制
代码如下:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h> /* 头文件必须加 */
int main(void)
{
int x, i;
printf("Input a initial value for i: ");
scanf("%d", &i);
while ((x = fork()) == -1); /* 创建子进程 */
if (x == 0) { /* 子进程 */
printf("When child runs, i=%d\n", i);
printf("Input a value in child: ");
scanf("%d", &i);
printf("i=%d\n", i);
} else { /* 父进程 */
wait(NULL); /* 正确调用 */
printf("After child runs, in parent, i=%d\n", i);
}
return 0;
}
出现这条警告:
main.c:21:9:warning:implicit declaration of function 'wait' [-Wimplicit-function-declaration]
是因为在程序里调用了wait(),却没有包含它的头文件。
在Linux/Unix下,wait()的原型在<sys/wait.h>里,加上即可消除警告,结果也会正常。
预期结果:
我预期程序的执行顺序是:
1. 程序启动:提示输入初始值,比如输入10
2. fork()执行:创建子进程,子进程复制父进程的所有变量(此时i=10)
3. 子进程执行:
※ 输出When child runs,i=10
※ 提示输入新值,比如输入20
※ 输出i=20
※ 子进程结束
4. 父进程执行:
※ wait()等待子进程结束
※ 输出After child runs, in parent,i=10

分析:
fork()创建子进程时,子进程会获得父进程整个地址空间的副本(Copy-On-Write机制)这意味着:
① 子进程中的变量i是父进程变量i的独立副本
② 在子进程中修改i不会影响父进程中的i
③ 两个进程有各自独立的存储空间
4. 父子执行顺序分析(fork_wait.c)
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h> // 添加wait()函数的头文件
int main(void)
{
pid_t pid; // 声明pid变量
// ①
pid = fork();
// ②
if(pid == 0) {
sleep(3);
printf("Child: pid=%d, ppid=%d\n", getpid(), getppid());
} else {
printf("Parent: Child=%d, pid=%d, ppid=%d\n", pid, getpid(), getppid());
wait(NULL);
printf("After Child ends.\n");
}
printf("In which process?\n");
return 0;
}
预期执行流程:
1. 父进程创建子进程:fork()返回子进程PID给父进程,返回0给子进程
2. 父子进程并发执行:
① 子进程:睡眠3秒→输出自己的PID和父进程PID
② 父进程:立即输出子进程PID、自身PID和父进程PID→等待子进程结束
3. 输出顺序:
① 父进程信息先输出
② 因为子进程要睡眠3秒,父进程阻塞等待
③ 3秒后子进程输出信息
④ 父进程收到子进程结束信号,输出结束信息
⑤ 两个进程都会执行最后的printf

错误1:缺少 #
include <sys/types.h> // 错误
#include <sys/types.h> // 正确
错误2:变量pid未声明
需要先声明pid_t pid
错误3:缺少必要的头文件
需要添加 #include <sys/wait.h>来使用wait()
分析:
1. 子进程的ppid(2708)=父进程的pid(2708)
2. 父进程的ppid(2348)是shell进程的PID
3. 父进程的"After Child ends."在子进程结束后才输出
4. 证明了wait()的阻塞等待功能正常工作
5. 两个进程都执行了最后的printf语句
6. 体现了fork()的"复制但共享代码"特性
5. 修改程序验证父子进程关系
parent_first.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void) {
pid_t pid = fork(); // 创建子进程
if(pid == 0) { // 子进程
printf("[Child] 启动 - PID: %d, 父进程PID: %d\n", getpid(), getppid());
printf("[Child] 将睡眠3秒,等待父进程结束...\n");
sleep(3);
printf("[Child] 醒来后 - PID: %d, 新父进程PID: %d (应为n",
getpid(), getppid());
} else { // 父进程
printf("[Parent] 启动 - 子进程PID: %d, 自身PID: %d, 父进程PID: %d\n",
pid, getpid(), getppid());
printf("[Parent] 立即退出,不等待子进程...\n");
exit(0); // 父进程直接退出
}
printf("[%s] 进程结束 - PID: %d\n",
(pid == 0) ? "Child" : "Parent", getpid());
return 0;
}
结果分析:
1. 父进程输出:
[Parent] 启动 - 子进程 3085, 自身PID: 3084, 父进程PID: 2714
① 父进程PID=3084创建了子进程PID=3085
② 父进程自己的父进程PID=2714
[Parent] 立即退出,不等待子进程…
① 父进程执行exit(0)立即退出
2. Shell提示符重现:
tzl@tzl-VirtualBox:~/桌面$
① 父进程退出后,shell重新获得控制权并显示提示符
3. 子进程输出:
[Child] 启动 - PID: 3085, 父进程PID: 2100
① 子进程启动时看到的父进程PID=2100(不是预期的3084,这说明父进程已经退出,子进程被其他进程(2100)收养
② [Child] 将睡眠3秒,等待父进程结束…
③ [Child] 醒来后 - PID: 3085, 新父进程PID: 2100
④ 3秒后子进程醒来,发现父进程仍然是2100
⑤ [Child] 进程结束 - PID: 3085
问题讨论:
1. 输出语句在位置①(fork前)
修改后的代码:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h> // 添加wait()函数的头文件
int main(void)
{
pid_t pid; // 声明pid变量
printf("In which process?\n"); // ① - 移到fork前
pid = fork();
// ②
if(pid == 0) {
sleep(3);
printf("Child: pid=%d, ppid=%d\n", getpid(), getppid());
} else {
printf("Parent: Child=%d, pid=%d, ppid=%d\n", pid, getpid(), getppid());
wait(NULL);
printf("After Child ends.\n");
}
}
预期结果:
只输出一次"In which process?"
在父进程中执行,子进程不会执行这行代码
因为fork()是在这条语句之后执行的
分析原因:
fork()复制的是执行到该点时的进程状态
输出语句在fork()之前,只有原始进程(父进程)执行了它
子进程从fork()之后开始执行,不会"回头"执行之前的代码
2. 输出语句在位置②(fork后,if前)
修改后的代码:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h> // 添加wait()函数的头文件
int main(void)
{
pid_t pid; // 声明pid变量
// ①
pid = fork();
printf("In which process?\n"); // ② 移到fork后,if前
if(pid == 0) {
sleep(3);
printf("Child: pid=%d, ppid=%d\n", getpid(), getppid());
} else {
printf("Parent: Child=%d, pid=%d, ppid=%d\n", pid, getpid(), getppid());
wait(NULL);
printf("After Child ends.\n");
}
}
预期结果:
输出两次"In which process?"
父子进程都会执行这行代码
但执行顺序不确定,可能父进程先输出,也可能子进程先输出
分析原因:
fork()之后有两个并发执行的进程
输出语句在条件判断之前,两个进程都会执行
体现了真正的并发性,输出顺序由调度器决定
3. 总结:
| 位置 | 输出次数 | 执行进程 | 原因分析 |
|---|---|---|---|
| ① fork前 | 1次 | 只有父进程 | fork()复制的是当前状态,不会执行之前的代码 |
| ② fork后if前 | 2次 | 父子进程都执行 | 两个并发进程都会执行fork()后的代码 |
| ③ if-else后 | 2次 | 父子进程都执行 | 代码共享,但执行顺序受wait()影响 |
更多推荐


所有评论(0)