推广 热搜: 二手  净利2626万  北京  二手车  SMM废铜现货交易日评  企业  全国  汽车  三星  公司 

linux系统 管道的意思是怎么用

   日期:2024-05-22     来源:www.haoconghui.com    作者:二手网    浏览:384    评论:0    
核心提示:[db:简介]

管道是Linux进程间的一种通信方法,两个进程可以通过一个共享内存地区来传递信息,并且管道中的数据只能是单向流动的,也就是说只能有固定的写进程和读进程。现在在任何一个shell中,都可以用|连接两个命令,shell会将前后两个进程的输入输出用一个管道相连,以便达到进程间通信的目的。

什么是管道?

管道,英文为pipe。管道是Linux进程间的一种通信方法,两个进程可以通过一个共享内存地区来传递信息,并且管道中的数据只能是单向流动的,也就是说只能有固定的写进程和读进程。

管道的创造人是道格拉斯.麦克罗伊,这位也是UNIX上早期shell的创造人。他在创造了shell之后,发现系统操作实行命令的时候,常常有需要要将一个程序的输出交给另一个程序进行处置,这种操作可以用输入输出重定向加文件解决,譬如:

[zorro@zorro-pcpipe]$ls-l/etc/etc.txt[zorro@zorro-pcpipe]$wc-letc.txt183etc.txt

但如此未免看上去太麻烦了。所以,管道的定义应运而生。现在在任何一个shell中,都可以用|连接两个命令,shell会将前后两个进程的输入输出用一个管道相连,以便达到进程间通信的目的:

[zorro@zorro-pcpipe]$ls-l/etc/|wc-l183

对比以上两种办法,大家也可以理解为,管道本质上就是一个文件,前面的进程以写方法打开文件,后面的进程以读方法打开。如此前面写完后面读,于是就达成了通信。事实上管道的设计也是遵循UNIX的所有皆文件设计原则的,它本质上就是一个文件。Linux系统直接把管道达成成了一种文件系统,借用VFS给应用程序提供操作接口。

虽然达成形态上是文件,但管道本身并不占用磁盘或者其他外部存储的空间。在Linux的达成上,它占用的是内存空间。所以,Linux上的管道就是一个操作方法为文件的内存缓冲区。

管道的分类和用

Linux上的管道分两类型型:

匿名管道

命名管道

这两种管道也叫做有名或无名管道。匿名管道最容易见到的形态就是大家在shell操作中最常见的|。它的特征是只能在父子进程中用,父进程在产生子进程前需要打开一个管道文件,然后fork产生子进程,如此子进程通过拷贝父进程的进程地址空间获得同一个管道文件的描述符,以达到用同一个管道通信的目的。此时除去父子进程外,没人了解这个管道文件的描述符,所以通过这个管道中的信息没办法传递给其他进程。这保证了传输数据的安全性,当然也减少了管道了通用性,于是系统还提供了命名管道。

大家可以用mkfifo或mknod命令来创建一个命名管道,这跟创建一个文件没什么不同:

[zorro@zorro-pcpipe]$mkfifopipe[zorro@zorro-pcpipe]$ls-lpipeprw-r--r--1zorrozorro0Jul1410:44pipe

可以看到创建出来的文件种类比较特殊,是p种类。表示这是一个管道文件。有了这个管道文件,系统中就有了对一个管道的全局名字,于是任何两个不有关的进程都可以通过这个管道文件进行通信了。譬如大家目前让一个进程写这个管道文件:

[zorro@zorro-pcpipe]$echoxxxxxxxxxxxxxxpipe

此时这个写操作会阻塞,由于管道另一端无人读。这是内核对管道文件概念的默认行为。此时假如有进程读这个管道,那样这个写操作的阻塞才会解除:

[zorro@zorro-pcpipe]$catpipexxxxxxxxxxxxxx

大伙可以察看到,当大家cat完这个文件之后,另一端的echo命令也返回了。这就是命名管道。

Linux系统无论对于命名管道和匿名管道,底层都用的是同一种文件系统的操作行为,这种文件系统叫pipefs。大伙可以在/etc/proc/filesystems文件中找到你的系统是否支持这种文件系统:

[zorro@zorro-pcpipe]$cat/proc/filesystems|greppipefsnodevpipefs

察看完了怎么样在命令行中用管道之后,大家再来看看怎么样在系统编程中用管道。

PIPE

大家可以把匿名管道和命名管道分别叫做PIPE和FIFO。这主要由于在系统编程中,创建匿名管道的系统调用是pipe(),而创建命名管道的函数是mkfifo()。用mknod()系统调用并指定文件种类为为S_IFIFO也可以创建一个FIFO。

用pipe()系统调用可以创建一个匿名管道,这个系统调用的原型为:

#includeunistd.hintpipe(intpipefd[2]);

这个办法将会创建出两个文件描述符,可以用pipefd这个数组来引用这两个描述符进行文件操作。pipefd[0]是读方法打开,作为管道的读描述符。pipefd[1]是写方法打开,作为管道的写描述符。从管道写端写入的数据会被内核缓存直到有人从另一端读取为止。大家来看一下怎么样在一个进程中用管道,虽然这个例子并没什么意义:

[zorro@zorro-pcpipe]$catpipe.c#includestdlib.h#includestdio.h#includeunistd.h#includestring.h#defineSTRINGhelloworld!intmain(){intpipefd[2];charbuf[BUFSIZ];if(pipe(pipefd)==-1){perror(pipe());exit(1);}if(write(pipefd[1],STRING,strlen(STRING))0){perror(write());exit(1);}if(read(pipefd[0],buf,BUFSIZ)0){perror(write());exit(1);}printf(%s/n,buf);exit(0);}

这个程序创建了一个管道,并且对管道写了一个字符串之后从管道读取,并打印在标准输出上。用一个图来讲明这个程序的状况就是如此的:

cript>cript>

一个进程自己给自己发送消息这当然不叫进程间通信,所以实质状况中大家不会在单个进程中用管道。进程在pipe创建完管道之后,总是都要fork产生子进程,成为如下图表示的样子:

cript>cript>

如图中描述,fork产生的子进程会继承父进程对应的文件描述符。借助这个特质,父进程先pipe创建管道之后,子进程也会得到同一个管道的读写文件描述符。从而达成了父子两个进程用一个管道可以完成半双工通信。此时,父进程可以通过fd[1]给子进程发消息,子进程通过fd[0]读。子进程也可以通过fd[1]给父进程发消息,父进程用fd[0]读。程序实比如下:

[zorro@zorro-pcpipe]$catpipe_parent_child.c#includestdlib.h#includestdio.h#includeunistd.h#includestring.h#includesys/types.h#includesys/wait.h#defineSTRINGhelloworld!intmain(){intpipefd[2];pid_tpid;charbuf[BUFSIZ];if(pipe(pipefd)==-1){perror(pipe());exit(1);}pid=fork();if(pid==-1){perror(fork());exit(1);}if(pid==0){printf(Childpidis:%d/n,getpid());if(read(pipefd[0],buf,BUFSIZ)0){perror(write());exit(1);}printf(%s/n,buf);bzero(buf,BUFSIZ);snprintf(buf,BUFSIZ,Messagefromchild:Mypidis:%d,getpid());if(write(pipefd[1],buf,strlen(buf))0){perror(write());exit(1);}}else{printf(Parentpidis:%d/n,getpid());snprintf(buf,BUFSIZ,Messagefromparent:Mypidis:%d,getpid());if(write(pipefd[1],buf,strlen(buf))0){perror(write());exit(1);}sleep(1);bzero(buf,BUFSIZ);if(read(pipefd[0],buf,BUFSIZ)0){perror(write());exit(1);}printf(%s/n,buf);wait(NULL);}exit(0);}

父进程先给子进程发一个消息,子进程接收到之后打印消息,之后再给父进程发消息,父进程再打印从子进程接收到的消息。程序实行成效:

[zorro@zorro-pcpipe]$./pipe_parent_childParentpidis:8309Childpidis:8310Messagefromparent:Mypidis:8309Messagefromchild:Mypidis:8310

从这个程序中大家可以看到,管道事实上可以达成一个半双工通信的机制。用同一个管道的父子进程可以分时给他们发送消息。大家也可以看到对管道读写的一些特征,即:

在管道中没数据的状况下,对管道的读操作会阻塞,直到管道内有数据为止。当一次写的数据量低于管道容量的时候,对管道的写操作一般不会阻塞,直接将要写的数据写入管道缓冲区即可。

当然写操作也不会再所有状况下都不阻塞。这里大家要先来认识一下管道的内核达成。上文说过,管道事实上就是内核控制的一个内存缓冲区,既然是缓冲区,就有容量上限。大家把管道一次最多可以缓存的数据量大小叫做PIPESIZE。内核在处置管道数据的时候,底层也要调用类似read和write如此的办法进行数据拷贝,这种内核操作每次可以操作的数据量也是有限的,普通的操作长度为一个page,即默觉得4k字节。大家把每次可以操作的数据量长度叫做PIPEBUF。POSIX标准中,对PIPEBUF有长度限制,需要其最小长度不能低于512字节。PIPEBUF有哪些用途是,内核在处置管道的时候,假如每次读写操作的数据长度不大于PIPEBUF时,保证其操作是原子的。而PIPESIZE的影响是,大于其长度的写操作会被阻塞,直到目前管道中的数据被读取为止。

在Linux 2.6.11之前,PIPESIZE和PIPEBUF事实上是一样的。在这之后,Linux重新达成了一个管道缓存,并将它与写操作的PIPEBUF达成成了不一样的定义,形成了一个默认长度为65536字节的PIPESIZE,而PIPEBUF只影响有关读写操作的原子性。从Linux 2.6.35之后,在fcntl系统调用办法中达成了F_GETPIPE_SZ和F_SETPIPE_SZ操作,来分别查询目前管道容量和设置管道容量。管道容量容量上限可以在/proc/sys/fs/pipe-max-size进行设置。

#defineBUFSIZE65536......ret=fcntl(pipefd[1],F_GETPIPE_SZ);if(ret0){perror(fcntl());exit(1);}printf(PIPESIZE:%d/n,ret);ret=fcntl(pipefd[1],F_SETPIPE_SZ,BUFSIZE);if(ret0){perror(fcntl());exit(1);}......

PIPEBUF和PIPESIZE对管道操作的影响会由于管道描述符是不是被设置为非阻塞方法而有行为变化,n为要写入的数据量时具体为:

O_NONBLOCK关闭,n = PIPE_BUF:

n个字节的写入操作是原子操作,write系统调用或许会由于管道容量(PIPESIZE)没足够的空间存放n字节长度而阻塞。

O_NONBLOCK打开,n = PIPE_BUF:

假如有足够的空间存放n字节长度,write调用会立即返回成功,并且对数据进行写操作。空间不够则立即显示错误返回,并且errno被设置为EAG人工智能N。

O_NONBLOCK关闭,n PIPE_BUF:

对n字节的写入操作不保证是原子的,就是说这次写入操作的数据或许会跟其他进程写这个管道的数据进行交叉。当管道容量长度低于要写的数据长度的时候write操作会被阻塞。

O_NONBLOCK打开,n PIPE_BUF:

假如管道空间已满。write调用显示错误返回并且errno被设置为EAG人工智能N。假如没满,则或许会写入从1到n个字节长度,这取决于目前管道的剩余空间长度,并且这类数据可能跟别的进程的数据有交叉。

以上是在用半双工管道的时候应该注意的事情,由于在这样的情况下,管道的两端都可能有多个进程进行读写处置。假如再加上线程,则事情可能变得更复杂。事实上,大家在用管道的时候,并不推荐如此来用。管道推荐怎么使用是其单工模式:即只有两个进程通信,一个进程只写管道,另一个进程只读管道。达成为:

[zorro@zorro-pcpipe]$catpipe_parent_child2.c#includestdlib.h#includestdio.h#includeunistd.h#includestring.h#includesys/types.h#includesys/wait.h#defineSTRINGhelloworld!intmain(){intpipefd[2];pid_tpid;charbuf[BUFSIZ];if(pipe(pipefd)==-1){perror(pipe());exit(1);}pid=fork();if(pid==-1){perror(fork());exit(1);}if(pid==0){close(pipefd[1]);printf(Childpidis:%d/n,getpid());if(read(pipefd[0],buf,BUFSIZ)0){perror(write());exit(1);}printf(%s/n,buf);}else{close(pipefd[0]);printf(Parentpidis:%d/n,getpid());snprintf(buf,BUFSIZ,Messagefromparent:Mypidis:%d,getpid());if(write(pipefd[1],buf,strlen(buf))0){perror(write());exit(1);}wait(NULL);}exit(0);}

这个程序事实上比上一个要简单,父进程关闭管道的读端,只写管道。子进程关闭管道的写端,只读管道。整个管道的打开成效最后成为下图所示:

cript>cript>

此时两个进程就只用管道达成了一个单工通信,并且这种状况下不需要考虑多个进程同时对管道写产生的数据交叉的问题,这是最经典的管道打开方法,也是大家推荐的管道用方法。另外,作为一个技术员,即便大家知道了Linux管道的达成,大家的代码也不可以依靠其特质,所以处置管道时该越界判断还是要判断,该错误检查还是要检查,如此代码才能更健壮。

FIFO

命名管道在底层的达成跟匿名管道完全一致,不同只不过命名管道会有一个全局可见的文件名以供其他人open打开用。再程序中创建一个命名管道文件的办法有两种,一种是用mkfifo函数。另一种是用mknod系统调用,例子如下:

[zorro@zorro-pcpipe]$catmymkfifo.c#includestdio.h#includesys/types.h#includesys/stat.h#includestdlib.hintmain(intargc,char*argv[]){if(argc!=2){fprintf(stderr,Argumenterror!/n);exit(1);}if(mknod(argv[1],0600|S_IFIFO,0)0){perror(mknod());exit(1);}exit(0);}

大家用第一个参数作为创建的文件路径。创建完之后,其他进程就能用open()、read()、write()标准文件操作等办法进行用了。其余所有些操作跟匿名管道用类似。应该注意的是,无论命名还是匿名管道,它的文件描述都没偏移量的定义,所以不可以用lseek进行偏移量调整。

 
标签: linux 系统 管道
打赏
 
更多>同类二手资讯
0相关评论

热门推荐
推荐图文
推荐二手资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  免责声明  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报