无法从 dup2() 和分叉后的管道读取。C



我正在编写一个echo字符串并sed两次的代码。我的输出是正确的,但是当我尝试将该字符串放在数组上时,它会阻塞read并继续进行其他调用。

代码如下:

#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
char **sendout=NULL;
int send_i=0;
void sender2(char* str_) {
int fd[2];
int fd1[2];
int fd2[2];
int pid;
char* echo[] = {"echo", str_, NULL};
char* sed[] = {"sed", "regex1", NULL};
char* sed2[] = {"sed", "regex2", NULL};
int status;
if (pipe(fd) < 0) {
exit(100);
}
pid = fork();
if (pid == 0) {
close(fd[0]);
dup2(fd[1], 1);
close(fd[1]);
execvp(echo[0], echo);
printf("Error in execvp1n");
}
if (pipe(fd1) < 0) {
exit(100);
}
pid = fork();
if (pid == 0) {
close(fd[1]);
close(fd1[0]);
dup2(fd[0], 0);
dup2(fd1[1], 1);
dup2(fd1[1], 2);
close(fd[0]);
close(fd1[1]);
execvp(sed2[0], sed2);
printf("Error in execvp2n");
}
if (pipe(fd2) < 0) {
exit(100);
}
pid = fork();
if (pid == 0) {
close(fd1[1]);
close(fd2[0]);
dup2(fd1[0], 0);
dup2(fd2[1], 1);
dup2(fd2[1], 2);
close(fd2[1]);
close(fd1[0]);
execvp(sed[0], sed);
}
pid = fork();
if (pid == 0) {
close(fd2[1]);
char* line = NULL;
size_t len = 0;
ssize_t read_;
FILE* f_pipe;
f_pipe = fdopen(fd2[0], "r");
printf("1n");
while ((read_ = getline(&line, &len, f_pipe)) != -1) {
printf("2n");
sendout = realloc(sendout, sizeof(char*) * (send_i + 1));
sendout[send_i] = strdup(line);
send_i++;
printf("%sn", line);
}
fclose(f_pipe);
close(fd2[0]);
return;
}
close(fd[1]);
close(fd[0]);
close(fd1[1]);
close(fd1[0]);
close(fd2[1]);
close(fd2[0]);
if (pid != 0) {
wait(&status);
}
}
int main() { 
sender2("hello"); 
}

就像我说的,这一切都一直工作到read.如果我将 3 个字符串传递给函数,输出如下所示:

1
1
1

如果我不重复到最后一个管道,它可以很好地打印我需要的东西,我还在最后一个分叉中使用了 return,因为它是唯一一个没有从execvp中杀死的子进程。但它甚至没有达到第一次打印。我什至尝试将管道作为文件打开或经典打开,所以我尝试打开并且也打开fopen,如您所见。我失败了,因为它无法读取任何内容。那将是一个时间问题。

分叉和文件描述符

分叉进程时,将继承所有文件描述符的副本。由于这些是副本,因此必须在子项和父项中关闭描述符。您应该始终尽快关闭它们。如果您分叉几次,则尤其如此。

在这里很容易错过一些东西。因此,最好非常仔细地检查所有文件描述符是否已关闭。

最小更改量

因此,代码获得结果的最小更改次数如下所示。

如果第 41 行中的第一个分叉成功,那么在父级中,您需要关闭管道文件描述符 fd[0] 和 fd[1],例如在第 56 行。

pid = fork();
if (pid == 0) {
...
}
close(fd[0]); //<-- add these two lines
close(fd[1]);
if (pipe(fd2) < 0) {
...

同样,您需要在 fd1 的第二个分叉之后执行相同的操作,因此:

pid = fork();
if (pid == 0) {
...
}
close(fd1[0]); //<-- add these two lines
close(fd1[1]);

pid = fork();

当你现在运行你的代码时,你已经得到作为输出:

1
2
hello

更好的测试用例

这还不会验证两个sed命令是否都能正常运行。对于测试用例,将 main 中的调用更改为:

sender2("hello mars");

并将 sed 命令更改为:

char* sed[] = {"sed", "s/moon/world/", NULL};
char* sed2[] = {"sed", "s/mars/moon/", NULL};

(sed2命令在代码中sed之前执行,如果在sed2之前执行sed会使代码更容易理解(

然后作为输出给出:

1
2
hello world

因此,两个 sed 命令都会被执行。

补充说明

以下是一些没有特别顺序的注释,主要是关于错误处理。

  • fork的调用返回pid_t而不是 int。因此,您应该将变量 pid 的定义更改为:pid_t pid;

  • 如果execvp失败,则应打印错误原因并以错误状态退出,例如:

    perror("execvp of command xyz failed");

    exit(EXIT_FAILURE);

  • 如果打开管道失败,还要在 stderr 上打印描述性消息。

  • 此外,fork调用可能失败,也应处理此问题。在这种情况下,fork 返回 -1。与上述相同,在 stderr 上打印错误消息并返回错误状态。

  • 在 main 中,您应该返回成功或失败状态(例如return EXIT_SUCCESS;(。

  • 您不使用变量read_。然后可以删除该变量。

  • 如果fdopen失败,则返回 NULL。应处理此错误情况。

  • 使用 realloc 分配的内存永远不会释放。

最新更新