标签:
#include<unistd.h>
int pipe(int fd[2])
如果成功,则返回值是0,如果失败,则返回值是-1,并且设置errno。
成功调用pipe函数之后,会返回两个打开的文件描述符,一个是管道的读取端描述符pipefd[0],另一个是管道的写入端描述符pipefd[1]。管道没有文件名与之关联,因此程序没有选择,只能通过文件描述符来访问管道,只有那些能看到这两个文件描述符的进程才能够使用管道。那么谁能看到进程打开的文件描述符呢?只有该进程及该进程的子孙进程才能看到。这就限制了管道的使用范围。
write(pipefd[1],wbuf,count);
一旦向管道的写入端写入数据后,就可以对读取端描述符pipefd[0]调用read,读出管道里面的内容。如下所示,管道上的read调用返回的字节数等于请求字节数和管道中当前存在的字节数的最小值。如果当前管道为空,那么read调用会阻塞(如果没有设置O_NONBLOCK标志位的话)。
调用pipe函数返回的两个文件描述符中,读取端pipefd[0]支持的文件操作定义在read_pipefifo_fops,写入端pipefd[1]支持的文件操作定义在write_pipefifo_fops,其定义如下:
const struct file_operations read_pipefifo_fops = { //读端相关操作
.llseek = no_llseek,
.read = do_sync_read,
.aio_read = pipe_read,
.write = bad_pipe_w, //一旦写,将调用bad_pipe_w
.poll = pipe_poll,
.unlocked_ioctl = pipe_ioctl,
.open = pipe_read_open,
.release = pipe_read_release,
.fasync = pipe_read_fasync,
};
const struct file_operations write_pipefifo_fops = {//写端相关操作
.llseek = no_llseek,
.read = bad_pipe_r, //一旦读,将调用bad_pipe_r
.write = do_sync_write,
.aio_write = pipe_write,
.poll = pipe_poll,
.unlocked_ioctl = pipe_ioctl,
.open = pipe_write_open,
.release = pipe_write_release,
.fasync = pipe_write_fasync,
};
我们可以看到,对读取端描述符执行write操作,内核就会执行bad_pipe_w函数;对写入端描述符执行read操作,内核就会执行bad_pipe_r函数。这两个函数比较简单,都是直接返回-EBADF。因此对应的read和write调用都会失败,返回-1,并置errno为EBADF。
static ssize_t
bad_pipe_r(struct file filp, char __user buf, size_t count, loff_t ppos)
{
return -EBADF; //返回错误
}
static ssize_t
bad_pipe_w(struct file filp, const char __user buf, size_t count,loff_t ppos)
{
return -EBADF;
}
我们只介绍了pipe函数接口,至今尚看不出来该如何使用pipe函数进行进程间通信。调用pipe之后,进程发生了什么呢?请看图9-5。
可以看到,调用pipe函数之后,系统给进程分配了两个文件描述符,即pipe函数返回的两个描述符。该进程既可以往写入端描述符写入信息,也可以从读取端描述符读出信息。可是一个进程管道,起不到任何通信的作用。这不是通信,而是自言自语。
如果调用pipe函数的进程随后调用fork函数,创建了子进程,情况就不一样了。fork以后,子进程复制了父进程打开的文件描述符(如图9-6所示),两条通信的通道就建立起来了。此时,可以是父进程往管道里写,子进程从管道里面读;也可以是子进程往管道里写,父进程从管道里面读。这两条通路都是可选的,但是不能都选。原因前面介绍过,管道里面是字节流,父子进程都写、都读,就会导致内容混在一起,对于读管道的一方,解析起来就比较困难。常规的使用方法是父子进程一方只能写入,另一方只能读出,管道变成一个单向的通道,以方便使用。如图9-7所示,父进程放弃读,子进程放弃写,变成父进程写入,子进程读出,成为一个通信的通道…
int pipefd[2];
pipe(pipefd);
switch(fork())
{
case -1:
/fork failed, error handler here/
case 0: /子进程/
close(pipefd[1]) ; /关闭掉写入端对应的文件描述符/
/子进程可以对pipefd[0]调用read/
break;
default: /父进程/
close(pipefd[0]); /父进程关闭掉读取端对应的文件描述符/
/父进程可以对pipefd[1]调用write, 写入想告知子进程的内容/
break
}
if(pipefd[1] != STDOUT_FILENO)
{
dup2(pipefd[1],STDOUT_FILENO);
close(pipefd[1]);
}
同样的道理,对于第二个子进程,如法炮制:
if(pipefd[0] != STDIN_FILENO)
{
dup2(pipefd[0],STDIN_FILENO);
close(pipefd[0]);
}
简单来说,就是第一个子进程的标准输出被绑定到了管道的写入端,于是第一个命令的输出,写入了管道,而第二个子进程管道将其标准输入绑定到管道的读取端,只要管道里面有了内容,这些内容就成了标准输入。
两个示例代码,为什么要判断管道的文件描述符是否等于标准输入和标准输出呢?原因是,在调用pipe时,进程很可能已经关闭了标准输入和标准输出,调用pipe函数时,内核会分配最小的文件描述符,所以pipe的文件描述符可能等于0或1。在这种情况下,如果没有if判断加以保护,代码就变成了:
dup2(1,1);
close(1);
这样的话,第一行代码什么也没做,第二行代码就把管道的写入端给关闭了,于是便无法传递信息了
道的一个重要作用是和外部命令进行通信。在日常编程中,经常会需要调用一个外部命令,并且要获取命令的输出。而有些时候,需要给外部命令提供一些内容,让外部命令处理这些输入。Linux提供了popen接口来帮助程序员做这些事情。
就像system函数,即使没有system函数,我们通过fork、exec及wait家族函数一样也可以实现system的功能。但终归是不方便,system函数为我们提供了一些便利。同样的道理,只用pipe函数及dup2等函数,也能完成popen要完成的工作,但popen接口给我们提供了便利。
popen接口定义如下:
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
popen函数会创建一个管道,并且创建一个子进程来执行shell,shell会创建一个子进程来执行command。根据type值的不同,分成以下两种情况。
如果type是r:command执行的标准输出,就会写入管道,从而被调用popen的进程读到。通过对popen返回的FILE类型指针执行read或fgets等操作,就可以读取到command的标准输出,如图9-10所示。
如果type是w:调用popen的进程,可以通过对FILE类型的指针fp执行write、fputs等操作,负责往管道里面写入,写入的内容经过管道传给执行command的进程,作为命令的输入,如图9-11所示
popen函数成功时,会返回stdio库封装的FILE类型的指针,失败时会返回NULL,并且设置errno。常见的失败有fork失败,pipe失败,或者分配内存失败。
I/O结束了以后,可以调用pclose函数来关闭管道,并且等待子进程的退出。尽管popen函数返回的是FILE类型的指针,也不应调用fclose函数来关闭popen函数打开的文件流指针,因为fclose不会等待子进程的退出。pclose函数成功时会返回子进程中shell的终止状态。popen函数和system函数类似,如果command对应的命令无法执行,就如同执行了exit(127)一样。如果发生其他错误,pclose函数则返回-1。可以从errno中获取到失败的原因。
下面给出一个简单的例子,来示范下popen的用法:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<sys/wait.h>
#include<signal.h>
#define MAX_LINE_SIZE 8192
void print_wait_exit(int status)
{
printf("status = %d\n",status);
if(WIFEXITED(status))
{
printf("normal termination,exit status = %d\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))
{
printf("abnormal termination,signal number =%d%s\n",
WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status)?"core file generated" : "");
#else
"");
#endif
}
}
int main(int argc ,char* argv[])
{
FILE *fp = NULL ;
char command[MAX_LINE_SIZE],buffer[MAX_LINE_SIZE];
if(argc != 2 )
{
fprintf(stderr,"Usage: %s filename \n",argv[0]);
exit(1);
}
snprintf(command,sizeof(command),"cat %s",argv[1]);
fp = popen(command,"r");
if(fp == NULL)
{
fprintf(stderr,"popen failed (%s)",strerror(errno));
exit(2);
}
while(fgets(buffer,MAX_LINE_SIZE,fp) != NULL)
{
fprintf(stdout,"%s",buffer);
}
int ret = pclose(fp);
if(ret == 127 )
{
fprintf(stderr,"bad command : %s\n",command);
exit(3);
}
else if(ret == -1)
{
fprintf(stderr,"failed to get child status (%s)\n",
strerror(errno));
exit(4);
}
else
{
print_wait_exit(ret);
}
exit(0);
}
标签:
原文地址:http://www.cnblogs.com/zengyiwen/p/5755170.html