【问题标题】:Two children of same parent are not communicating using pipe if parent do not call wait()如果父级不调用 wait(),则同一父级的两个子级不使用管道进行通信
【发布时间】:2015-09-08 14:09:00
【问题描述】:

请看下面的代码:

#include<stdio.h>

main(){
        int pid, fds[2], pid1;
        char buf[200];
        pipe(fds);
        pid = fork();

        if(pid==0)
        {
                close(fds[0]);
                scanf("%s", &buf);
                write(fds[1], buf, sizeof(buf)+1);
        }
        else
        {
                pid1 = fork();

                if(pid1==0)
                {
                        close(fds[1]);
                        read(fds[0], buf, sizeof(buf)+1);
                        printf("%s\n", buf);
                }
                else
                {
       Line1:              wait();
                }
        }
}

如果我不注释掉 Line1,它工作正常。请看下面:

hduser@pc4:~/codes/c/os$ ./a.out
hello //*Entry from keyboard*
hello //Output
hduser@pc4:~/codes/c/os$ 

但是如果我注释掉Line1,两个子进程没有通信:

hduser@pc4:~/codes/c/os$ ./a.out
hduser@pc4:~/codes/c/os$ 
hi //*Entry from keyboard*
hi: command not found
hduser@pc4:~/codes/c/os$

这里我无法理解 wait() 的意义。

【问题讨论】:

  • read(fds[0], buf, sizeof(buf)+1); 是未定义的行为。它将 201 个字节读入一个 200 字节的数组中。
  • 调试的第一步是测试每个系统调用以查看失败的原因。写入标准错误。我也会写进度报告。你应该用半现代的 C(至少 C99)编写;这需要&lt;unistd.h&gt;main() 的正确声明。
  • 首先,'pid' 被定义为在 unistd.h 头文件中定义的 'pid_t',发布的代码丢失了。
  • 这两行:scanf("%s", &amp;buf); write(fds[1], buf, sizeof(buf)+1); 有几个问题: 1) 始终检查 scanf() 的返回值(不是参数值)以确保操作成功。 2) 使用 %s 输入/格式参数时,始终包含一个长度修饰符 (sizeof(buf)-1),这样用户就不会溢出缓冲区。 3) 要发送的字节数永远不会比缓冲区长(通常更短)建议:`write(fds[1], buf, strlen(buf)+1);'
  • 发布的代码存在严重的逻辑问题。 fork() 函数可以返回 3 种值:发生错误时为 -1,在子进程中执行时为 0,在父进程中执行时为某个正数。发布的代码未能检查对 fork() 的调用的错误情况

标签: c linux pipe ipc


【解决方案1】:

这里发生的是父进程在子进程完成之前完成执行。导致孩子无法使用终端。

让我们仔细看看这一切。

wait() 是做什么的?

wait() 系统调用暂停调用进程的执行,直到 它的一个孩子终止了。

你的程序是这样的

您的 main Process 派生了 2 个子进程。第一个写入管道,而另一个从管道读取。这一切都是在main process 继续执行时发生的。

当主进程执行它的代码时会发生什么?它终止。当它终止时,它放弃对终端的控制。这会导致孩子无法访问终端。

这就解释了为什么你会得到command not found——你输入的不是你程序的stdin,而是shell提示符本身。


您的代码也存在一些其他问题,

1) 在这部分代码中,

            scanf("%s", &buf);

这是错误的。你很不幸,没有遇到分段错误。由于buf 已经是一个地址,这应该是

            scanf("%s", buf);

2) 注意这一点,

            read(fds[0], buf, sizeof(buf)+1);

正如 cmets 部分所指出的,这是未定义的行为。您正在尝试读取更多数据并将其存储在较小的内存空间中。这 应该是,

            read(fds[0], buf, sizeof(buf));

3) 致电wait()。你已经创建了两个子进程,你应该等待它们都完成,所以你应该调用wait()两次

【讨论】:

    【解决方案2】:

    在修复了代码中的一些错误之后,我想出了一个半仪表化版本的程序,如下所示:

    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(void)
    {
        int pid, fds[2], pid1;
        char buf[200];
        pipe(fds);
        pid = fork();
    
        if (pid == 0)
        {
            close(fds[0]);
            printf("Prompt: "); fflush(0);
            if (scanf("%199s", buf) != 1)
                fprintf(stderr, "scanf() failed\n");
            else
                write(fds[1], buf, strlen(buf) + 1);
        }
        else
        {
            pid1 = fork();
    
            if (pid1 == 0)
            {
                close(fds[1]);
                if (read(fds[0], buf, sizeof(buf)) > 0)
                    printf("%s\n", buf);
                else
                    fprintf(stderr, "read() failed\n");
            }
            else
            {
    /*Line1:              wait();*/
            }
        }
        return 0;
    }
    

    在严格的选项下编译干净(Mac OS X 10.10.5 上的 GCC 5.1.0):

    gcc -O3 -g -std=c11 -Wall -Wextra -Werror p11.c -o p11 
    

    当我运行它时,输出是:

    $ ./p11
    Prompt: scanf() failed
    read() failed
    $ 
    

    问题很明确; scanf() 失败。问题:为什么

    wait() 版本需要一个额外的标头#include &lt;sys/wait.h&gt; 和正确的调用顺序。我使用了以下段落:

            else
            {
                printf("Kids are %d and %d\n", pid, pid1);
                int status;
                int corpse = wait(&status);
                printf("Parent gets PID %d status 0x%.4X\n", corpse, status);
            }
    

    编译运行后,现在的输出是:

    $ ./p11
    Kids are 20461 and 20462
    Prompt: Albatross
    Albatross
    Parent gets PID 20461 status 0x0000
    $
    

    那么,问题就变成了:当父进程不等待时,子进程的标准输入是如何或为什么关闭的?造成严重破坏的是 Bash 进行了一些工作控制。

    我再次升级了程序,使用int main(int argc, char **argv) 并测试该命令是否传递了任何参数:

            else if (argc > 1 && argv != 0) // Avoid compilation warning for unused argv
            {
                printf("Kids are %d and %d\n", pid, pid1);
                int status;
                int corpse = wait(&status);
                printf("Parent gets PID %d status 0x%.4X\n", corpse, status);
            }
    

    我有一个传家宝贝壳,它接近原始的伯恩贝壳。我在它下面运行了程序,它的行为和我预期的一样:

    $ ./p11
    Prompt: $ Albatross
    Albatross
    
    $ ./p11 1
    Kids are 20483 and 20484
    Prompt: Albatross
    Albatross
    Parent gets PID 20483 status 0x0000
    $
    

    注意第一次运行时Prompt: 后面的$;那是 shell 提示符,但是当我输入 Albatross 时,它(幸运的是)被 p11 进程的子进程读取。这不能保证;它可能是读取输入的外壳。在第二次运行中,我们看到了父母的输出,然后是孩子在工作,然后是父母退出消息。

    因此,在经典 shell 下,您的代码将按预期工作。 Bash 以某种方式干扰了子进程的正常运行。 Korn shell 的行为类似于 Bash。 C shell (tcsh) 也是如此。尝试dash,我得到了有趣的行为(3 次运行):

    $ ./p11
    Prompt: $ Albatross
    scanf() failed
    read() failed
    dash: 2: Albatross: not found
    $ ./p11
    Prompt: $ Albatross
    scanf() failed
    dash: 4: Albatross: not found
    $ read() failed
    
    $ ./p11
    Prompt: scanf() failed
    $ read() failed
    
    $ 
    

    请注意,前两次运行显示dash 正在读取输入,但孩子们直到我在输入信天翁后按回车后才发现问题。上次,孩子们在我打字之前就发现了问题。

    而且,回到 Bash,重定向标准输入“正常”工作:

    $ ./p11 <<< Albatross
    Prompt: Albatross
    $ ./p11 1 <<< Albatross
    Kids are 20555 and 20556
    Prompt: Albatross
    Parent gets PID 20555 status 0x0000
    $
    

    Albatross 的输出当然来自第二个孩子。

    答案将潜伏在作业控制外壳的某个行为中,但这足以让我想回到之前的生活。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-02
      • 2020-12-09
      • 1970-01-01
      相关资源
      最近更新 更多