我在 C 中使用管道时遇到了严重的问题。我应该从命令行接收参数(例如:./myprogram 123 45 67),一次一个字符地将参数读入缓冲区,将字符发送到子进程进行计数,然后将读取的总字符数返回给父进程。我的代码如下(注意:注释是我应该做的):
// Characters from command line arguments are sent to child process
// from parent process one at a time through pipe.
// Child process counts number of characters sent through pipe.
// Child process returns number of characters counted to parent process.
// Parent process prints number of characters counted by child process.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
static int toChild[2];
static int fromChild[2];
static char buffer;
int main(int argc, char **argv)
{
int status;
int nChars = 0;
pid_t pid;
pipe(toChild);
pipe(fromChild);
if ((pid = fork()) == -1) {
printf("fork error %d\n", pid);
return -1;
}
else if (pid == 0) {
close(toChild[1]);
close(fromChild[0]);
// Receive characters from parent process via pipe
// one at a time, and count them.
int count = 0;
printf("child about to read\n");
while(read(toChild[0], &buffer, 1)){
count++;
}
// Return number of characters counted to parent process.
write(fromChild[1], &count, sizeof(count));
close(toChild[0]);
close(fromChild[1]);
printf("child exits\n");
}
else {
close(toChild[0]);
close(fromChild[1]);
// -- running in parent process --
printf("CS201 - Assignment 3 - Chris Gavette\n");
write(toChild[1], &argv[1], 1);
// Send characters from command line arguments starting with
// argv[1] one at a time through pipe to child process.
read(fromChild[0], &nChars, 1);
// Wait for child process to return. Reap child process.
// Receive number of characters counted via the value
// returned when the child process is reaped.
close(toChild[1]);
close(fromChild[0]);
waitpid(pid, &status, 0);
printf("child counted %d chars\n", nChars);
printf("parent exits\n");
return 0;
}
}
Run Code Online (Sandbox Code Playgroud)
即使我关闭了两个管道的两端,子进程似乎也挂了。
对于初学者来说,这是错误的。
write(toChild[1], &count, 1)
Run Code Online (Sandbox Code Playgroud)
它最终会导致你的问题。count是int,不是char或unsigned char。您需要发送sizeof(count). 此外,遇到错误时的读取函数将返回 EOF,该值非零,因此您的子退出条件不合适。它应该看起来像这样:
while(read(toChild[0], &buffer, 1) == 1)
Run Code Online (Sandbox Code Playgroud)
最后,您的父进程应该循环遍历每个参数,并将argv[]每个参数作为strlen大小的缓冲区发送。
我几乎可以肯定这就是你想要做的。请注意,为了保持清楚了解哪个描述符用于特定目的,我更喜欢使用 a#define来记录每个进程用于读取和写入的内容。顺便说一句,这可以扩展到任意数量的进程,我确信这对于您的下一个任务来说不会太远:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
// P0_READ - parent read source
// P0_WRITE - parent write target
// P1_READ - child read source
// P1_WRITE - child write target
#define P0_READ 0
#define P1_WRITE 1
#define P1_READ 2
#define P0_WRITE 3
#define N_PIPES 4
int main(int argc, char **argv)
{
int fd[N_PIPES], count = 0, i;
pid_t pid;
char c;
if (pipe(fd) || pipe(fd+2))
{
perror("Failed to open pipe(s)");
return EXIT_FAILURE;
}
// fork child process
if ((pid = fork()) == -1)
{
perror("Failed to fork child process");
return EXIT_FAILURE;
}
// child process
if (pid == 0)
{
// close non P1 descriptors
close(fd[P0_READ]);
close(fd[P0_WRITE]);
// get chars from input pipe, counting each one.
while(read(fd[P1_READ], &c, 1) == 1)
count++;
printf("Child: count = %d\n", count);
write(fd[P1_WRITE], &count, sizeof(count));
// close remaining descriptors
close(fd[P1_READ]);
close(fd[P1_WRITE]);
return EXIT_SUCCESS;
}
// parent process. start by closing unused descriptors
close(fd[P1_READ]);
close(fd[P1_WRITE]);
// send each arg
for (i=1; i<argc; ++i)
write(fd[P0_WRITE], argv[i], strlen(argv[i]));
// finished sending args
close(fd[P0_WRITE]);
// Wait for child process to return.
wait(NULL);
// wait for total count
if (read(fd[P0_READ], &count, sizeof(count)) == sizeof(count))
printf("Parent: count = %d\n", count);
// close last descriptor
close(fd[P0_READ]);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输入
./progname argOne argTwo
Run Code Online (Sandbox Code Playgroud)
输出
Child: count = 12
Parent: count = 12
Run Code Online (Sandbox Code Playgroud)
编辑:具有子返回状态的单个管道
从原始问题的评论看来,您的作业可能会要求将子进程的返回状态作为结果计数,而不是在管道中返回它。为此,您可以使用单个管道描述符对来完成此操作。我更喜欢第一种方法,但这也有效:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
// P0_WRITE - parent write target
// P1_READ - child read source
#define P1_READ 0
#define P0_WRITE 1
#define N_PIPES 2
int main(int argc, char **argv)
{
int fd[N_PIPES], count = 0;
pid_t pid;
char c;
if (pipe(fd))
{
perror("Failed to open pipe(s)");
return EXIT_FAILURE;
}
// fork child process
pid = fork();
if (pid == -1)
{
perror("Failed to fork child process");
return EXIT_FAILURE;
}
if (pid == 0)
{
// close non P1 descriptors
close(fd[P0_WRITE]);
// Return number of characters counted to parent process.
while(read(fd[P1_READ], &c, 1) == 1)
++count;
close(fd[P1_READ]);
printf("Child: count = %d\n", count);
return count;
}
// parent process. start by closing unused descriptors
close(fd[P1_READ]);
// eacn each arg entirely
for (int i=1; i<argc; ++i)
write(fd[P0_WRITE], argv[i], strlen(argv[i]));
// finished sending args
close(fd[P0_WRITE]);
// Wait for child process to return.
if (wait(&count) == -1)
{
perror("Failed to wait for child process");
return EXIT_FAILURE;
}
printf("Parent: count = %d\n", WEXITSTATUS(count));
return 0;
}
Run Code Online (Sandbox Code Playgroud)
结果是相同的,但请注意,这是一种调试方法,因为大多数调试器都会在您的子进程上发出信号,并且真正的退出状态会丢失。例如,在我的 Mac 上,在 Xcode 下运行:
Failed to wait for child process: Interrupted system call
Run Code Online (Sandbox Code Playgroud)
从命令行运行时给出:
Child: count = 12
Parent: count = 12
Run Code Online (Sandbox Code Playgroud)
我更喜欢双管道方法的众多原因之一。