Chinaunix首页 | 论坛 | 博客

fx

  • 博客访问: 1378177
  • 博文数量: 115
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 3964
  • 用 户 组: 普通用户
  • 注册时间: 2013-05-02 14:36
文章分类
文章存档

2022年(2)

2019年(2)

2018年(10)

2017年(1)

2016年(50)

2015年(12)

2014年(9)

2013年(29)

分类: LINUX

2013-06-22 10:10:24

历史上管道时半双工的,即数据同一时刻只能在一个方向上流动。现在一些系统提供全双工的管道。但是为了可移植性,我们不应该假定系统提供了此特性。
所以通常仍旧是使用半双工的管道。

pipe函数用来创建一个管道
#include
int pipe(int filedes[2]);
成功返回0,出错返回-1

经由参数filedes返回两个文件描述符。filedes[0]为读而打开。filedes[1]为写而打开。 向filedes[1]中写入数据从filedes[0]中读出数据。

通常调用pipe创建一个管道接着调用fork创建一个子进程。因为子进程继承了父进程的打开的文件描述符。所以现在父子进程中都有管道两端的描述符
如下图所示

因为我们应该使用半双工管道。所以如果需要一个父进程数据到子进程的通道,那么就关闭父进程中的读端,关闭子进程中的写端。

同理,如果需要子进程数据到父进程的通道,那么就关闭子进程的读端,关闭父进程的写端。

另外与需要注意几条规则。
 1 当读一个写端已被关闭的管道时,在所有数据被读取后,read 返回0,表示到达文件结束处。也就是说即使管道的写端关闭了,但只要管道中还有之前写
的未读完的数据,那么管道读端仍旧可以读取其中的数据知道读完数据。
 2 如果管道的读端被关闭,那么写数据时会产生信号SIGPIPE。若忽略或捕捉该信号并从信号处里程序返回,则write返回-1,errno设置为EPIPE.
 3 写管道时,常量PIPE_BUF规定了内核中管道缓冲区的大小。如果对管道调用write,而且写的字数小于等于PIPE_BUF,则此操作不会与其他进程的write操作
穿插进行。当多个进程写管道,并且有进程的write操作写的字数大于PIPE_BUF时,则会导致写操作的数据相互穿插

我们可以利用管道来实现一个显示文件内容的程序。我们并不是自己完全实现,而是调用现有的 cat 程序。
cat 默认是读标准输入的(当没有给一个文件时)。那么我们利用管道。在父进程中读取文件内容到一个临时缓冲区然后写到管道中。然后在子进程中调用dup2使其标准输入成为管道的读端。然后在执行exec使cat运行。就会输出文件的内容。

程序如下:
  6 int main(int argc,char *argv[]){
  7         if(argc!=2){
  8                 printf("usage: a.out ");
  9                 exit(1);
 10         }
 11         int fd[2];
 12         if(pipe(fd)==-1){
 13                 perror("pipe error");
 14                 exit(1);
 15         }
 16         int pid=fork();
 17         if(pid==-1){
 18                 perror("fork error");
 19                 exit(1);
 20         }else if(pid>0){                //father
 21                 close(fd[0]);   //父进程中关闭读端
 22 
 23                 int file=open(argv[1],O_RDONLY);
 24                 if(file==-1){
 25                         perror("open file error");
 26                         exit(1);
 27                 }
 28                 int PIPE_BUF=fpathconf(fd[1],_PC_PIPE_BUF);
 29                 char buf[PIPE_BUF];
 30                 int nreads;
 31                 while((nreads=read(file,buf,PIPE_BUF))!=0){
 32                         if(nreads==-1){
 33                                 perror("read error");
 34                                 exit(1);
 35                         }else{
 36                                 if(write(fd[1],buf,nreads)!=nreads){
 37                                         perror("write error");
 38                                         exit(1);
 39                                 }
 40                         }
 41                 }
 42                 close(fd[1]);
 43                 waitpid(pid,NULL,0);
 44                 printf("child done\n");
 45 
 46         }else{                          //child
 47                 close(fd[1]); //子进程中关闭写端
 48                 if(fd[0]!=0){
 49                         if(dup2(fd[0],0)==-1){       //是标准输入成为管道的读端
 50                                 perror("dpu2 error");
 51                                 exit(1);
 52                         }
 53                 }
 54                 execl("/bin/cat","cat",(char *)0);     //执行cat程序
 55                 exit(1);
 56         }
 57         exit(0);
 58 }

让这个程序读一个现有的文件输出如下:
feng@ubuntu:~/learn_linux_c_second/chapter_15$ ./a.out head.h
#include
#include
#include
#include
child done

这里需要注意的一点是 42行的close(fd[1])  是必需的,
如果去掉 42行,那么因为父进程中我们调用了waitpid等待子进程结束。子进程调用execl执行了cat程序。因为cat程序
会读标准输入,而标准输入此时是管道的读端。父进程执行到waitpid时父进程已不再发送数据到管道但是却并未关闭管道的写端。那么子进程中的cat就会阻塞
在读取管道数据上。 也就是说,此时父进程在等待子进程结束,而子进程却在等到父进程发送数据或关闭管道写端。所以导致产生死锁!

使用管道的更常见的操作是创建一个管道连接到另一个进程,然后读其输出或向其输入端发送数据,为此标准I/O库提供了两个函数popen和pclose。
这两个函数实现的操作是:创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,执行一个shell以运行命令,然后等待命令终止

比如我们可以通过管道读取一个将大写转化为小写的程序的输出,然后打印出来

转换程序如下:
  3 int main(void){
  4         char c;
  5 
  6         while((c=getchar())!=EOF){
  7                 if(isupper(c))
  8                         c=tolower(c);
  9                 putchar(c);
 10                 if(c=='\n')
 11                         fflush(stdout);
 12         }
 13         exit(0);
 14 }
把程序命名为 convert

然后我们在下面的程序读上面的程序的输出,并打印:
  3 int main(void){
  4         FILE *fp;
  5         fp=popen("./convert","r");
  6         if(fp==NULL){
  7                 fprintf(stderr,"popen error\n");
  8                 exit(1);
  9         }
 10         char  returns[BUFSIZ];
 11         if(read(fileno(fp),returns,BUFSIZ)==-1){
 12                 perror("read error");
 13                 exit(1);
 14         }
 15         printf("%s\n",returns);
 16         exit(1);
 17 }
程序运行如下:
feng@ubuntu:~/learn_linux_c_second/chapter_15$ ./a.out
HJHJFHDR
hjhjfhdr

到这里我们所说的管道都存在一种局限。他们只能由相关的进程使用。这些相关的进程的共同的祖先创建了管道。

命名管道FIFO解决了这个问题。FIFO使不相关的进程也能交换数据
关于 FIFO 的一些具体细节分析亲参考 之间写的一篇 http://blog.chinaunix.net/uid-28852942-id-3656727.html

最后使用pipe创建的管道来写一个父子进程间同步的例程
同步例程实现放在 tongbu.h中 内容如下:
  
  6 int fd1[2];
  7 int fd2[2];
  8 int father_to_child;
  9 int read_from_child;
 10 int child_to_father;
 11 int read_from_father;
 12 
 13 void TELL_WAIT(){
 14         if(pipe(fd1)==-1 || pipe(fd2)==-1){
 15                 perror("pipe error");
 16                 exit(1);
 17         }
 18 father_to_child=fd1[1];
 19 read_from_child=fd2[0];
 20 
 21 child_to_father=fd2[1];
 22 read_from_father=fd1[0];
 23 }
 24 
 25 void WAIT_CHILD(pid_t pid){
 26         int c;
 27         if(read(read_from_child,&c,1)!=1){
 28                 perror("read error");
 29         }
 30 }
 31 
 32 void TELL_CHILD(pid_t pid){
 33         int c='p';
 34         if(write(father_to_child,&c,1)!=1){
 35                 perror("write error");
 36                 exit(1);
 37         }
 38 }


下面是一个有竞争的程序:
  6 int main(void){
  7         pid_t pid;
  8         if((pid=fork())==-1){
  9                 perror("fork error");
 10                 exit(1);
 11         }else if(pid==0){       //child
 12                 int i,j;
 13                 for(i=0;i<10;i++){
 14                         write(1,"c",1);
 15                         j=100000;
 16                         while(j--);
 17                 }
 18         }else{
 19                 int i,j;
 20                 for(i=0;i<10;i++){
 21                         write(1,"f",1);
 22                         j=100000;
 23                         while(j--);
 24                 }
 25                 printf("\n");
 26                 waitpid(pid,NULL,0);
 27         }
 28         exit(0);
 29 }
for循环中使用了while循环只是为了模拟一个慢速的读写操作,该程序输出如下:
ccfeng@ubuntu:~/learn_linux_c_second/chapter_15$ ./a.out
fffccffccffcfcfcfc
输出中 子进程和父进程的输出交叉在一起打印

使用上面提供的同步方法 改动程序如下:
  6 int main(void){
  7 /* + */ TELL_WAIT();
  8         pid_t pid;
  9         if((pid=fork())==-1){
 10                 perror("fork error");
 11                 exit(1);
 12         }else if(pid==0){       //child
 13                 int i,j;
 14                 for(i=0;i<10;i++){
 15                         write(1,"c",1);
 16                         j=100000;
 17                         while(j--);
 18                 }
 19  /* + */        TELL_FATHER(getppid());
 20         }else{
 21                 int i,j;
 22 /* + */         WAIT_CHILD(pid);
 23                 for(i=0;i<10;i++){
 24                         write(1,"f",1);
 25                         j=100000;
 26                         while(j--);
 27                 }
 28                 printf("\n");
 29                 waitpid(pid,NULL,0);
 30         }
 31         exit(0);
 32 }


改动的地方很少只是加几个同步函数,上面的同步是让子进程先输出;程序运行如下:
feng@ubuntu:~/learn_linux_c_second/chapter_15$ ./a.out
ccccccccccffffffffff
现在 父子进程的 输出按照我们的意愿 顺序输出。


同理如果想让父进程先输出 。只需要 将父进程中的 WAIT 改为 TELL 子进程中的 TELL 改为 WAIT 就行了
阅读(6198) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~