直链: 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后,ifif(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()影响
Logo

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

更多推荐