我正在尝试创建自己的shell我相信我已经正确完成了分叉,但我无法弄清楚如何正确管道。任何帮助或提示将不胜感激。
基本上我的管道不工作,我花了很多年时间试图弄清楚如何让它们在进程之间正确传输数据。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include "ourhdr.h" // from Steven's book Advanced programing in the UNIX Enviroment
extern int makeargv(char *, char * , char ***);
int main()
{ char **argp;
int i,j,vpret;
char buf[80];
pid_t pid;
int pnum;
//pipe number
int ploc[16];
//pipe location
//(this has a max of 12 possible pipes)
int targ;
int fdleft[2], fdright[2];
printf(" <(^_^)> \nHello \n I am your console and I am here to help you \n");
printf(" If you dont need me anymore just say \"bye\" ");
fflush(stdout);
write(1,"\n(>^_^)> ",8);
while(strcmp(fgets(buf, 80, stdin), "bye\n")!=0)
{
j=makeargv(buf," \n",&argp); //this breaks the line up and returns the number of commands
pnum = 0;
ploc[0] = 0;
if (j > 16) j = 16;
for (i=0;i<j;i++)
{
if ( strcmp(argp[i], "|") == 0)
{
argp[i]= NULL;
ploc[pnum+1] = (i+1);
pnum++;
}
}
for (i = 0; i < (pnum+1); i++)
{
pipe(fdright);
if (i != 0)
{
dup2(fdright[1], fdleft[0]);
}
pid = fork();
switch (pid)
{
case -1:
err_sys("fork failed");
break;
case 0: // child
if (i != pnum)
{
dup2(fdright[1],1);
}
if ( i != 0);
{
dup2(fdright[0],0);
}
//printf("(^o^) running pipe[%i]\n" , i);
targ =(ploc[i]) ;
execvp(argp[targ],&argp[targ]);
write(1,"(-_-) I'm sorry the exec failed \n",33);
exit(1);
default:
dup2(fdleft[1], fdright[1]);
waitpid(pid,NULL, 0);
close(fdright[0]);
close(fdright[1]);
//waitpid(pid,NULL, 0);
}
}
//waitpid(pid, NULL, 0);
write(1,"\n(>^_^)> ",8);
}
printf(" v(^o^)^ BYE BYE!\n");
}
谢谢
答案 0 :(得分:3)
各种评论:
while (strcmp(fgets(buf, 80, stdin), "bye\n")!=0)
如果shell被赋予EOF而不是bye
,则会崩溃。不要像这样组合两个函数调用。如果您想在一个循环条件下完成所有操作,请使用:
while (fgets(buf, sizeof(buf), stdin) != 0 && strcmp(buf, "bye\n") != 0)
我们将再次讨论对命令行长度的限制。
由于您没有提供makeargv()
来查看,我们必须假设它可以正常工作。
将事物拆分为命令和管道的循环是:
pnum = 0;
ploc[0] = 0;
if (j > 16) j = 16;
for (i = 0; i < j; i++)
{
if (strcmp(argp[i], "|") == 0)
{
argp[i] = NULL;
ploc[pnum+1] = (i+1);
pnum++;
}
}
假设我们有一个命令行输入:ls -l | grep lemon
。您的makeargv()
似乎会返回5并设置argp
,如下所示:
argp[0] = "ls";
argp[1] = "-l";
argp[2] = "|";
argp[3] = "grep";
argp[4] = "lemon";
argp[5] = 0; // Inferred - things will crash sooner or later if wrong
你的这个循环会给你:
ploc[0] = 0;
ploc[1] = 3;
pnum = 1;
您的代码在fdleft
数组中有一对文件描述符,但您永远不会初始化该数组(例如,调用pipe()
),即使您在调用{ {1}}。
然后主dup2()
循环必须运行两次,每个命令一次。对于管道中的三个或更多命令(例如for
)的一般情况,您的第一个命令(who | grep me | sort
)需要其标准输入不变,但其标准输出将连接到连接的管道{ {1}}和who
。 'middle'命令(倒数第二个命令的第二个,或示例中的who
)每个都需要它的标准输入来自前一个管道,并且需要为其标准输出创建一个新管道。最后一个命令(在本例中,第三个命令grep
)需要其标准输入来自最后一个管道,其标准输出不变。
您的代码不会这样做,也不会靠近。
当您使用grep me
然后sort
或pipe()
将任何描述符映射到标准I / O描述符时,您需要关闭 both 管道的末端。你根本没有足够的dup()
电话。
您的父进程必须依次启动每个子进程,并且只有在它们全部启动后才等待它们退出。组织流程有不同的方法。父母可以分叉一次;孩子可以负责在管道中启动前导命令,最后执行最后一个命令。父母只有一个直接孩子(其他人是孙子女),所以只需要等待一个命令完成。另一种方法是父母知道管道中的每个流程,并等待所有流程完成。
如果您的父进程在启动剩余部分之前等待管道中的每个命令完成,则最终可能会出现死锁形式。一个孩子将大量数据写入其管道,它被内核阻止,直到某个进程从管道读取,但是从管道读取的进程尚未启动,并且父进程正在等待子进程退出。您也失去了多处理和并发的好处。
在你的'案例0'中,你有一个无关的分号。我的编译器警告过我。如果你没有警告过它,你需要使用更多编译警告或获得更好的编译器。
dup2()
关于SO的迷你炮弹中的管道有很多问题,包括:
13636252的答案几乎是通用的。唯一的障碍是它使用char ***
,这很容易让人感到困惑,而且编写得非常紧密,它具有相互递归的函数,重复次数最少。 OTOH,它工作正常,您的close()
函数也使用case 0: // child
if (i != pnum)
{
dup2(fdright[1], 1);
}
if (i != 0); // Unwanted semi-colon!
{
dup2(fdright[0], 0);
}
参数。
这是您的代码重新编写,以便它可以工作。它包括makeargv()
和char ***
的实现。我的err_sys()
只是假设命令行中的字数少于32个。在我的想象中,它不是一个强大的命令行解析器。它允许您输入makeargv()
并给出正确的答案;它还允许makeargv()
并给出正确的答案;它还允许ls | wc
并给出正确的答案。管道符号周围的空格是至关重要的(在普通的shell中,它们是可选的,因此who | grep me | sort
也应该可以工作,但它不会使用此代码。
ls
示例输出:
who|grep me|sort