将字符从 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 看到。您的代码似乎另有假设。

为了使这一点更明显,我建议将变量声明移动到相应的流程代码中。两个进程只需要复制fdpid,其他变量具体到每个进程的任务。因此,您可以将 statusnChar 的声明移动到 parent 流程特定代码,并且您可以移动 charCounterreadbufferreadIn到 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) 手动将错误打印到 stderrperror(3) 的额外好处是将错误消息描述附加到您输入的文本中,因此通常是一个不错的选择。

示例:

if (pipe(fd) < 0) {
    perror("pipe(2) error");
    exit(EXIT_FAILURE);
}

您在整个代码中使用的其他函数也可能会失败,并且您再次忽略了(可能的)错误 return。 close(2)read(2) 一样会失败。处理错误,它们存在是有原因的。

  • 你使用readIn的方式是错误的。 readInread(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));

&argvchar ***类型,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.