这是我为自己的shell找到的代码。它工作正常,但我无法理解的是代码的管道部分。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
char* cmndtkn[256];
char buffer[256];
char* path=NULL;
char pwd[128];
int main(){
//setting path variable
char *env;
env=getenv("PATH");
putenv(env);
system("clear");
printf("\t MY OWN SHELL !!!!!!!!!!\n ");
printf("_______________________________________\n\n");
while(1){
fflush(stdin);
getcwd(pwd,128);
printf("[MOSH~%s]$",pwd);
fgets(buffer,sizeof(buffer),stdin);
buffer[sizeof(buffer)-1] = '\0';
//tokenize the input command line
char* tkn = strtok(buffer," \t\n");
int i=0;
int indictr=0;
// loop for every part of the command
while(tkn!=NULL)
{
if(strcoll(tkn,"exit")==0 ){
exit(0);
}
else if(strcoll(buffer,"cd")==0){
path = buffer;
chdir(path+=3);
}
else if(strcoll(tkn,"|")==0){
indictr=i;
}
cmndtkn[i++] = tkn;
tkn = strtok(NULL," \t\n");
}cmndtkn[i]='\0';
// execute when command has pipe. when | command is found indictr is greater than 0.
if(indictr>0){
char* leftcmnd[indictr+1];
char* rightcmnd[i-indictr];
int a,b;
for(b=0;b<indictr;b++)
leftcmnd[b]=cmndtkn[b];
leftcmnd[indictr]=NULL;
for(a=0;a<i-indictr-1;a++)
rightcmnd[a]=cmndtkn[a+indictr+1];
rightcmnd[i-indictr]=NULL;
if(!fork())
{
fflush(stdout);
int pfds[2];
pipe(pfds);
if(!fork()){
close(1);
dup(pfds[1]);
close(pfds[0]);
execvp(leftcmnd[0],leftcmnd);
}
else{
close(0);
dup(pfds[0]);
close(pfds[1]);
execvp(rightcmnd[0],rightcmnd);
}
}else
wait(NULL);
//command not include pipe
}else{
if(!fork()){
fflush(stdout);
execvp(cmndtkn[0],cmndtkn);
}else
wait(NULL);
}
}
}
调用close()参数为0和1是什么意思是什么意思以及对dup()的调用是什么?
答案 0 :(得分:0)
在Unix上,dup()
调用使用编号最小的未使用文件描述符。因此,调用close(1)
之前的dup()
是强制dup()
使用文件描述符1.类似于close(0)
。
因此,别名是让进程使用stdout
管道的写端(文件描述符1用于控制台输出),以及stdin
管道的读取端(文件描述符0用于控制台输入)。
代码可能更清楚地表达为dup2()
。
dup2(fd[1], 1); /* alias fd[1] to 1 */
关于ls | sort
如何运作的问题,您的问题不仅限于为什么要进行dup()
系统调用。您的问题实际上是Unix中的管道如何工作,以及shell命令管道如何工作。
Unix中的管道是一对文件描述符,其相关之处在于,在可写描述符上写入数据允许从可读描述符中读取数据。 pipe()
调用在数组中返回此对,其中第一个数组元素是可读的,第二个数组元素是可写的。
在Unix中,fork()
后跟某种exec()
是生成新流程的唯一方法(还有其他库调用,例如system()
或{{1}创建进程,但是他们调用popen()
并在引擎盖下执行fork()
。 exec()
生成子进程。子进程从调用中看到fork()
的返回值,而父进程看到非零返回值(子进程的PID)或0
表示发生了错误
子进程是父进程的副本。这意味着当子项修改变量时,它正在修改驻留在其自己的进程中的变量的副本。父级没有看到修改发生,因为父级具有原始副本)。但是,可以使用形成管道的重复文件描述符对来允许子进程与其父进程相互通信。
因此,-1
表示生成了两个进程,ls | sort
写入的输出被ls
读取为输入。两个进程意味着两次调用sort
来创建两个子进程。一个子进程将fork()
exec()
命令,另一个子进程将ls
exec()
命令。它们之间使用管道以允许进程相互通信。 sort
进程写入管道的可写端,ls
进程从管道的可读端读取。
在发出sort
后,ls
进程被强制写入管道的可写端并dup()
调用。强制close(1)
进程在sort
之后使用dup()
调用读取管道的可读端。
此外,关闭管道文件描述符的close(0)
调用用于确保close()
进程是唯一具有对可写fd的开放引用的进程,ls
进程1}}进程是唯一一个对可读fd进行开放引用的进程。该步骤很重要,因为在sort
退出后,它将关闭fd的可写端,ls
进程将期望看到EOF作为结果。但是,如果某些其他进程仍然打开了可写的fd,则不会发生这种情况。
答案 1 :(得分:0)
http://en.wikipedia.org/wiki/Standard_streams#Standard_input_.28stdin.29
stdin是文件描述符0。
stdout是文件描述符1。
在!fork部分,进程关闭stdout然后在pfds [1]上调用dup,根据:
http://linux.die.net/man/2/dup
在最低可用位置创建指定文件描述符的副本,该位置将为1,因为它刚刚关闭(并且stdin尚未关闭)。这意味着发送到stdout的所有内容都将真正转到pfds [1]。
所以,基本上,它设置了两个新的流程来相互交流。 !fork部分用于将子数据发送到stdout(文件描述符1)的新子节点,父节点(else块)关闭stdin,因此当它尝试从stdout读取时它确实从pfds [0]读取。
每个进程必须关闭它未使用的pfds中的文件描述符,因为现在该进程已经分叉了两个打开的句柄。每个进程现在执行到left / right-cmnd,但是新的stdin和stdout映射仍然用于新进程。
这里解释了两次分叉:Why fork() twice