匿名管道(pipe)
管道是一种最基本的IPC机制,由pipe函数创建:
#include < unistd.h >int pipe(int filedes[2]);
调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。
开辟了管道之后如何实现两个进程间的通信?如下:
1. 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道读端,?子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
- 使用管道有一些限制:
1、两个进程通过一个管道只能实现单向通信。
2、管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖先那里继承管道文件描述符。 - 使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):
1、如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程 从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
2、如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的 进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
3、如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
4、 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
1. 从管道中读取数据
1. 如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0 2. 当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数
2. 向管道中写入数据
1. 向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞 2. 只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号,应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止)
3. 用于shell
管道可用于输入输出重定向,它将一个命令的输出直接定向到另一个命令的输入,当在某个shell程序(Bourne shell或C shell等)键入who │ wc -l后,相应shell程序将创建who以及wc两个进程和这两个进程间的管道。考虑下面的命令行
kill -l | grep SIGRTMIN 30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1 34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5 38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9 42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13 46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14
4. 用于具有亲缘关系的进程间通信
管道实现细节
在 Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的
有两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作
管道的局限性
管道的主要局限性正体现在它的特点上
1. 只支持单向数据流 2. 只能用于具有亲缘关系的进程之间 3. 没有名字 4. 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小) 5. 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等
实例:
#include <stdio.h> #include <stdlib.h> #include <limits.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include "config.h" #include "unpipc.h" #include "my_err.h" /* struct ipc_perm { uid_t uid;//owner user id gid_t gid;//owner group id uid_t cuid;//creater user id gid_t cgid;//creater group id mode_t mode;//weite or reads ulong_t seq;// key_t key; };*/ //get Posix IPC name char *px_ipc_name(const char* name) { char *dir,*dst,*slash; if((dst=malloc(PATH_MAX))==NULL)// return NULL; if((dir=getenv("PX_IPC_NAME"))==NULL) { #ifdef POSIX_IPC_PREFIX dir=POSIX_IPC_PREFIX //from config.h #else dir="/tmp/"; #endif } slash=(dir[strlen(dir)-1]==‘/‘)?"":"/"; snprintf(dst,PATH_MAX,"%s%s%s",dir,slash,name); return dst; } void client(int readfd,int writefd) { size_t len, n; char buff[MAXLINE]; fgets(buff,MAXLINE,stdin); len=strlen(buff); if(buff[len-1]==‘\n‘) len--; //write pathname to ipc channel write(writefd,buff,len); //read from ipc,write to standard output if((n=read(readfd,buff,MAXLINE))>0)//reads access from pipe write(STDOUT_FILENO,buff,n);//stdou_fileno from unistd.h } void server(int readfd,int writefd) { int fd; size_t n; char buff[MAXLINE+1]; //read pthname from ipc channel if((n=read(readfd,buff,MAXLINE))==0) err_quit("end-of-file while reading pathname"); buff[n]=‘\0‘; if((fd=open(buff,O_RDONLY))<0) { snprintf(buff+n,sizeof(buff)-n,"can‘t open,%s\n",strerror(errno)); n=strlen(buff); write(writefd,buff,n);//error must tell client } else { while((n=read(fd,buff,MAXLINE))>0) write(writefd,buff,n); close(fd); } } int main() { //printf("%d:\n",PATH_MAX); int pipe1[2],pipe2[2]; pid_t childpid; pipe(pipe1); pipe(pipe2); if((childpid=fork())==0) { close(pipe1[1]);//children close pipe1 write close(pipe2[0]);//children close pipe2 reads server(pipe1[0],pipe2[1]); exit(0); } close(pipe1[0]);//father close pipe1 reads close(pipe2[1]);//fathre close pipe2 write client(pipe2[0],pipe1[1]); waitpid(childpid,NULL,0); return 0; }
原文地址:https://www.cnblogs.com/tianzeng/p/9353840.html