一、管道概述:
在Linux中,管道是一种使用非常频繁的通信机制。从本质上说,管道也是一种文件,但是它和一般的文件有所不同,管道可以克服使用文件进程通信的两个问题,具体表现为:
1.限制管道的大小:
实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不像文件那样不加检验的增长。
使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据读出,以便腾出足够的空间供write()调用写。
2.读取进程也可能工作的比写进程块:
当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。
注意:从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写入更多的数据。
二、管道使用方法
1、使用管道符“|”
在Linux系统中,由管道连接起来的进程可以自动运行,就如同在他们有一个数据流一样。例如:
我们要使用sort命令来排序ps的输出:
不使用管道方法:$ ps > psout.txt $ sort psout.txt > pssort.out
使用管道方法:$ ps | sort > pssort.out
我们要在屏幕上看到他们
我们要使用第三个线程:$ ps | sort |more
使用管道数并没有一个量的限制。如果我们要看到正在运行的处理shell以外不同名字的进程,我们可以使用下面的命令:$ ps -xo comm |sort | unip | grep -v sh |more ,在这个命令中,使用了ps的输出,将这个输出以字母的顺序进行排序,使用unip来解压线程,使用grep -v sh在移除名为sh的进程,最后在屏幕上显示结果。
2、C语言通过管道进行进程间通信
管道分为有名管道(fifo)和无名管道(FIFO),他们都是通过内核缓冲区按先进先出的方式进行数据传输,管道的一段顺序的写入数据,另一端顺序的读入数据读写的位置都是自动增加,数据只读一次,之后就被释放。在缓冲区写满时,则由相应的规则控制读写进程进入等待队列,当空的缓冲区有写入数据或满的缓冲区有数据读出时,就唤醒等待队列中的写进程继续写。
无名管道:无名管道没有名字,对于管道中使用的文件描述符没有路径名,也就是不存在任何意义上的文件,它们只是在内存中跟某一个索引节点相关联的两个文件描述符。
(1)、无名管道是半双工的,数据只能在一个方向上移动,也就是对单个管道来讲,只能进行读或者写。
(2)、无名管道只能在具有公共祖先的进程间通信,即或是父子关系、或是在兄弟关系进程间通信。
(3)、Linux下使用pipe(int fd[2])函数创建一个无名半双工管道,管道两端可分别用描述字fd[0]以及fd[1]来描述需要注意的是,管道的两端是固定了任务的。即一端只能用于读,有描述符fd[0]表示,称其为管道读端;另一端则只能用于写,由描述符fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据、或者向管道读端写入数据都将导致错误发生。一般文件的I/O函数都可以用于管道,如read、write、close等等。
注意:在进程读写管道时,对一个管道进行读写操作后名,read函数返回为0,有两种意义,一种是管道中无数据并且写入端已经关闭。另一种是管道中无数据,写入端依然存活。
- int main(int args,char *argv[])
- {
- int fd[2];//管道描述符
- char buf[MAXSIZE];//存放管道收发的数据
- memset(buf,0,sizeof(buf));
- int len;//记录长度
- if(pipe(fd)==-1)//创建无名管道
- {
- printf("error is %s",strerror(errno));
- exit(1);
- }
- int fd=fork();
- if(fd==0)
- {
- close(fd[1]);
- while((len=read(fd[0],buf,sizeof(buf)))>0)
- {
- write(STDOUT_FILENO,buf,strlen(buf));
- }
- close(fd[0]);
- }
- if(fd>0)
- {
- close(fd[0]);
- read(STDIN_FILENO,buf,sizeof(buf));
- write(fd[1],buf,strlen(buf));
- close(fd[1]);
- waitpid(pid,NULL,0);
- }
- return 0;
- }
有名管道:有名管道是持久稳定的,它们存在于文件系统中,可以用于不同的进程间通信。
(1)、创建方式:
a.通过路径名指出,在文件系统中可见
$ mkfifo [option] name
创建一个名为name的有名管道。
b.通过函数mkfifo函数创建
int mkfifo(const char *pathname,mode_t mode);
函数成功:创建一个名为pathname的有名管道,返回0
函数失败:返回0,并设置errno
- int main()
- {
- if(mkfifo("fifo1",0666)==-1)
- {
- printf("create fifo error:%s",strerror(errno));
- }
- else
- {
- printf("success");
- }
- return 0;
- }
c.删除fifo
int unlink(const char *pathname);
函数执行成功返回0,否则返回-1,并设置errno
- int main(int args,char *argv[])
- {
- if(args==1)
- return 0;
- int i=unlink("fifo1");
- if(i==-1)
- {
- printf("%s",strerror(errno));
- }
- return 0;
- }
(2)、FIFO遵循先进先出规则:
a.对管道从开始处返回数据
b.对管道写则把数据添加到末尾
c.不支持lseek()等文件定位操作
(3)、操作
a.打开和关闭FIFO
int open(const char *pathname,int flags);
int close(int fd);
FIFO的两端使用前都要打开,open()中如果参数flags为O_RDONLY将阻塞open()调用,一直到另一个进程为写入数据打开FIFO为止。相同的,参数O_WRONLY也导致阻塞一直未读出数据打开FIFO为止。
读FIFO:
- int main(int args,char *argv[])
- {
- int len=0;
- char buf[MaxSize];
- memset(buf,0,sizeof(buf));
- if((fd=open("fifo1",O_RDONLY))<0)
- exit(1);
- while((len=read(fd,buf,sizeof(buf)))>0)
- {
- write(STDOUT_FILENO,buf,strlen(buf));
- memset(buf,0,sizeof(buf));
- }
- close(fd);
- return 0;
- }
写FIFO:
- int main(int args,char *argv[])
- {
- int len=0;
- char buf[100];
- memset(buf,0,sizeof(buf));
- if((fd=open("fifo1",O_ERONLY))<0)
- exit(1)
- while(1)
- {
- read(STDIN_FILENO,buf,sizeof(buf));
- if(buf[0]=='0')
- {
- break;
- }
- write(fd,buf,strlen(buf));
- }
- return 0;
- }
有名管道和无名管道:
向管道中写入数据时,Linux将不保证写入的原子性,管道缓冲区有一空闲区域,写进程就会试图向管道写入数据。如果读进程不读完??管道缓冲区的数据,那么写操作将一直阻塞。
无名管道存在着两个严重的缺点:
第一,无名管道只能用于连接具有共同祖先的进程;
第二,无名管道是依附进程而临时存在的。所以有了有名管道的出现FIFO。有名管道继承了无名管道的所有特性和优点之外,还摒弃了无名管道的两个缺点。