【问题标题】:Unable to exit while loop after reading information written to pipe读取写入管道的信息后无法退出while循环
【发布时间】:2019-08-18 01:28:03
【问题描述】:

TLDR:您必须关闭所有子管道中所有管道的写入端。只有当没有进程的写入端仍然打开时,读取才会检测到 EOF。 感谢@Bodo

作为操作系统课程作业的一部分,我试图从x operand y 格式的文件中读取行并将这些行分配给不同的子进程,以便每个子进程都可以将这些行作为输入并进行计算并将其写入一个输出文件。

通过获得正确的结果,我觉得我快到了,但是在将所有写入的行读取到管道的读取端之后,我的代码似乎导致了一个无休止的 while 循环。

这里是相关代码sn -p

int child_work(int pipes[][2], int proc, int procid, FILE * out)
{
    int i;
    pid_t mypid;
    Expression exp;
    float result;
    int procidx = procid;
    char expression[MAIN_BUF_LEN];
    int r_val;
    printf("entered while loop for child process %d\n", mypid);
    while(1)
    {
        if ( (r_val = read(pipes[procid][0], expression, MAIN_BUF_LEN)) > 0)
        {
            printf("return values of read: %d\n", r_val);
            exp_readln(&exp, expression);
            result = exp_cal(&exp);
            printf("[#%d]: %d %0.3f\n", procidx, mypid, result);
            fprintf(out, "#%d: %d %0.3f\n", procidx, mypid, result);
            fflush(out);
            procidx += proc;
        }
        else
        {
            break;
        }
    }
    printf("exited while loop and reached end of child process %d\n", mypid);
    return 0;

int main(int argc, char **argv)
{
    if (argc != 4)
    {
        printf("not enough arguments");
        return 0;
    }

    const char *infile;  // Name of infile
    const char *outfile; // Name of outfile
    int proc;            // Number of child process to fork

    // Save arguments to variables
    infile = argv[1];
    outfile = argv[2];
    sscanf(argv[3], "%u", &proc);

    int pipes[proc][2]; // Pipes to be created
    pid_t child_pids[proc]; // store all the pids of children created

    int i; // Loop counter

    char buf[MAIN_BUF_LEN];
    Expression exp;

    FILE * in_ptr, *out_ptr;
    // Open infile with read-only, outfile with write and append.
    if ((in_ptr = fopen(infile, "r")) == NULL)
    {
        printf("Error in opening file. Ending program. \n");
        return 1;
    }
    out_ptr = fopen(outfile, "a+");

    // Get parent pid and print to outfile
    int ppid = getpid();
    fprintf(out_ptr, "%d\n", ppid);
    fflush(out_ptr);

    // $proc pipes should be created and saved to pipes[proc][2]
    for (i = 0; i < proc; ++i)
    {
        // TODO
        if (pipe(pipes[i]) == -1 )
        {
            printf("Pipe failed for pipe %d\n", i);
            return 1;
        }
    }

    // $proc child processes should be created.
    // Call child_work() immediately for each child.
    for (i = 0; i < proc; ++i)
    {
        int pid;
        // create child only if in parent process
        if (getpid() == ppid)
        {
            pid = fork();
            if (pid != 0)
                printf("created child with child pid %d\n", pid);
                child_pids[i] = pid;
        }

        if (pid == 0) // in child process
        {
            child_work(pipes, proc, i, out_ptr);
            break;
        }
        else if (pid < 0) // error in forking
        {
            printf("Fork failed.\n");
        }
    }

    // Close reading end of pipes for parent
    for (i = 0; i < proc; ++i)
    {
        // TODO
        if (getpid() == ppid)
            close(pipes[i][0]);
    }

    // Read lines and distribute the calculations to children in round-robin
    // style.
    // Stop when a empty line is read.

    char* line = NULL;
    size_t len = 0;
    ssize_t read = 0;
    int j = 0;
    while ((read = getline(&line, &len, in_ptr)) != -1) {
        //printf("Retrieved line of length %zu:\n", read);
        //printf("%s", line);
        j = j % proc;
        write(pipes[j++][1], line, strlen(line)+1);
    }

    // Close all the pipes when the task ends
    for (i = 0; i < proc; ++i)
    {
    //   close(pipes[i][READ]);
       close(pipes[i][WRITE]);
    }
    printf("Task 6 complete!");

    for (i = 0; i < proc; ++i)
    {
        waitpid(child_pids[i], NULL, 0);
    }

    fprintf(out_ptr, "\n");
    fflush(out_ptr);

    return 0;
}

这是我得到的输出,由于进程不会终止,它似乎陷入了无限的 while 循环。此外,return values of read: 的值应该是 22 或 23,具体取决于我正在使用的特定输入文件,但我不知道为什么它会为特定的后续子进程递增。似乎没有一个子进程能够退出 while 循环,因为这个 printf("exited while loop and reached end of child process %d\n", mypid); 似乎没有被执行。我的理解是,如果已经读取了一个管道,则返回值将是读取的行的字节大小,如果达到EOF或错误,则返回值分别为0或-1。

entered while loop for child process 16016
entered while loop for child process 16017
entered while loop for child process 16018
entered while loop for child process 16020
return values of read: 22
entered while loop for child process 16019
[#0]: 16016 1.783
return values of read: 22
return values of read: 22
[#2]: 16018 0.061
[#1]: 16017 0.195
return values of read: 22
return values of read: 22
[#5]: 16016 0.269
return values of read: 46
[#10]: 16016 1.231
return values of read: 22
return values of read: 22
[#6]: 16017 0.333
return values of read: 22
return values of read: 46
[#11]: 16017 1.684
[#7]: 16018 -0.734
return values of read: 46
[#12]: 16018 0.134
[#3]: 16019 0.778
return values of read: 68
[#4]: 16020 -0.362
return values of read: 68
[#9]: 16020 0.506
[#8]: 16019 -0.450

对于我可能犯的愚蠢错误,我将不胜感激。谢谢!

【问题讨论】:

  • 您应该edit 您的问题以显示写入管道的代码以及创建管道和分叉子项的代码。它会关闭写作结束吗?您是否关闭子进程中管道的写入端?无法保证您将准确读取写入另一端的数据量。您可能会得到更少,或者您可能会从 2 个或更多 write 调用中获得组合数据。
  • @Bodo 感谢您的及时反馈!我想让这个问题简短一些,这样人们就不会被一个冗长的问题吓倒:'(我更新了它,希望你能很快找到我犯的一些新手错误。

标签: c linux process operating-system pipe


【解决方案1】:

代码中有几个问题。

很遗憾,我无法编译它并修复错误,因为它不完整。

  1. 您不能像这样定义大小不恒定的数组。

    int pipes[proc][2]; // Pipes to be created
    

    我希望编译器会在这一行显示警告。
    您应该使用动态分配 (malloc) 或静态分配具有最大大小的数组,并检查 proc 是否不大于最大值。

  2. 您必须关闭所有子管道中所有管道的写入端。 read 将仅在没有进程的写入端仍处于打开状态时检测 EOF。

  3. 代替

    while(1)
    {
        if ( (r_val = read(pipes[procid][0], expression, MAIN_BUF_LEN)) > 0)
        {
            /*...*/
        }
        else
        {
            break;
        }
    }
    

    我建议

    while((r_val = read(pipes[procid][0], expression, MAIN_BUF_LEN)) > 0)
    {
            /*...*/
    }
    
  4. 代替

        pid = fork();
        if (pid != 0)
            printf("created child with child pid %d\n", pid);
    

    应该是

        pid = fork();
        if (pid > 0)
            printf("created child with child pid %d\n", pid);
    

    因为pid &lt; 0 是一个错误。

  5. 代替

    if (pid == 0) // in child process
    {
        child_work(pipes, proc, i, out_ptr);
        break;
    }
    

    使用

    if (pid == 0) // in child process
    {
        child_work(pipes, proc, i, out_ptr);
        return 0;
    }
    

    使用break;,子进程将在for 循环之后继续执行代码,该循环将在child_work 返回时读取文件并写入管道。

  6. 不能保证每个子节点都会在父节点writes 下一个数据之前从管道轮到read,因此它可能会在单个read 中获得两条或更多消息。在实际应用中,您还应该准备好处理不完整的readwrite 调用,并通过额外的readwrite 调用继续写入/读取剩余数据。

    我认为处理部分readwrite 的最简单方法是使用缓冲IO。您可以将fdopen 与管道的 wrtite 文件描述符或读取文件描述符一起使用,并将数据作为以换行符结尾的文本行写入/读取,例如分别为fprintffgets

【讨论】:

  • 首先,非常感谢您提供的详细信息!我不知道为什么,但是您提出的第一个问题对于我的特定环境来说不是问题并且编译得很好,但是我继续修复了除 6 之外的其余部分。确实,无限 while 循环是由于我没有正确关闭管道。但是,我仍然遇到child_work 中的read 函数每次都没有读取正确数量的数据的问题,我认为这与您提出的第 6 个问题有关。如何解决这个问题?很抱歉跟进另一个问题。
  • 我意识到这是剩下的主要问题,因为我已将 read(pipes[procid][READ], expression, MAIN_BUF_LEN) 替换为 read(pipes[procid][READ], expression, 22) 并且它导致具有 22 个字符的行的输出正确,但对于具有 23 个字符的行则变得混乱。
  • 我实际上最终将write 的第三个参数也更改为 MAIN_BUF_LEN,这解决了问题!我想这不是很优雅,因为它不会动态读取和写入所需的最小字节数,但它可以在所有行少于 1024 个字符的情况下工作。有没有更优雅的方法,或者你也会这样做?
猜你喜欢
  • 2010-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-18
  • 1970-01-01
相关资源
最近更新 更多