【问题标题】:Control flow of fork system call when wait is present or not present存在或不存在等待时 fork 系统调用的控制流程
【发布时间】:2021-11-07 16:47:58
【问题描述】:

在这段代码中(在linux上运行):

void child_process()
{
    int count=0;

    for(;count<1000;count++)
    {
        printf("Child Process: %04d\n",count);
    }
    printf("Child's process id: %d\n",getpid());
}

void parent_process()
{
    int count=0;

    for(;count<1000;count++)
    {
        printf("Parent Process: %04d\n",count);
    }
}


int main()
{
    pid_t pid;
    int   status;        

    if((pid = fork()) < 0)
    {
        printf("unable to create child process\n");
        exit(1);
    }
    
    if(pid == 0)
        child_process();
    

    if(pid > 0)
    {
        printf("Return value of wait: %d\n",wait();
        parent_process();
    }

    return 0;
}

如果代码中不存在wait(),则其中一个进程(子进程或父进程)将完成它的执行,然后将控制权交给 linux 终端,最后剩下的进程(子进程或父进程)将跑步。这种情况的输出是:

Parent Process: 0998
Parent Process: 0999
guest@debian:~/c$ Child Process: 0645   //Control given to terminal & then child process is again picked for processing
Child Process: 0646
Child Process: 0647

如果代码中出现wait(),执行流程应该是什么?

当调用fork() 时,必须创建一个包含父进程和子进程的进程树。上述代码在子进程处理结束时,通过wait()系统调用通知父子僵尸进程死亡,但是父子进程是两个独立的进程,控制权是否必须通过子进程结束后直接到父进程?(根本没有控制权给其他进程,如终端) - 如果是,那么就像子进程是父进程的一部分(就像从另一个函数调用的函数) .

【问题讨论】:

  • 捕获并打印来自系统调用的返回值(通过在代码中包含getpid()的返回值来识别正在打印的进程。
  • @JonathanLeffler 谢谢,子进程的 PID 的返回值和wait() 的返回值是一样的,但是不清楚控制权是否直接从子进程转移到父进程,中间没有其他进程(如未使用 wait() 时的终端。
  • 我相信没有 wait() 流程会跟随。 - 终端产生父进程,父进程产生子进程。子进程没有控制,它只能访问相同的标准输入和标准输出。一旦父进程完成,它就会退出,并将控制权返回给终端,而子进程在后台运行。使用wait:父进程产生子进程,然后执行wait,等待子进程完成。然后它执行剩下的代码结束退出。
  • 没有这样的保证;其他进程可以随时运行。但在正常用例中,不会有任何其他进程甚至试图从该终端读取或写入。唯一可能的候选者是 shell,它本身就是 wait()ing 让父级终止,而不是执行终端 I/O。
  • fork()wait() 的主要应用是让孩子调用exec* 函数之一,以运行另一个程序。您不能在子例程中执行此操作,因为 exec* 不会返回。这就是system() 的工作方式,这也是 shell 首先运行程序的方式。

标签: c operating-system fork wait


【解决方案1】:

此评论至少具有误导性:

   //Control given to terminal & then child process is again picked for processing

“终端”过程并没有真正进入等式。假设您使用终端仿真器与您的程序进行交互,它始终在运行。 (如果您使用的是控制台,则没有终端进程。但现在不太可能。)

控制用户界面的过程是您使用的任何外壳。你输入一些命令行,比如

$ ./a.out

shell 会安排你的程序运行。 (顺便说一下,shell 是一个普通的用户程序,没有特殊权限。你可以自己写。)

具体来说,外壳:

  1. 使用fork 创建子进程。
  2. 使用waitpid 等待子进程完成。

子进程设置任何必要的重定向,然后使用一些exec 系统调用,通常是execve,将自己替换为./a.out 程序,传递execve(或其他)您指定的命令行参数.

就是这样。

您的程序在./a.out 中使用fork 创建一个孩子,然后可能等待孩子完成后再终止。一旦你的父进程终止,shell 的waitpid() 就可以返回,一旦它返回,shell 就会打印一个新的命令提示符。

所以至少有三个相关的进程:shell、你的父进程和你的子进程。在没有像waitpid() 这样的同步功能的情况下,无法保证排序。因此,当您的父进程调用fork() 时,创建的子进程可以立即开始执行。或不。如果它确实立即开始执行,它不一定会抢占您的父进程,假设您的计算机相当现代并且具有多个核心。它们可以同时执行。但这不会持续很长时间,因为您的父进程要么立即调用exit,要么立即调用wait

当一个进程调用wait(或waitpid)时,它会被挂起并在它等待的进程终止时再次变为可运行状态。但同样没有保证。进程可运行这一事实并不意味着它将立即开始运行。但一般来说,在没有高负载的情况下,操作系统很快就会开始运行它。同样,它可能与另一个进程同时运行,例如您的子进程(如果您的父进程没有等待它完成)。

简而言之,如果您进行了一百万次实验,而您的父母等待您的孩子,那么您将看到相同的结果一百万次;孩子必须在父母解除暂停之前完成,而您的父母必须在外壳解除暂停之前完成。 (如果您的父进程在等待之前打印了一些内容,您会看到不同的结果;父进程和子进程的输出可以是任意顺序,甚至可以重叠。)

另一方面,如果您的父母不等孩子,那么您可能会看到许多结果中的任何一个,并且在一百万次重复中您可能会看到多个结果(但不是相同的概率)。由于父子之间没有同步,因此输出可以按任意顺序出现(或交错)。由于子进程与 shell 不同步,它的输出可能出现在 shell 提示符之前或之后,或者与 shell 提示符交错出现。没有保证,除了在你的父母完成之前,shell 不会恢复。

请注意,终端仿真器是一个完全独立的进程,始终可以运行。它拥有一个模拟终端的伪终端(“pty”)。伪终端是一种管道;在管道的一端是认为它正在与控制台通信的进程,而在另一端是终端仿真器,它解释写入 pty 的任何内容以便在 GUI 中呈现它,并发送任何击键它接收,适当地修改为通过管道返回的字符流。由于终端模拟器永远不会挂起,因此它的执行与您计算机上任何其他处于活动状态的进程交错,它会(或多或少)立即向您显示由您的 shell 发送的任何输出或它启动的进程。 (再次假设机器没有过载。)

【讨论】:

    猜你喜欢
    • 2019-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-13
    • 2015-08-17
    • 1970-01-01
    相关资源
    最近更新 更多