在子进程中读取

Reading in child process

我的作业有问题。

我必须在父进程死后从子进程的终端读取数据。上面写得很清楚,父进程必须在执行子进程后立即结束,所以我找到的解决方案(例如使用 wait() )对我没有用。

我的代码

int main(void)
{ 
    printf("start main\n");
    if(fork() == 0){
        char buffer[64];
        fgets(buffer, 64, stdin);
        printf("Child process: %s\n", buffer);
    }
    else printf("end main\n");
    //Using WAIT() here is not allowed in my assignment.
    return 0;
}

它不等我输入数据。看来父进程结束后,子进程在后台,无法从终端读取任何数据。

结果

damian@damian-Virtualbox:-$ ./testuje
start main
end main
damian@damian-Virtualbox:-$ Child Process: 
echo test | ./testuje
start main
end
damian@damian-Virtualbox:-$ Child Process: test

程序应该做什么

print: start main
print: end main
then it should:
wait for user to type something
print: child process: text_typed_by_user

编辑:有人建议我使用 tee 命令。你知道如何使用它来实现我想要的吗?

您可能想使用 vfork 而不是 fork(检查 documentation here):

pid_t vfork(void);

vfork - create a child process and block parent

使用 wait 不是一个选项,因为它旨在供 parent 进程用来监视 child 进程的退出状态。

同步 child 进程与 parent 退出事件的一种可能方法是利用所有打开的文件描述符在进程终止时关闭这一事实。因此,用 socketpair 打开一对连接的套接字可以用来通知 child parent 已经退出。默认情况下,socketpair 将创建阻塞套接字,当 parent 退出且 sp[0] 关闭时,它将通知 child 中的 sp[1] 并且 read 将 return 0. 未经测试的代码如下:

int sp[2];

socketpair(AF_UNIX,SOCK_STREAM,0,sp); // needs error check
switch (fork()) {
    case -1: // error
        break;
    case 0: // parent
        close(sp[1]);
        ....
        exit; // will close sp[0] too
    default: // child
        close(sp[0]);
        read(sp[1],sp,sizeof(int)); // needs error check
        // here sp[0] was used as temp buffer
        ... // do your read
 }

编辑:还有另一种方法,但它看起来更简单而不是更好,因为它使用 CPU 旋转直到 parent 退出(假设这对练习没问题)。这可能会导致竞争条件:

// in child
pid_t ppid=getppid();

while (ppid==getppid()); // loop until parent dies

如果 parent 在第一次调用 getppid 之前退出,则可能会发生竞争。

这场竞赛可以通过保存 parent 进程 pid 来修复,同时仍在 parent 并且只在 child 中循环:

// in parent, before fork
pid_t ppid=getpid();
....
// in child after fork
while (ppid==getppid()); // loop until parent dies

你必须在 fork 后的 child 进程中调用 setsid()。当 parent 终止时,这将阻止对 child 的 SIGHUP。

if(fork() == 0){
    setsid();
    char buffer[64];
    fgets(buffer, 64, stdin);
    printf("Child process: %s\n", buffer);
    exit(EXIT_SUCCESS);
}

但是,主线程退出和 setsid() 调用之间可能存在竞争条件,您可能需要处理这个问题(丑陋但简单的 hack 是 [=18 中的 sleep(1) =] 在 fork()).

之后

您可以结合使用 setsid(与子进程创建新会话)和 __fpurge 来清除标准输入并避免破坏 fgets 输入:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio_ext.h>


int main(void)
{
    printf("start main\n");
    if(fork() == 0){
        char buffer[64];

        setsid();
        __fpurge(stdin);

        fgets(buffer, 64, stdin);
        printf("Child reads: '%s'\n", buffer);
    }
    else printf("end main\n");

    return 0;
}

注意: POSIX fflush() 的标准声明该行为在标准输入中未定义。所以你应该避免fflush(stdin)__fpurge(在 stdio_ext.h 中定义)是 this topic.

中提到的一种替代方法

我已经实现了我想通过 运行 实现的目标: tee | ./myprogram.