【操作系统】(fork函数)课堂问题记录02(更新)

【操作系统】(fork)课堂问题记录

验证此实验需要在ubuntu中运行。若没有安装虚拟机,则下载MINGW,编写c程序,在Windows的cmd中运行程序。MINGW包含gcc和一系列工具,是windows下的gnu环境,让开发者在windows下可以写gnu的c/c++代码, 编译的结果是windows的可执行文件exe,PE格式的,在windows下运行。

Q:关于fork( )的基本知识

fork() 是一个用于创建进程的系统调用函数,在 Unix 和类 Unix 操作系统中非常常见。它用于通过复制当前进程来创建一个新进程,称为子进程。fork() 的执行结果在父进程和子进程中有所不同。

主要特点:

  • - 当 fork() 被调用时,它会创建一个与当前进程几乎完全相同的子进程。父进程的地址空间、环境变量等都会被子进程继承。
  • - 在 fork() 的返回值中:
  • - 父进程 中,fork() 返回子进程的 PID(进程 ID)
  • - 子进程 中,fork() 返回 0
  • - 如果 fork() 调用失败,则返回 -1
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

int main()
{
	int r = fork();
	if(r < 0)
	{
		fprintf(stderr,"fork failed\n");
		exit(1);
	}
	else if(r == 0)
	{
		printf("I am child process (pid:%d)\n",(int) getpid());
	}
	else
	{
		printf("I am parent process (pid:%d)\n",(int) getpid());
	}
	exit(0);
}

注意事项:

  1. 子进程与父进程共享同一份代码,但运行的上下文不同。在 fork() 调用后,父进程和子进程都会执行 fork() 后的代码,但它们通过不同的路径进行控制(根据 fork() 的返回值)。

  2. 独立的进程空间:尽管父子进程共享相同的内存内容,但它们是独立的,修改子进程中的变量不会影响父进程,反之亦然。

  3. 资源消耗fork() 通常会消耗一定的系统资源来复制进程。如果频繁使用 fork(),可能会影响系统性能。

运行以上程序:

1.先使用 gcc filename.c -o filename 编译代码,生成可执行文件
在这里插入图片描述

2.运行可执行文件

在这里插入图片描述

Q:fork()中关于子进程与父进程哪个先执行?

通过上面的实验可以发现,fork()调用一次,返回两次,一次是在父进程中返回子进程的进程ID,另一次是在子进程中返回0.fork函数子进程与父进程之间的顺序不确定。有可能父进程先执行,也有可能子进程先执行,取决于操作系统的调度机制。

Q:关于父进程和子进程。

1.并发执行:父进程和子进程是并发执行的,顺序不确定。

2.独立的进程:父子进程有各自独立的地址空间,子进程是父进程的副本,它们不会互相干扰。

3.资源共享:文件描述符等资源在进程中共享,但地址空间和执行流是独立的。

Q:能否控制父子进程的输出顺序?

在 Unix/Linux 系统中,fork() 创建的父进程和子进程是并发执行的,操作系统的调度程序决定它们的执行顺序,因此,默认情况下,父进程和子进程的输出顺序是无法直接控制的。但我们可以通过某些同步机制来实现对父子进程输出顺序的控制。

常见的同步方法:

  1. wait() 函数:父进程可以调用 wait() 来等待子进程执行完毕后再继续执行。这样可以保证父进程的输出在子进程之后。
//wait_fork.c

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();  // 创建新进程
    
    if (pid < 0) {
        // fork() 失败
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("This is the child process. PID: %d\n", getpid());
    } else {
        // 父进程等待子进程结束
        wait(NULL);  // 等待子进程结束
        printf("This is the parent process. PID: %d\n", getpid());
    }

    return 0;
}

在这里插入图片描述

在这个示例中,父进程调用了 wait(),因此它会等待子进程执行完后才会输出信息,从而确保子进程的输出在前,父进程的输出在后。

  1. 管道(pipe:通过使用管道,父进程和子进程可以通过读写管道进行通信,从而控制它们的执行顺序。

pipe() 是一种进程间通信机制。父进程和子进程可以通过管道来交换数据,从而控制它们的输出顺序。

//pipe_fork.c

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    int pipefd[2];  // 用于通信的管道
    pipe(pipefd);   // 创建管道
    
    pid_t pid = fork();  // 创建子进程
    
    if (pid < 0) {
        // fork() 失败
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程
        close(pipefd[0]);  // 关闭读取端
        printf("This is the child process. PID: %d\n", getpid());
        write(pipefd[1], "done", 4);  // 向管道写入完成标志
        close(pipefd[1]);  // 关闭写入端
    } else {
        // 父进程
        char buffer[5];
        close(pipefd[1]);  // 关闭写入端
        read(pipefd[0], buffer, 4);  // 等待读取子进程的信号
        buffer[4] = '\0';
        printf("This is the parent process. PID: %d\n", getpid());
        close(pipefd[0]);  // 关闭读取端
    }

    return 0;
}

在这里插入图片描述

在这个示例中,子进程向管道中写入数据,父进程通过管道读取数据,读取到数据后才继续执行,保证了子进程先执行并输出内容。

  1. 信号(signal:通过使用信号,父进程和子进程可以相互通知对方已经完成某些任务,协调执行顺序。

父子进程可以通过信号相互通知,确保输出顺序。例如,父进程可以通过信号等待子进程完成后再执行。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void child_done(int signum) {
    // 处理子进程完成的信号
}

int main() {
    signal(SIGUSR1, child_done);  // 父进程设置信号处理器
    
    pid_t pid = fork();  // 创建子进程
    
    if (pid < 0) {
        // fork() 失败
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("This is the child process. PID: %d\n", getpid());
        kill(getppid(), SIGUSR1);  // 发送信号给父进程
    } else {
        // 父进程等待信号
        pause();  // 等待子进程信号
        printf("This is the parent process. PID: %d\n", getpid());
    }

    return 0;
}

在这里插入图片描述

在这个示例中,子进程通过 kill() 发送 SIGUSR1 信号给父进程,父进程接收到信号后再继续执行,从而确保子进程先输出,父进程后输出。

Q:在fork之前定义一个变量并为变量赋初值,在父进程和子进程处理时改变变量的值,最后输出的是父进程里的还是子进程里面的?

在调用 fork() 函数之前定义并赋值的变量,在 fork() 之后,父进程和子进程各自会拥有一份相同的变量副本。也就是说,fork() 会将进程的内存空间(包括变量)复制一份给子进程。

因此,在 fork() 之后,父进程和子进程各自对变量的修改互不影响,两个进程拥有独立的内存空间。每个进程可以独立地修改该变量,而不会影响对方的值。

#include <stdio.h>
#include <unistd.h>

int main() {
    int x = 99;
    pid_t pid = fork();

    if (pid == 0) {
        // 子进程
        x = 33;
        printf("子进程: x = %d\n", x);
    } else if (pid > 0) {
        // 父进程
        x = 66;
        printf("父进程: x = %d\n", x);
    } else {
        // fork 失败
        printf("fork失败!\n");
    }

    return 0;
}

在这里插入图片描述

父进程和子进程各自修改的 x 变量不会互相影响,因为它们在不同的进程中。在 fork() 之后,父进程和子进程各自独立地拥有自己的变量副本,并且对变量的修改不会影响对方。因此,输出的是各自进程中的变量值。

Q:若改变的是全局变量,最终结果有何不同?

fork() 之前定义并赋值的全局变量与局部变量的行为类似。虽然它是全局的,但在调用 fork() 后,父进程和子进程会各自拥有该全局变量的副本。因为 fork() 会复制整个进程的地址空间,包括全局变量,因此父子进程对全局变量的修改互不影响。

#include <stdio.h>
#include <unistd.h>

int global_var = 10;  // 定义全局变量

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        // 子进程
        global_var = 20;
        printf("子进程: global_var = %d\n", global_var);
    } else if (pid > 0) {
        // 父进程
        global_var = 30;
        printf("父进程: global_var = %d\n", global_var);
    } else {
        // fork 失败
        printf("fork失败!\n");
    }

    return 0;
}

在这里插入图片描述

说明:

  1. 父进程和子进程拥有独立的全局变量副本:尽管全局变量在 fork() 之前被定义并赋值,但在 fork() 之后,父进程和子进程各自拥有该全局变量的独立副本,修改一方的全局变量不会影响另一方。
  2. 父子进程互不影响:在子进程中将全局变量设置为 20,在父进程中将其设置为 30,但这两个进程中的修改不会相互影响。
  3. 即使是全局变量,父子进程在 fork() 之后也有各自的副本,修改一方的全局变量不会影响另一方的全局变量。因此,父进程输出父进程修改后的值,子进程输出子进程修改后的值。

写在最后:

在课堂上的疑问课下一定要及时解答,不要越积越多。✿✿ヽ(°▽°)ノ✿()┏(^0^)┛

天天开心!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值