一些事情。
(1) 对于每个孩子,您只创建了一个管道 [父到子],但您需要第二个 [子到父](即管道不是双向的像套接字)。
(2) 当您预先创建 all 管道时,在子节点中,您必须关闭当前子节点的 not 管道,而不仅仅是它的两个管道的管道侧。
如果您不这样做,那么子 N 将为 每个 子级的两个管道,用于 所有 个子级 非 N.
在 [给定] 分叉后,如果父级完全关闭打开的管道,子级将仍然继承 [a copy] 中打开的任何文件描述符分叉时的父级。因此,关闭父级没有任何效果,因为 child 仍然让它们保持打开状态——对于 所有 的子级
这就是你原来的程序所做的。
在我的版本 [以下] 中,它不那么严重。如果没有预关闭(通过childclose),子 0 仅保持其自己的管道打开。但是,子 1 将打开子 0 的管道。孩子 2 将打开孩子 0 和孩子 1 的管道。等等......
因此,许多孩子正在打开彼此的管道描述符。因此,当父进程关闭管道时,它们仍然被其他子进程保持打开状态,因此 no 子进程将永远看到 EOF
如果您想对此进行可视化,请使用您的原始代码,并将其作为fork 之后的第一个可执行部分(例如,紧接在case 0 之后):
{
pid_t pid = getpid();
char buf[100];
printf("DEBUG: %d\n",pid);
sprintf(buf,"ls -l /proc/%d/fd",pid);
system(buf);
}
忽略 stdin/stdout/stderr,而不是预期的 2 个(应该是 4 个)打开的描述符,您将在 每个子项中看到 (2 * PROCESSES)(即 10 个)描述符。
在父节点中完成最后的关闭后,您可以[在父节点中]重复这样的序列,您将仍然看到相同的内容[减去每个子节点将关闭的两个]。
这可以更容易地用结构来组织。为了证明它确实有效,我添加了一些带有回显的实际数据传输。我还添加了一些调试选项以显示差异。
以下是更正后的代码[请原谅无偿的风格清理]:
#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/wait.h>
#define PROCESSES 5
int opt_n; // do _not_ close other children
int opt_p; // original semantics
int opt_v; // show list
// child control
struct child {
int cld_idx; // child index
pid_t cld_pid; // child's pid
int cld_status; // child's exit status
int cld_topar[2]; // pipe: child-to-parent
int cld_tocld[2]; // pipe: parent-to-child
};
#define CLOSEME(_fd) \
do { \
if (_fd >= 0) \
close(_fd); \
_fd = -1; \
} while (0)
struct child children[PROCESSES];
// fdlist -- output list of open descriptors
void
fdlist(struct child *cld,const char *reason)
{
struct child cld2;
char cmd[100];
if (cld == NULL) {
cld = &cld2;
cld->cld_pid = getpid();
cld->cld_idx = -1;
}
printf("\n");
printf("fdlist: idx=%d pid=%d (from %s)\n",
cld->cld_idx,cld->cld_pid,reason);
sprintf(cmd,"ls -l /proc/%d/fd",cld->cld_pid);
system(cmd);
}
// childclose -- close any pipe units from other children
void
childclose(int i)
{
struct child *cld;
for (int j = 0; j < PROCESSES; ++j) {
if (j == i)
continue;
cld = &children[j];
CLOSEME(cld->cld_topar[0]);
CLOSEME(cld->cld_topar[1]);
CLOSEME(cld->cld_tocld[0]);
CLOSEME(cld->cld_tocld[1]);
}
}
// childopen -- create pipes for child
void
childopen(int i)
{
struct child *cld;
cld = &children[i];
// to cut down on the clutter, only create the pipes as we need them
pipe(cld->cld_topar);
pipe(cld->cld_tocld);
}
// childstart -- start up child
void
childstart(int i)
{
struct child *cld;
pid_t pid;
cld = &children[i];
// to cut down on the clutter, only create the pipes as we need them
if (! opt_p)
childopen(i);
pid = fork();
if (pid < 0) {
perror("Error forking a child");
exit(1);
}
switch (pid) {
case 0: // child
// close any pipe that doesn't belong to us
if (! opt_n)
childclose(i);
pid = getpid();
cld->cld_pid = pid;
if (opt_v)
fdlist(cld,"childstart");
// Close the pipe sides we don't need
CLOSEME(cld->cld_topar[0]);
CLOSEME(cld->cld_tocld[1]);
// Inside the child process, die immediately
int len;
char buffer[128];
while (1) {
len = read(cld->cld_tocld[0], buffer, sizeof(buffer) - 1);
if (len <= 0)
break;
// Keep reading and echoing
write(cld->cld_topar[1],buffer,len);
}
printf("child %d: Dying\n",i);
exit(1);
break;
default: // parent
// give child time to print message
if (opt_v)
sleep(1);
cld->cld_pid = pid;
// Parent process, close the pipe sides we don't need
CLOSEME(cld->cld_topar[1]);
CLOSEME(cld->cld_tocld[0]);
break;
}
}
int
main(int argc, char **argv)
{
char *cp;
struct child *cld;
int len;
char buf[128];
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
case 'n': // do _not_ close other descriptors
opt_n = 1;
break;
case 'p': // preopen all pipes
opt_p = 1;
break;
case 'v': // show verbose messages
opt_v = 1;
break;
}
}
setlinebuf(stdout);
printf("main: pipes will be created %s\n",
opt_p ? "all at once" : "as needed");
printf("main: other child descriptors %s be closed\n",
opt_n ? "will not" : "will");
for (int i = 0; i < PROCESSES; i++) {
cld = &children[i];
cld->cld_idx = i;
cld->cld_topar[0] = -1;
cld->cld_topar[1] = -1;
cld->cld_tocld[0] = -1;
cld->cld_tocld[1] = -1;
}
// create pipes for _all_ children ahead of time
if (opt_p) {
for (int i = 0; i < PROCESSES; i++)
childopen(i);
if (opt_v)
fdlist(NULL,"master/OPEN");
}
// start up all children
for (int i = 0; i < PROCESSES; i++)
childstart(i);
// show final list
if (opt_v) {
sleep(1);
for (int i = 0; i < PROCESSES; i++) {
cld = &children[i];
fdlist(cld,"master/POSTSTART");
}
}
// send to child
for (int i = 0; i < PROCESSES; i++) {
cld = &children[i];
len = sprintf(buf,"child %d, you are pid %d\n",i,cld->cld_pid);
write(cld->cld_tocld[1],buf,len);
}
// receive from child
printf("\n");
for (int i = 0; i < PROCESSES; i++) {
cld = &children[i];
len = read(cld->cld_topar[0],buf,sizeof(buf));
printf("RECV(%d): %s",i,buf);
}
// show final list
if (opt_v) {
sleep(1);
for (int i = 0; i < PROCESSES; i++) {
cld = &children[i];
fdlist(cld,"master/FINAL");
}
}
// CLOSE ALL PIPES FROM PARENT TO CHILDREN------------
for (int i = 0; i < PROCESSES; i++) {
cld = &children[i];
CLOSEME(cld->cld_topar[0]);
CLOSEME(cld->cld_tocld[1]);
}
// AWAIT CHILDREN DEATHS
for (int i = 0; i < PROCESSES; i++) {
cld = &children[i];
waitpid(cld->cld_pid,&cld->cld_status,0);
}
printf("All children have died\n");
return 0;
}