我正在做 OSTEP 的作业,并做以下练习:
编写一个程序,创建两个子级,并将其中一个的标准输出连接到另一个的标准输入,使用 pipeline() 系统调用。
我是这样理解我需要做什么的:要连接第一个子进程的标准输出,我应该在父进程中使用
pipe(pipe_name)
制作管道,执行一个 fork()
创建第一个子进程,然后使用 dup2( pipe_name[1],STDOUT_FILENO);
更改标准输出流。之后,任何 printf
语句都会将文本发送到管道。然后我返回父进程并执行另一个 fork()
创建第二个子进程,并执行 dup2( pipe_name[0],STDIN_FILENO);
更改输入。
这是正确的吗?如果是这样,第二个孩子如何读取管道中的内容?我正在努力做
char words;
read(pipe_name[0], words, 1000);
但是我的输出只是
0
,而不是我之前打印的字符串。我的 C 语法有问题吗,还是更基础(我只是在这样做时学习 C,所以前者是肯定的可能性)。
这是我的完整代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int
main(int argc, char *argv[])
{ int pipe_name[2];
printf("hello world (pid:%d)\n", (int) getpid());
if(pipe(pipe_name)<0){ printf("mission failed! We'll gettem next time...\n");
} else {
int rc = fork();
if (rc < 0) {
// fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// child (new process)
printf("hello, I am child (pid:%d)\n", (int) getpid());
dup2( pipe_name[1],STDOUT_FILENO);
printf("hello, I am child (pid:%d)\n", (int) getpid());
} else {
// parent goes down this path (original process)
wait(NULL);
int rc2 = fork();
if (rc2 < 0) {
// fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc2 == 0) {
dup2( pipe_name[1],STDIN_FILENO);
char words;
read(STDIN_FILENO, words, 1000);
printf("My sibling says:\n %d\n", words);
} else { wait(NULL);
// parent goes down this path (original process)
printf("hello, I am parent of %d (pid:%d)\n",
rc2, (int) getpid());
}
}
return 0;
}}
输出是
hello world (pid:30421)
hello, I am child (pid:30422)
My sibling says:
0
hello, I am parent of 30423 (pid:30421)
有两个主要问题影响您的代码:
您打算如何将最多 1000 个字符读取为单个字符
char words; read(STDIN_FILENO, words, 1000);
如果读取字符,则需要将其格式化为字符串(
%s
,而不是%d
)。
将管道的写入端复制到标准输入意味着读取操作将失败
dup2(pipe_name[1],STDIN_FILENO);
1 必须为 0。
管道上写入的内容不会是空终止字符串;它将是一个字节数组。你必须确保它是一个字符串。
确保立即关闭管道未使用的末端是个好主意。这在这里并不是什么大问题,因为您只执行一次读取操作,而不是读取到 EOF。尽管如此,从一开始就养成良好的习惯还是有好处的。
此外,应在
stderr
上报告错误,并且当出现问题时程序应以非零状态退出。
这是解决这些问题的修订代码:
/* SO 7818-3904 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
static void wait_loop(void)
{
int corpse;
int status;
while ((corpse = wait(&status)) != -1)
{
fprintf(stderr, "%d: PID %d exited with status 0x%.4X\n",
(int)getpid(), corpse, status);
}
}
int
main(void)
{
int pipe_name[2];
printf("hello world (pid:%d)\n", (int)getpid());
if (pipe(pipe_name) < 0)
{
fprintf(stderr, "mission failed! We'll gettem next time...\n");
exit(1);
}
int rc1 = fork();
if (rc1 < 0)
{
fprintf(stderr, "fork 1 failed\n");
exit(1);
}
else if (rc1 == 0)
{
printf("hello, I am child 1 (pid:%d)\n", (int)getpid());
dup2(pipe_name[1], STDOUT_FILENO);
close(pipe_name[0]);
close(pipe_name[1]);
printf("hello, I am child 1 (pid:%d)\n", (int)getpid());
}
else
{
wait_loop(); /* Not really necessary here */
int rc2 = fork();
if (rc2 < 0)
{
fprintf(stderr, "fork 2 failed\n");
exit(1);
}
else if (rc2 == 0)
{
close(pipe_name[1]);
printf("hello, I am child 2 (pid:%d)\n", (int)getpid());
dup2(pipe_name[0], STDIN_FILENO);
close(pipe_name[0]);
char words[1000];
int nbytes = read(STDIN_FILENO, words, sizeof(words));
if (nbytes > 0)
{
words[nbytes] = '\0';
printf("My sibling says:\n%d: [%s]\n", nbytes, words);
}
else
{
fprintf(stderr, "%d: Got value %d from read(): %d %s\n",
(int)getpid(), nbytes, errno, strerror(errno));
exit(1);
}
}
else
{
close(pipe_name[0]);
close(pipe_name[1]);
wait_loop();
// parent goes down this path (original process)
printf("%d: hello, I am parent of %d and %d\n",
(int)getpid(), rc1, rc2);
}
}
return 0;
}
输出示例:
hello world (pid:23510)
hello, I am child 1 (pid:23511)
23510: PID 23511 exited with status 0x0000
hello, I am child 2 (pid:23512)
My sibling says:
32: [hello, I am child 1 (pid:23511)
]
23510: PID 23512 exited with status 0x0000
23510: hello, I am parent of 23511 and 23512
请注意,我将等待代码放入函数中并打印出返回的值 - 我认为
wait(NULL)
是草率的表现。您的进程可能有它不知道的子进程(不太可能,但有可能)。获取输出有助于调试。
第一个等待循环并不是真正必要的,尽管在这种情况下它并没有什么害处。但是,如果孩子们传输了足够的数据(目前超过 64 KiB,但 POSIX 只保证一个管道可以容纳 4 KiB),那么早期的等待循环将阻止读取器开始,而写入器将永远不会完成,因为它是等待某个进程清空管道,但这不会发生。