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(¤tTime, 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
)缓冲区也会被刷新(即写入和清空),并且通常所有打开的文件都会在程序结束时自动关闭(通过 return
从 main
或通过调用 exit
).
但是,在这种情况下,您可以通过向它发送一个(致命的、无法捕获的)信号来终止该进程。这意味着您的文件永远不会关闭,您的缓冲区永远不会被写入,它们的内容会丢失。这就是为什么您看不到任何输出的原因。
在您的第二个版本中,您调用 exit
而不是向自己发送信号。这将执行调用 atexit
处理程序、关闭所有打开的文件并刷新其缓冲区的正常清理。
顺便说一句,您可以写 raise(X)
而不是 kill(getpid(), X)
。它更短且更便携(raise
是标准 C)。
我目前正在研究操作系统和并发性,我关于进程调度器的实践之一是使用 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(¤tTime, 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
)缓冲区也会被刷新(即写入和清空),并且通常所有打开的文件都会在程序结束时自动关闭(通过 return
从 main
或通过调用 exit
).
但是,在这种情况下,您可以通过向它发送一个(致命的、无法捕获的)信号来终止该进程。这意味着您的文件永远不会关闭,您的缓冲区永远不会被写入,它们的内容会丢失。这就是为什么您看不到任何输出的原因。
在您的第二个版本中,您调用 exit
而不是向自己发送信号。这将执行调用 atexit
处理程序、关闭所有打开的文件并刷新其缓冲区的正常清理。
顺便说一句,您可以写 raise(X)
而不是 kill(getpid(), X)
。它更短且更便携(raise
是标准 C)。