将字符从 parent 发送到 child 进程并将字符计数返回到 C 中的 parent
sending characters from parent to child process and returning char count to parent in C
因此,对于我的计算机系统 class 的作业,我需要在程序 运行s.
时在命令行中键入字符
这些字符(例如abcd ef
)将存储在argv[]
.
parent 通过管道将这些字符一次一个地发送到 child 进程,然后该进程计算字符数并忽略空格。发送所有字符后,child 然后 returns 它为 parent 报告的字符数。
当我尝试 运行 程序时,它告诉我 readIn
的值为 4,child 处理了 0 个字符并且 charCounter
是 2.
我觉得我很接近,但我遗漏了一些重要的东西:/ a
和 parent 过程中的 char 数组试图硬编码这些东西以查看如果它有效但我仍然不成功。任何帮助将不胜感激,谢谢!
// 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 <stdlib.h>
#include <stdio.h>
#include <unistd.h> // for fork()
#include <sys/types.h> // for pid_t
#include <sys/wait.h> // for waitpid()
int main(int argc, char **argv)
{
int fd[2];
pid_t pid;
int status;
int charCounter = 0;
int nChar = 0;
char readbuffer[80];
char readIn = 'a';
//char a[] = {'a', 'b', 'c', 'd'};
pipe(fd);
pid = fork();
if (pid < 0) {
printf("fork error %d\n", pid);
return -1;
}
else if (pid == 0) {
// code that runs in the child process
close(fd[1]);
while(readIn != 0)
{
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
printf("The value of readIn is %d\n", readIn);
if(readIn != ' ')
{
charCounter++;
}
}
close(fd[0]);
//open(fd[1]);
//write(fd[1], charCounter, sizeof(charCounter));
printf("The value of charCounter is %d\n", charCounter);
return charCounter;
}
else
{
// code that runs in the parent process
close(fd[0]);
write(fd[1], &argv, sizeof(argv));
//write(fd[1], &a, sizeof(a));
close(fd[1]);
//open(fd[0]);
//nChar = read(fd[0], readbuffer, sizeof(readbuffer));
nChar = charCounter;
printf("CS201 - Assignment 3 - Andy Grill\n");
printf("The child processed %d characters\n\n", nChar);
if (waitpid(pid, &status, 0) > 0)
{
if (WIFEXITED(status))
{
}
else if (WIFSIGNALED(status))
{
}
}
return 0;
}
}
你误用了管道。
管道是单向通信通道。您可以使用它将数据从 parent 进程发送到 child 进程,或者将数据从 child 进程发送到 parent。你不能两者都做——即使你在两个进程上都保持管道的读写通道打开,每个进程也永远不会知道什么时候轮到它从管道读取(例如,你最终可能会在 child 应该由 parent) 阅读。
将字符从 parent 发送到 child 的代码似乎大部分正确(下面有更多详细信息),但您需要重新设计 child 到 parent 的通信。现在,您有两个选项可以将结果从 child 发送到 parent:
- 使用另一个管道。在分叉 child-to-parent 通信之前,您设置了一个额外的管道。这使设计和代码变得复杂,因为现在您有 4 个文件描述符要从 2 个不同的管道进行管理,并且您需要小心关闭每个文件描述符的位置以确保进程不会挂起。这也可能有点矫枉过正,因为 child 只是向 parent.
发送一个数字
- Return 将 child 的结果作为退出值。这就是你现在正在做的,这是一个不错的选择。但是,您无法在 parent 中检索该信息:child 的终止状态告诉您已处理的字符数,您可以使用
waitpid(2)
获取此值,您已经这样做了,但是您永远不会查看 status
(其中包含您要查找的结果)。
记住 child 进程有自己的地址 space。尝试读取 parent 中的 charCounter
是没有意义的,因为 parent 从未修改过它。 child 进程有它自己的 charCounter
副本,所以任何修改只能被 child 看到。您的代码似乎另有假设。
为了使这一点更明显,我建议将变量声明移动到相应的流程代码中。两个进程只需要复制fd
和pid
,其他变量具体到每个进程的任务。因此,您可以将 status
和 nChar
的声明移动到 parent 流程特定代码,并且您可以移动 charCounter
、readbuffer
和 readIn
到 child。这将非常明显地表明变量在每个进程上是完全独立的。
现在,一些更具体的评论:
pipe(2)
可以 return 出错。您忽略了 return 值,您不应该这样做。至少,如果 pipe(2)
由于某种原因失败,您应该打印一条错误消息并终止。我还注意到您在 fork(2)
和 printf("fork error %d\n", pid);
中报告了错误。这不是正确的方法:fork(2)
和其他系统调用(和库调用)总是 return -1
出错并设置 errno
全局变量以指示原因.因此 printf()
将始终打印 fork error -1 无论错误原因是什么。这没有帮助。此外,它将错误消息打印到 stdout
,并且出于多种原因,应该将错误消息打印到 stderr
。所以我建议改用 perror(3)
,或者使用 fprintf(3)
手动将错误打印到 stderr
。 perror(3)
的额外好处是将错误消息描述附加到您输入的文本中,因此通常是一个不错的选择。
示例:
if (pipe(fd) < 0) {
perror("pipe(2) error");
exit(EXIT_FAILURE);
}
您在整个代码中使用的其他函数也可能会失败,并且您再次忽略了(可能的)错误 return。 close(2)
和 read(2)
一样会失败。处理错误,它们存在是有原因的。
你使用readIn
的方式是错误的。 readIn
是 read(2)
的结果,它 return 是读取的字符数(应该是 int
)。该代码使用 readIn
就好像它是下一个读取的字符一样。读取的字符存储在 readbuffer
中,而 readIn
会告诉您该缓冲区中有多少字符。因此,您使用 readIn
遍历缓冲区内容并计算字符数。像这样:
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
while (readIn > 0) {
int i;
for (i = 0; i < readIn; i++) {
if (readbuffer[i] != ' ') {
charCounter++;
}
}
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
}
现在,关于 parent 过程:
您没有将字符写入管道。这是没有意义的:
write(fd[1], &argv, sizeof(argv));
&argv
是char ***
类型,sizeof(argv)
和sizeof(char **)
是一样的,因为argv
是一个char **
。传入函数时不保留数组维度。
您需要手动循环 argv
并将每个条目写入管道,如下所示:
int i;
for (i = 1; i < argv; i++) {
size_t to_write = strlen(argv[i]);
ssize_t written = write(fd[1], argv[i], to_write);
if (written != to_write) {
if (written < 0)
perror("write(2) error");
else
fprintf(stderr, "Short write detected on argv[%d]: %zd/zd\n", i, written, to_write);
}
}
注意argv[0]
是程序名,所以i
从1开始。如果你想把argv[0]
也算进去,改成从0开始就可以了。
最后,正如我之前所说,您需要使用 waitpid(2)
获取的终止状态来获取 child 的实际计数 return。因此,您只能在 waitpid(2)
returned 并确保 child 正常终止后打印结果。此外,要获取实际的退出代码,您需要使用 WEXITSTATUS
宏(只有在 WIFEXITED
return 为真时才能安全使用)。
所以这是福l 计划解决所有这些问题:
// 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 <stdlib.h>
#include <stdio.h>
#include <string.h> // for strlen()
#include <unistd.h> // for fork()
#include <sys/types.h> // for pid_t
#include <sys/wait.h> // for waitpid()
int main(int argc, char **argv)
{
int fd[2];
pid_t pid;
if (pipe(fd) < 0) {
perror("pipe(2) error");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid < 0) {
perror("fork(2) error");
exit(EXIT_FAILURE);
}
if (pid == 0) {
int readIn;
int charCounter = 0;
char readbuffer[80];
if (close(fd[1]) < 0) {
perror("close(2) failed on pipe's write channel");
/* We use abort() here so that the child terminates with SIGABRT
* and the parent knows that the exit code is not meaningful
*/
abort();
}
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
while (readIn > 0) {
int i;
for (i = 0; i < readIn; i++) {
if (readbuffer[i] != ' ') {
charCounter++;
}
}
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
}
if (readIn < 0) {
perror("read(2) error");
}
printf("The value of charCounter is %d\n", charCounter);
return charCounter;
} else {
int status;
if (close(fd[0]) < 0) {
perror("close(2) failed on pipe's read channel");
exit(EXIT_FAILURE);
}
int i;
for (i = 1; i < argc; i++) {
size_t to_write = strlen(argv[i]);
ssize_t written = write(fd[1], argv[i], to_write);
if (written != to_write) {
if (written < 0) {
perror("write(2) error");
} else {
fprintf(stderr, "Short write detected on argv[%d]: %zd/%zd\n", i, written, to_write);
}
}
}
if (close(fd[1]) < 0) {
perror("close(2) failed on pipe's write channel on parent");
exit(EXIT_FAILURE);
}
if (waitpid(pid, &status, 0) < 0) {
perror("waitpid(2) error");
exit(EXIT_FAILURE);
}
if (WIFEXITED(status)) {
printf("CS201 - Assignment 3 - Andy Grill\n");
printf("The child processed %d characters\n\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
fprintf(stderr, "Child terminated abnormally with signal %d\n", WTERMSIG(status));
} else {
fprintf(stderr, "Unknown child termination status\n");
}
return 0;
}
}
一些最后的笔记:
- shell 将参数拆分为 spaces,因此如果您以
./a.out this is a test
启动程序,代码将看不到单个 space。这是无关紧要的,因为 spaces 无论如何都应该被忽略,但是如果你想测试代码是否真的忽略了 spaces,你需要引用参数以便 shell不处理它们,如 ./a.out "this is a test" "hello world" "lalala"
.
- 仅使用程序退出代码的最右边(最低位)8 位,因此
WEXITSTATUS
永远不会 return 超过 255。如果 child 读取超过 255字符,该值将环绕,因此您有效地拥有一个以 256 为模的字符计数器。如果这是一个问题,那么您需要采用另一种方法并为 child-to-parent 通信设置第二个管道并写入结果在那里(并让 parent 阅读)。您可以在 man 2 waitpid
: 上确认
WEXITSTATUS(status)
returns the exit status of the child. This consists of the least
significant 8 bits of the status argument that the child
specified in a call to exit(3) or _exit(2) or as the argument for a return
statement in main(). This macro should be employed only if
WIFEXITED returned true.
因此,对于我的计算机系统 class 的作业,我需要在程序 运行s.
时在命令行中键入字符这些字符(例如abcd ef
)将存储在argv[]
.
parent 通过管道将这些字符一次一个地发送到 child 进程,然后该进程计算字符数并忽略空格。发送所有字符后,child 然后 returns 它为 parent 报告的字符数。
当我尝试 运行 程序时,它告诉我 readIn
的值为 4,child 处理了 0 个字符并且 charCounter
是 2.
我觉得我很接近,但我遗漏了一些重要的东西:/ a
和 parent 过程中的 char 数组试图硬编码这些东西以查看如果它有效但我仍然不成功。任何帮助将不胜感激,谢谢!
// 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 <stdlib.h>
#include <stdio.h>
#include <unistd.h> // for fork()
#include <sys/types.h> // for pid_t
#include <sys/wait.h> // for waitpid()
int main(int argc, char **argv)
{
int fd[2];
pid_t pid;
int status;
int charCounter = 0;
int nChar = 0;
char readbuffer[80];
char readIn = 'a';
//char a[] = {'a', 'b', 'c', 'd'};
pipe(fd);
pid = fork();
if (pid < 0) {
printf("fork error %d\n", pid);
return -1;
}
else if (pid == 0) {
// code that runs in the child process
close(fd[1]);
while(readIn != 0)
{
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
printf("The value of readIn is %d\n", readIn);
if(readIn != ' ')
{
charCounter++;
}
}
close(fd[0]);
//open(fd[1]);
//write(fd[1], charCounter, sizeof(charCounter));
printf("The value of charCounter is %d\n", charCounter);
return charCounter;
}
else
{
// code that runs in the parent process
close(fd[0]);
write(fd[1], &argv, sizeof(argv));
//write(fd[1], &a, sizeof(a));
close(fd[1]);
//open(fd[0]);
//nChar = read(fd[0], readbuffer, sizeof(readbuffer));
nChar = charCounter;
printf("CS201 - Assignment 3 - Andy Grill\n");
printf("The child processed %d characters\n\n", nChar);
if (waitpid(pid, &status, 0) > 0)
{
if (WIFEXITED(status))
{
}
else if (WIFSIGNALED(status))
{
}
}
return 0;
}
}
你误用了管道。
管道是单向通信通道。您可以使用它将数据从 parent 进程发送到 child 进程,或者将数据从 child 进程发送到 parent。你不能两者都做——即使你在两个进程上都保持管道的读写通道打开,每个进程也永远不会知道什么时候轮到它从管道读取(例如,你最终可能会在 child 应该由 parent) 阅读。
将字符从 parent 发送到 child 的代码似乎大部分正确(下面有更多详细信息),但您需要重新设计 child 到 parent 的通信。现在,您有两个选项可以将结果从 child 发送到 parent:
- 使用另一个管道。在分叉 child-to-parent 通信之前,您设置了一个额外的管道。这使设计和代码变得复杂,因为现在您有 4 个文件描述符要从 2 个不同的管道进行管理,并且您需要小心关闭每个文件描述符的位置以确保进程不会挂起。这也可能有点矫枉过正,因为 child 只是向 parent. 发送一个数字
- Return 将 child 的结果作为退出值。这就是你现在正在做的,这是一个不错的选择。但是,您无法在 parent 中检索该信息:child 的终止状态告诉您已处理的字符数,您可以使用
waitpid(2)
获取此值,您已经这样做了,但是您永远不会查看status
(其中包含您要查找的结果)。
记住 child 进程有自己的地址 space。尝试读取 parent 中的 charCounter
是没有意义的,因为 parent 从未修改过它。 child 进程有它自己的 charCounter
副本,所以任何修改只能被 child 看到。您的代码似乎另有假设。
为了使这一点更明显,我建议将变量声明移动到相应的流程代码中。两个进程只需要复制fd
和pid
,其他变量具体到每个进程的任务。因此,您可以将 status
和 nChar
的声明移动到 parent 流程特定代码,并且您可以移动 charCounter
、readbuffer
和 readIn
到 child。这将非常明显地表明变量在每个进程上是完全独立的。
现在,一些更具体的评论:
pipe(2)
可以 return 出错。您忽略了 return 值,您不应该这样做。至少,如果pipe(2)
由于某种原因失败,您应该打印一条错误消息并终止。我还注意到您在fork(2)
和printf("fork error %d\n", pid);
中报告了错误。这不是正确的方法:fork(2)
和其他系统调用(和库调用)总是 return-1
出错并设置errno
全局变量以指示原因.因此printf()
将始终打印 fork error -1 无论错误原因是什么。这没有帮助。此外,它将错误消息打印到stdout
,并且出于多种原因,应该将错误消息打印到stderr
。所以我建议改用perror(3)
,或者使用fprintf(3)
手动将错误打印到stderr
。perror(3)
的额外好处是将错误消息描述附加到您输入的文本中,因此通常是一个不错的选择。
示例:
if (pipe(fd) < 0) {
perror("pipe(2) error");
exit(EXIT_FAILURE);
}
您在整个代码中使用的其他函数也可能会失败,并且您再次忽略了(可能的)错误 return。 close(2)
和 read(2)
一样会失败。处理错误,它们存在是有原因的。
你使用
readIn
的方式是错误的。readIn
是read(2)
的结果,它 return 是读取的字符数(应该是int
)。该代码使用readIn
就好像它是下一个读取的字符一样。读取的字符存储在readbuffer
中,而readIn
会告诉您该缓冲区中有多少字符。因此,您使用readIn
遍历缓冲区内容并计算字符数。像这样:readIn = read(fd[0], readbuffer, sizeof(readbuffer)); while (readIn > 0) { int i; for (i = 0; i < readIn; i++) { if (readbuffer[i] != ' ') { charCounter++; } } readIn = read(fd[0], readbuffer, sizeof(readbuffer)); }
现在,关于 parent 过程:
您没有将字符写入管道。这是没有意义的:
write(fd[1], &argv, sizeof(argv));
&argv
是char ***
类型,sizeof(argv)
和sizeof(char **)
是一样的,因为argv
是一个char **
。传入函数时不保留数组维度。
您需要手动循环 argv
并将每个条目写入管道,如下所示:
int i;
for (i = 1; i < argv; i++) {
size_t to_write = strlen(argv[i]);
ssize_t written = write(fd[1], argv[i], to_write);
if (written != to_write) {
if (written < 0)
perror("write(2) error");
else
fprintf(stderr, "Short write detected on argv[%d]: %zd/zd\n", i, written, to_write);
}
}
注意argv[0]
是程序名,所以i
从1开始。如果你想把argv[0]
也算进去,改成从0开始就可以了。
最后,正如我之前所说,您需要使用 waitpid(2)
获取的终止状态来获取 child 的实际计数 return。因此,您只能在 waitpid(2)
returned 并确保 child 正常终止后打印结果。此外,要获取实际的退出代码,您需要使用 WEXITSTATUS
宏(只有在 WIFEXITED
return 为真时才能安全使用)。
所以这是福l 计划解决所有这些问题:
// 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 <stdlib.h>
#include <stdio.h>
#include <string.h> // for strlen()
#include <unistd.h> // for fork()
#include <sys/types.h> // for pid_t
#include <sys/wait.h> // for waitpid()
int main(int argc, char **argv)
{
int fd[2];
pid_t pid;
if (pipe(fd) < 0) {
perror("pipe(2) error");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid < 0) {
perror("fork(2) error");
exit(EXIT_FAILURE);
}
if (pid == 0) {
int readIn;
int charCounter = 0;
char readbuffer[80];
if (close(fd[1]) < 0) {
perror("close(2) failed on pipe's write channel");
/* We use abort() here so that the child terminates with SIGABRT
* and the parent knows that the exit code is not meaningful
*/
abort();
}
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
while (readIn > 0) {
int i;
for (i = 0; i < readIn; i++) {
if (readbuffer[i] != ' ') {
charCounter++;
}
}
readIn = read(fd[0], readbuffer, sizeof(readbuffer));
}
if (readIn < 0) {
perror("read(2) error");
}
printf("The value of charCounter is %d\n", charCounter);
return charCounter;
} else {
int status;
if (close(fd[0]) < 0) {
perror("close(2) failed on pipe's read channel");
exit(EXIT_FAILURE);
}
int i;
for (i = 1; i < argc; i++) {
size_t to_write = strlen(argv[i]);
ssize_t written = write(fd[1], argv[i], to_write);
if (written != to_write) {
if (written < 0) {
perror("write(2) error");
} else {
fprintf(stderr, "Short write detected on argv[%d]: %zd/%zd\n", i, written, to_write);
}
}
}
if (close(fd[1]) < 0) {
perror("close(2) failed on pipe's write channel on parent");
exit(EXIT_FAILURE);
}
if (waitpid(pid, &status, 0) < 0) {
perror("waitpid(2) error");
exit(EXIT_FAILURE);
}
if (WIFEXITED(status)) {
printf("CS201 - Assignment 3 - Andy Grill\n");
printf("The child processed %d characters\n\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
fprintf(stderr, "Child terminated abnormally with signal %d\n", WTERMSIG(status));
} else {
fprintf(stderr, "Unknown child termination status\n");
}
return 0;
}
}
一些最后的笔记:
- shell 将参数拆分为 spaces,因此如果您以
./a.out this is a test
启动程序,代码将看不到单个 space。这是无关紧要的,因为 spaces 无论如何都应该被忽略,但是如果你想测试代码是否真的忽略了 spaces,你需要引用参数以便 shell不处理它们,如./a.out "this is a test" "hello world" "lalala"
. - 仅使用程序退出代码的最右边(最低位)8 位,因此
WEXITSTATUS
永远不会 return 超过 255。如果 child 读取超过 255字符,该值将环绕,因此您有效地拥有一个以 256 为模的字符计数器。如果这是一个问题,那么您需要采用另一种方法并为 child-to-parent 通信设置第二个管道并写入结果在那里(并让 parent 阅读)。您可以在man 2 waitpid
: 上确认
WEXITSTATUS(status)
returns the exit status of the child. This consists of the least significant 8 bits of the status argument that the child specified in a call to exit(3) or _exit(2) or as the argument for a return statement in main(). This macro should be employed only if WIFEXITED returned true.