shell 中的输出重定向如何适用于 Linux 中 C 中的 fork() 生成的子进程?

How the Output Redirection in shell works for the child process produced by fork() in C in Linux?

我目前正在研究操作系统和并发性,我关于进程调度器的实践之一是使用 C 语言来弄清楚多个进程如何在 Linux 中工作 "parallel" 与 g运行 毫秒数。这是我的代码:

/* This file's name is Task05_3.c */
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

int kill(pid_t pid, int sig);
unsigned usleep(unsigned seconds);

#define NUMBER_OF_PROCESSES 7
#define MAX_EXPERIMENT_DURATION 4

long int getDifferenceInMilliSeconds(struct timeval start, struct timeval end)
{
    int seconds = end.tv_sec - start.tv_sec;
    int useconds = end.tv_usec - start.tv_usec;
    int mtime = (seconds * 1000 + useconds / 1000);
    return mtime;
}

int main(int argc, char const *argv[])
{
    struct timeval startTime, currentTime;
    int diff;

    int log[MAX_EXPERIMENT_DURATION + 2] = {-1};
    /* initialization */
    for (int k = 0; k < MAX_EXPERIMENT_DURATION + 2; ++k)
        log[k] = -1;

    gettimeofday(&startTime, NULL);

    pid_t pid_for_diss = 0;

    for (int i = 0; i < NUMBER_OF_PROCESSES; ++i)
    {
        pid_for_diss = fork();
        if (pid_for_diss < 0) {
            printf("fork error, errno(%d): %s\n", errno, strerror(errno));
        } else if (pid_for_diss == 0) {
            /* This loop is for logging when the child process is running */
            while (1) {
                gettimeofday(&currentTime, NULL);
                diff = getDifferenceInMilliSeconds(startTime, currentTime);
                if (diff > MAX_EXPERIMENT_DURATION)
                {
                    break;
                }
                log[diff] = i;
            }
            // for (int k = 0; k < MAX_EXPERIMENT_DURATION + 2; ++k)
            // {
            //     if (log[k] != -1)
            //     {
            //         printf("%d, %d\n", log[k], k);
            //     }
            // }
            // exit(0);
            break;
        }
    }

    /* This loop is for print the logged results out */
    if (pid_for_diss == 0)
    {
        for (int k = 0; k < MAX_EXPERIMENT_DURATION + 2; ++k)
        {
            if (log[k] != -1)
            {
                printf("%d, %d\n", log[k], k);
            }
        }
        kill(getpid(), SIGKILL);
    }

    int status;
    while (wait(&status) != -1);// -1 means wait() failed
    printf("Bye from the parent!\n");
}

基本上,我的想法是,我为父进程设置一个for循环,用fork()产生7个子进程,并将它们设置为一个while循环,迫使它们竞争使用[=43= 】 一段时间内。而每次子进程调度到运行时,我将父进程当前时间和开始时间的差值近似记录到一个属于运行ning子进程的数组中。然后在所有 7 个进程都打破了 while 循环之后,我为每个子进程设置了另一个 for 循环以打印出它们的记录结果。

但是,当我尝试将输出重定向到 Linux 机器中的 .csv 文件时,发生了一些奇怪的事情: 首先,我在主要 for 循环之外设置了打印循环(如您在我的代码中所见),然后我直接在 bash 中 运行 ./Task05_3 结果如下:

psyhq@bann:osc$ gcc -std=c99 Task05_3.c -o Task05_3
psyhq@bann:osc$ ./Task05_3
5, 0
4, 0
6, 0
4, 1
1, 0
4, 2
4, 3
4, 4
0, 0
1, 1
6, 1
1, 2
1, 3
1, 4
5, 1
5, 2
5, 3
5, 4
6, 2
6, 3
2, 0
6, 4
2, 1
2, 2
2, 3
2, 4
0, 1
3, 0
0, 2
0, 3
0, 4
3, 1
3, 2
3, 3
3, 4
Bye from the parent!
psyhq@bann:osc$

这里可以看到所有的结果(包括父进程和子进程)都已经在终端打印出来了,子进程的结果是运行dom顺序(我觉得可以是由于多个进程同时写入标准输出)。但是,如果我尝试通过 ./Task05_3 > 5output_c.csv 运行 它,我会发现我的目标 .csv 文件只包含来自父进程的结果,它看起来像:Result_in_csv01

所以我的第一个问题是.csv文件怎么可能只包含父进程的提示?是因为我输入的指令bash只是重定向了父进程的输出,与子进程的输出流无关吗?

更重要的是,当我尝试将 for 循环(用于打印)放在主 for 循环中(参考上面代码中注释的 for 循环)和 运行 代码 ./Task05_3 > 5output_c.csv 更令人困惑的事情发生了,.csv 文件现在看起来像:Result_in_csv02

现在包含所有结果!并且子进程结果的顺序不再是 运行dom!! (很明显,其他子进程一直在等待,直到 运行ning 子进程打印出所有结果)。所以我的第二个问题是,在我简单地更改 for 循环的位置后,这怎么会发生?

PS。 Linux 机器我 运行 我的代码在:

psyhq@bann:osc$ cat /proc/version
Linux version 3.10.0-693.2.2.el7.x86_64 (builder@kbuilder.dev.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC) ) #1 SMP Tue Sep 12 22:26:13 UTC 2017

GCC 版本为:

psyhq@bann:osc$ gcc --version
gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-16)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

默认情况下缓冲通过 stdio 函数的输出。这意味着它不会立即写入,而是在某些内部结构(FILE 内部)中累积,直到……发生某些事情。存在三种可能:

  • A FILE 是无缓冲的。然后立即写入输出。
  • 行缓冲。当缓冲区已满或看到 '\n'(换行符)时写入输出。
  • 块缓冲。当缓冲区已满时写入输出。

您始终可以使用 fflush.

手动强制写入

您打开的文件(使用 fopen)默认情况下是块缓冲的。 stderr 开始时没有缓冲。 stdout 如果它指的是终端,则为行缓冲,否则为块缓冲。

您的子进程打印整行 (printf("%d, %d\n", log[k], k);)。这意味着只要 stdout 到达终端,所有内容都会立即出现(因为它是行缓冲的)。

但是当您将输出重定向到文件时,stdout 变成块缓冲。缓冲区可能非常大,因此您的所有输出都会累积在缓冲区中(它永远不会满)。通常当 FILE 句柄关闭时(使用 fclose)缓冲区也会被刷新(即写入和清空),并且通常所有打开的文件都会在程序结束时自动关闭(通过 returnmain 或通过调用 exit).

但是,在这种情况下,您可以通过向它发送一个(致命的、无法捕获的)信号来终止该进程。这意味着您的文件永远不会关闭,您的缓冲区永远不会被写入,它们的内容会丢失。这就是为什么您看不到任何输出的原因。


在您的第二个版本中,您调用 exit 而不是向自己发送信号。这将执行调用 atexit 处理程序、关闭所有打开的文件并刷新其缓冲区的正常清理。


顺便说一句,您可以写 raise(X) 而不是 kill(getpid(), X)。它更短且更便携(raise 是标准 C)。