q.T*_*hen 2 c unix linux posix pipe
首先,准备好看到一些魔法。
嗨,在过去的几个小时里,我一直在为这个问题感到沮丧和挣扎,我不明白为什么子进程没有死亡。我基本上有一个单亲进程和许多子进程。所有的孩子都需要与父母沟通,而父母需要与所有的孩子沟通。对于初学者,我只是让孩子们不断尝试read,但我的父母什么也不发送,只是关闭write管道的末端,从而导致他们reads停止阻塞。这是我的进程(我的宏定义是5子进程):
5 int*指向2管道的点数组。父母使用第一个与孩子交谈,孩子使用第二个
5并关闭管道的适当末端
read循环应该终止这是我的代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/types.h>
#define PROCESSES 5
int main(int argc, char ** argv) {
int * pipes[PROCESSES];
for (int i = 0; i < PROCESSES; i++) {
pipes[i] = malloc(sizeof(int) * 2);
if (pipe(pipes[i]) == -1) {
perror("Error piping");
exit(1);
}
}
//PIDS we will wait on
int children_pids[PROCESSES];
for (int i = 0; i < PROCESSES; i++) {
int status = fork();
switch(status) {
case -1:
perror("Error forking a child");
exit(1);
case 0:
//Close the pipes we don't need
close(pipes[i][1]);
//Inside the child process, die immediately
char buffer[128] = "";
while (read(pipes[i][0], buffer, 127) > 0) {
//Keep reading and doing nothing
}
printf("Dying\n");
exit(1);
default:
//Parent process, close the pipes we don't need
close(pipes[i][0]);
break;
}
//Parent continue spawning children
children_pids[i] = status;
}
//CLOSE ALL PIPES FROM PARENT TO CHILDREN------------
for (int i = 0; i < PROCESSES; i++) {
if (close(pipes[i][1]) == -1) {
perror("Error closing a pipe");
exit(1);
}
}
//AWAIT CHILDREN DEATHS
for (int i = 0; i < PROCESSES; i++) {
wait(&children_pids[i]);
}
printf("All children have died");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我知道是read孩子们的循环阻止了孩子们死亡,因为当它被移除时,它工作正常。但是,我无法弄清楚为什么会这样。在底部的循环中,我清楚地关闭了所有管道,甚至检查了错误。为什么是这样?!怎么read仍然阻碍我实现我的return;目标??!?
首先,我将回顾一些您显然知道的信息。我写这个是因为其他人也可能会阅读这个答案,因为为答案提供一些上下文总是好的。
之后,我将在以下位置展示您的代码的原因:
case 0: // the child process
close(pipes[i][1]); // <- if (i == 2) pipes[1][1] is open.
Run Code Online (Sandbox Code Playgroud)
可能意味着执行了以下任务:
case 0: // the child process
// close all input endpoints (input only performed by root process)
// also close all irrelevant output endpoints:
for (int j = 0; j < PROCESSES; j++){
close(pipes[j][1]);
if(j != i)
close(pipes[j][0]);
}
Run Code Online (Sandbox Code Playgroud)
众所周知,每个子进程都会收到dup一份文件描述符 (fd)的ed 副本,每个子进程pipe都包含两个文件描述符,一个用于输入(读取),另一个用于输出(写入)。
每次 fork 进程时,这两个端点(文件描述符) - 对于每个打开的管道 - 都是重复的。
read将阻塞,而传入的数据仍有可能最终到达——这意味着,read当至少一个“输出”(写入)文件描述符仍然打开时,它将阻塞。
在下面的示例中,我将打开一个管道并分叉该进程。分叉进程将关闭它的“输入”(写入)端点并调用read. read会阻塞,因为在父进程中仍然有一个打开的输入 fd(记住,它fd是重复的)。父关闭它的“输入”后fd,没有更多的写入端点,读取将失败(停止阻塞)。
注意,我没有在管道上写任何东西。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct {
int in; // the input fd
int out; // the output fd
} pipe_io;
int main() {
// the container;
pipe_io io;
// make the pipe
pipe((int*)&io);
// forking will duplicate the open files
pid_t child;
if (!(child = fork())) { // fork==0 => we're in the child process
close(io.out); // closing one reading access point.
char buff[4];
// read will block because there's still an open writing access point.
printf("Child waiting (read will block)\n");
read(io.in, buff, 1);
// cleanup and exit process.
close(io.in);
printf("Child exits (read stopped blocking once all inputs were closed)\n");
exit(0);
}
sleep(1); // wait...
printf("closing parent's writing (output) endpoint.\n");
close(io.out);
sleep(1); // wait...
printf("closing parent's reading (input) endpoint.\ndone.\n");
waitpid(child, NULL, 0);
}
Run Code Online (Sandbox Code Playgroud)
输出是代码控制流的明确指示:
Child waiting (read will block)
closing parent's writing (output) endpoint.
Child exits (read stopped blocking once all inputs were closed)
closing parent's reading (input) endpoint.
done.
Run Code Online (Sandbox Code Playgroud)
因此,为了使调用read失败(而不是阻塞),我们需要关闭所有写入端点/通道。
在您的代码中,每个进程都有一个管道,但是您允许每个进程保持其他进程的“输入”(写入)端点打开 - 所以read总是会阻塞。
case 0: // the child process
// This line only closes this process's input stream, but this stream is
// open for all other processes:
close(pipes[i][1]); // <- if (i == 2) pipes[1][1] is open.
//...
// `read` will ALWAYS block because other processes keep input endpoints.
while (read(pipes[i][0], buffer, 127) > 0) {
//Keep reading and doing nothing
}
printf("Dying\n");
exit(1);
Run Code Online (Sandbox Code Playgroud)
你可能想写:
case 0: // the child process
// closing all input endpoints (input only performed by root process)
// also closing all irrelevant output endpoints:
for (int j = 0; j < PROCESSES; j++){
close(pipes[j][1]);
if(j != i)
close(pipes[j][0]);
}
//...
Run Code Online (Sandbox Code Playgroud)
聚苯乙烯
为每个进程打开一个管道是不太常见的,除非每个进程都有一个单独的角色。
共享相同功能的所有进程共享同一个管道更为常见。
例如,如果一个进程族被用来执行一个共享的任务族,那么哪个进程执行哪个任务可能并不重要——所以如果任务被提交到一个共享管道并且第一个进程读取它会更有效数据是执行任务的数据。
当一个进程忙于执行任务时,它不会从管道中读取,如果另一个进程可用(在“读取”时阻塞),它会立即投入工作(而不是等待繁忙的进程)。
这种单管道设计最大限度地减少了“等待”时间并消除了任何调度问题(管道缓冲区的限制除外)。