c - c 中的管道,2 个 fork 与 main 对话

标签 c linux process pipe

好吧,简短的故事是:
我有一个需要做 X 的程序,它做了 0.25X。我使用 2 个 fork 和 4 个管道,我不知道如何调试它。 (在 linux env 中使用 eclipse c/c++)。

很长的故事:
我有一个程序需要从包含整数对的文本文件中计算 gcd(最大公约数)。这个程序有一个父亲(主)和两个 child (叉)需要通过管道与父亲交谈。 (每个 child 2 个管子。)
当我在 ubuntu 中编译并运行程序时,我没有收到任何错误,但程序没有完成其任务。我不知道它在哪里/为什么坏了。我该如何调试?我在 eclipse c/c++ 中编码,调试器无法处理 fork 。当我调试时,它从文件中读取所有数字(不计算 gcd)但是当我在 ubuntu 终端中运行时,它只读取第一行并中断。这是完整的代码:

int main(int argc, char **argv) {
if (argc != 2 || strcmp(argv[1], "--help") == 0) {
    fprintf(stderr, "‫‪usage: %s <FILE NAME>\n", argv[0]);
    return EXIT_FAILURE;
}

int pfd_child1_r[2], pfd_child1_w[2], pfd_child2_r[2], pfd_child2_w[2];
if (pipe(pfd_child1_r) == -1 || pipe(pfd_child1_w) == -1
        || pipe(pfd_child2_r) == -1 || pipe(pfd_child2_w) == -1) {
    perror("cannot pipe()");
    return EXIT_FAILURE;
}

createChilds(pfd_child1_r, pfd_child1_w, pfd_child2_r, pfd_child2_w);

FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
    perror("fopen(): ");
    return EXIT_FAILURE;
}

char line[100];
char *token;
int numbers[2], num, line_count = 1, counter = 0, result = 0;

while (fgets(line, sizeof(line), fp) != NULL) {
    token = strtok(line, " ");
    while (token != NULL) {
        num = atoi(token);
        if (num < 1 || counter == 2) {
            fprintf(stderr, "‫‪illegal‬‬ ‫‪input‬‬ at line %d\n",
                    line_count);
            return EXIT_FAILURE;
        }
        numbers[counter] = num;
        counter++;
        token = strtok(NULL, " ");
    }
    counter = 0;
    if (line_count % 2 == 0) { // use first child
        write(pfd_child1_w[1], &numbers[0], sizeof(int));
        write(pfd_child1_w[1], &numbers[1], sizeof(int));
    } else { // use second child
        write(pfd_child2_w[1], &numbers[0], sizeof(int));
        write(pfd_child2_w[1], &numbers[1], sizeof(int));
    }

    if (line_count > 1) { // after first run alternate to get result
        if (line_count % 2 == 0) { // read from second child
            read(pfd_child2_r[0], &result, sizeof(int));
            printf("%d %d\t\tgcd: %d\n", numbers[0], numbers[1], result);
        } else { // read from first child
            read(pfd_child1_r[0], &result, sizeof(int));
            printf("%d %d\t\tgcd: %d\n", numbers[0], numbers[1], result);
        }
    }

    line_count++;
}

fclose(fp);
return EXIT_SUCCESS;
}

void createChilds(int pfd_child1_r[2], int pfd_child1_w[2], int pfd_child2_r[2],

    int pfd_child2_w[2]) {

switch (fork()) {
case -1:
    perror("cannot fork()");
    exit(EXIT_FAILURE);

case 0: /* First child: */
    if (close(pfd_child1_r[0]) == -1) { /* Read end is unused */
        perror("cannot close()");
        exit(EXIT_FAILURE);
    }

    if (close(pfd_child1_w[1]) == -1) { /* Write end is unused */
        perror("cannot close()");
        exit(EXIT_FAILURE);
    }

    /* Duplicate stdout on write end of pipe; close duplicated descriptor */

    if (pfd_child1_w[1] != STDOUT_FILENO) { /* Defensive check */
        if (dup2(pfd_child1_r[1], STDOUT_FILENO) == -1) {
            perror("cannot dup2()");
            exit(EXIT_FAILURE);
        }
        if (close(pfd_child1_r[1]) == -1) {
            perror("cannot close()");
            exit(EXIT_FAILURE);
        }
    }

    /* Duplicate stdin on read end of pipe; close duplicated descriptor */

    if (pfd_child1_w[1] != STDIN_FILENO) { /* Defensive check */
        if (dup2(pfd_child1_w[0], STDIN_FILENO) == -1) {
            perror("cannot dup2()");
            exit(EXIT_FAILURE);
        }
        if (close(pfd_child1_w[0]) == -1) {
            perror("cannot close()");
            exit(EXIT_FAILURE);
        }
    }
    execlp("./v1_child", "./v1_child", NULL); /* Writes to pipe */
    exit(EXIT_SUCCESS);

default: /* Parent go to next child */
    break;
}

switch (fork()) {
case -1:
    perror("cannot fork()");
    exit(EXIT_FAILURE);

case 0: /* Second child: exec 'wc' to read from pipe */
    if (close(pfd_child2_r[0]) == -1) { /* Read end is unused */
        perror("cannot close()");
        exit(EXIT_FAILURE);
    }

    if (close(pfd_child2_w[1]) == -1) { /* Write end is unused */
        perror("cannot close()");
        exit(EXIT_FAILURE);
    }

    /* Duplicate stdout on write end of pipe; close duplicated descriptor */

    if (pfd_child2_w[1] != STDOUT_FILENO) { /* Defensive check */
        if (dup2(pfd_child2_r[1], STDOUT_FILENO) == -1) {
            perror("cannot dup2()");
            exit(EXIT_FAILURE);
        }
        if (close(pfd_child2_r[1]) == -1) {
            perror("cannot close()");
            exit(EXIT_FAILURE);
        }
    }

    /* Duplicate stdin on read end of pipe; close duplicated descriptor */

    if (pfd_child2_w[1] != STDIN_FILENO) { /* Defensive check */
        if (dup2(pfd_child2_w[0], STDIN_FILENO) == -1) {
            perror("cannot dup2()");
            exit(EXIT_FAILURE);
        }
        if (close(pfd_child2_w[0]) == -1) {
            perror("cannot close()");
            exit(EXIT_FAILURE);
        }
    }
    execlp("./v1_child", "./v1_child", NULL); /* Writes to pipe */
    exit(EXIT_SUCCESS);

default: /* Parent falls through */
    break;
}

/* Parent closes unused file descriptors for pipe */

if (close(pfd_child1_r[1]) == -1 || close(pfd_child1_w[0]) == -1
        || close(pfd_child2_r[1]) == -1 || close(pfd_child2_w[0]) == -1) {
    perror("cannot close()");
    exit(EXIT_FAILURE);
}

第二个文件是 gcd 文件,我还没有完成它,应该继续获取数字的循环不存在。但我只想让第一行正常工作,然后我会修复其余部分。
int gcd(int n1, int n2) {
    if (n2 == 0)
        return n1;
    return gcd(n2, n1 % n2);
}

int main(int argc, char **argv) {

    int n1, n2, result;
    if (scanf("%d %d", &n1,&n2) != 2) {
        fprintf(stderr, "error reading numbers in child\n");
        return -1;
    }
    if (n1 > n2)
        result = gcd(n1, n2);
    else
        result = gcd(n2,n1);
    printf("%d", result);
}

最佳答案

如何调试

一种简单的调试方法总是添加 fprintf(stderr, "...")对子程序的声明。然后您可以运行该程序并查看子进程在做什么。

转移值

由于您重定向 stdin 和 stdout 并在计算 gcd 的 v1_child 程序中使用 sscanf/printf,我假设您想将值作为字符串传输。

一种简单的方法是使用 fprintf 将整数写入格式化字符串。您可以使用 fdopen 将流关联到现有的管道文件描述符。

因此,您必须将数字从字符串转换为字符串。

可变长度数据和缓冲 I/O

如果您使用字符串来传输值,则每对值都有一个可变长度。通常,在 C 程序中使用换行符来表示完整的输入记录。

读/写整行的另一个原因是读/写调用也只能传输部分字节数。因此,您必须知道输入记录何时完成。另一种方法是使用二进制格式,它会自动表示具有固定长度的格式。

通过使用流处理缓冲 I/O,使用 fflush 可以确保所有缓冲数据都通过流的底层写入函数写入。

函数

可以将特征分成几个功能,使流程更容易理解。

可能的改进

这或许已经是一个开始。

另一个可能的改进可能是使用 strtol 代替 atoi,因为 atoi 不执行错误检查。类似的 sscanf 不会报告转换错误(例如行尾的非数字字符),至少我们查看分配的输入项的数量。

大概还有可能提高代码的可读性。

使用 waitpid 可以在父级中检查子级的退出状态代码。

程序

您的代码在上面提到的几点中稍作修改,然后可能如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void create_pipe(int *);
void close_fd(int);
int child(const int *, const int *);
int read_input_line(FILE *fp, char *line, int max, int *numbers, int line_count);
void write_to_child(FILE *fp, const int *numbers);
int read_from_child(FILE *fp);


int main(int argc, char *argv[]) {

    if (argc != 2 || strcmp(argv[1], "--help") == 0) {
        fprintf(stderr, "‫‪usage: %s <FILE NAME>\n", argv[0]);
        return EXIT_FAILURE;
    }

    int pfd_child1_r[2];
    int pfd_child1_w[2];
    int pfd_child2_r[2];
    int pfd_child2_w[2];

    create_pipe(pfd_child1_r);
    create_pipe(pfd_child1_w);
    create_pipe(pfd_child2_r);
    create_pipe(pfd_child2_w);

    pid_t pid1 = fork();

    if (pid1 == 0) { //child 1
        close_fd(pfd_child2_r[0]);
        close_fd(pfd_child2_r[1]);
        close_fd(pfd_child2_w[0]);
        close_fd(pfd_child2_w[1]);
        return child(pfd_child1_r, pfd_child1_w);
    } else if (pid1 > 0) {
        close_fd(pfd_child1_r[1]);
        close_fd(pfd_child1_w[0]);

        pid_t pid2 = fork();
        if (pid2 == 0) { //child 2
            close_fd(pfd_child1_r[0]);
            close_fd(pfd_child1_w[1]);
            return child(pfd_child2_r, pfd_child2_w);
        } else if (pid2 > 0) {
            close_fd(pfd_child2_r[1]);
            close_fd(pfd_child2_w[0]);

            FILE *fp_child1_w = fdopen(pfd_child1_w[1], "w");
            FILE *fp_child2_w = fdopen(pfd_child2_w[1], "w");
            FILE *fp_child1_r = fdopen(pfd_child1_r[0], "r");
            FILE *fp_child2_r = fdopen(pfd_child2_r[0], "r");

            if (!fp_child1_w || !fp_child2_w || !fp_child1_r || !fp_child2_r) {
                perror("fdopen() failed");
                return EXIT_FAILURE;
            }

            FILE *fp = fopen(argv[1], "r");
            if (fp == NULL) {
                perror("fopen(): ");
                return EXIT_FAILURE;
            }

            char line[100];
            int numbers[2], line_count = 0;
            while (read_input_line(fp, line, sizeof(line), numbers, line_count) == 2) {
                if (line_count % 2 == 0) {
                    write_to_child(fp_child1_w, numbers);
                } else {
                    write_to_child(fp_child2_w, numbers);
                }

                if (line_count % 2 == 0) {
                    int result = read_from_child(fp_child1_r);
                    printf("%d %d\t\tgcd: %d\n", numbers[0], numbers[1], result);
                } else {
                    int result = read_from_child(fp_child2_r);
                    printf("%d %d\t\tgcd: %d\n", numbers[0], numbers[1], result);
                }
                line_count++;
            }

            //fclose closes also associated file descriptor
            fclose(fp_child1_w);
            fclose(fp_child2_w);
            fclose(fp_child1_r);
            fclose(fp_child2_r);

            fclose(fp);
            return EXIT_SUCCESS;
        } else {
            perror("second fork failed");
            return EXIT_FAILURE;
        }
    } else {
        perror("first fork failed");
        return EXIT_FAILURE;
    }
}


int read_input_line(FILE *fp, char *line, int max, int *numbers, int line_count) {
    char *token;
    int num, counter = 0;

    line[0] = '\0';
    if (fgets(line, max, fp) != NULL) {
        token = strtok(line, " ");
        while (token != NULL) {
            num = atoi(token);
            if (num < 1 || counter == 2) {
                fprintf(stderr, "‫‪illegal‬‬ ‫‪input‬‬ at line %d\n", line_count + 1);
                exit(EXIT_FAILURE);
            }
            numbers[counter] = num;
            counter++;
            token = strtok(NULL, " ");
        }
    }
    return counter;
}

int read_from_child(FILE *fp) {
    char buf[128];
    int result = -1;
    if (fgets(buf, sizeof(buf), fp)) {
        if (sscanf(buf, "%d", &result) == 1)
            return result;
    }
    return -1;
}

void write_to_child(FILE *fp, const int *numbers) {
    fprintf(fp, "%d %d\n", numbers[0], numbers[1]);
    fflush(fp);
}

int child(const int *pfd_child_r, const int *pfd_child_w) {
    dup2(pfd_child_r[1], STDOUT_FILENO);
    dup2(pfd_child_w[0], STDIN_FILENO);
    close_fd(pfd_child_r[0]);
    close_fd(pfd_child_r[1]);
    close_fd(pfd_child_w[0]);
    close_fd(pfd_child_w[1]);
    execlp("./v1_child", "./v1_child", NULL);
    fprintf(stderr, "execution of v1_child failed\n");
    exit(EXIT_FAILURE);
}

void create_pipe(int *fd) {
    if (pipe(fd) == -1) {
        perror("cannot pipe()");
        exit(EXIT_FAILURE);
    }
}

void close_fd(int fd) {
    if (close(fd) == -1) {
        perror("cannot close()");
        exit(EXIT_FAILURE);
    }
}

相应的 v1_child.c 可能如下所示:
#include <stdio.h>
#include <stdlib.h>

int gcd(int n1, int n2) {
    if (n2 == 0)
        return n1;
    return gcd(n2, n1 % n2);
}

int main(void) {

    int n1, n2, result;
    char buf[128];
    while(fgets(buf, sizeof(buf), stdin)) {
        if (sscanf(buf, "%d %d", &n1, &n2) != 2) {
            fprintf(stderr, "error reading numbers in child\n");
            return -1;
        }
        if (n1 > n2)
            result = gcd(n1, n2);
        else
            result = gcd(n2, n1);
        printf("%d\n", result);
        fflush(stdout);
    }
    return EXIT_SUCCESS;
}  

测试

随着输入
5 25
49 14
64 462
1155 84

输出将是
5 25        gcd: 5
49 14       gcd: 7
64 462      gcd: 2
1155 84     gcd: 21

关于c - c 中的管道,2 个 fork 与 main 对话,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61193226/

相关文章:

c++ - 终止 C++ 中的线程及其产生的任何进程

c - 是否有任何工具可以为有效的 C 程序生成报告

c - 如何释放Tree占用的内存,C?

linux - 如何在 Linux 中重定向生成的子进程的输出?

linux - 识别在 linux 上打开特定进程的文件

c# - ftp 进程不会关闭 (c#)

java - 注释在运行时会产生任何影响吗?

c - 新的类 C 语言支持 - Eclipse Xtext 与 Eclipse CDT 与 Netbeans(JavaCC,...)

c - 难以理解 CPU 调度概念

python - 使用 Python 的 os.walk 函数和 ls 命令获得不同的结果