进程间的通信主题一-----管道
每个进程各自有不同的⽤用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Com!)
简言之,进程间通信的本质就是让不同的进程间可以看见共享同一份资源。
管道
管道的分类:
一、匿名管道
二、命令管道
匿名管道
管道是由pipe函数创建的,(可以以文件的方式来理解)
函数格式:
#include<unistd.h>
int pipe(int filefd[2])
pipe函数在内核中开辟一段缓冲区(称为管道)用于通信,它分别有两个端口,就是pipe函数的参数,filefd[0]是读端,filefd[1]是写端。这两个端口就像是向文件中read和write内容。pipe函数创建管道成功则返回0,失败则返回-1。
匿名管道的特点:
- 单向通信
- 进程间有血缘关系,常用于父子进程间通信
- 面向字节流
- 管道的生命周期随进程,进程退出后,管道文件也随之关闭
- 只有当管道有数据时,读端才读取,所以他们是同步的,不用提供保护机制
管道的读写特殊情况:
- 如果所有指向管道写端的.文件描述符都关闭了管道写端的引用计数等于0.而仍然有
进程 从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像
读到文件末尾一样。
- 如果有指向管道写端的.文件描述符没关闭(管道写端的引.用计数.大于0),.而持有管道写
端的 进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数
据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
- 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进
程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终⽌止。
- 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读
端的 进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时
再 次write会阻塞,直到管道中有空位置了才写入数据并返回
- 创建一个管道
- 创建子进程
- 父进程关闭写端,读取管道文件内容
- 子进程关闭读端,向管道中写入内容
代码实现:
这样将会导致父进程收到信号终止进程
父进程正确读取方式:
验证结果:
命令管道
命令管道与上述描述的匿名管道做大的区别就是,突破了相互通信的进程间必须拥有血缘关系的界限,命令管道可以实现任何进程间的单向通信
创建管道函数格式:
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *path,mode_t mode)
代码实现图:
servet.c文件
client.c文件
makefile文件:
结果图:
管道容量(转自:http://blog.chinaunix.net/uid-27471192-id-3305880.html)
- 根据手册,linux上的PIPE容量为(capacity)65536个字节;实验得到ubuntu的PIPE_BUF为4096。这里要特别说明的是我看到网上有人将这个Capacity和PIPE_BUF混淆了,当 管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现 有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)(PIPE_BUF在 include/linux/limits.h中定义,不同的内核版本可能会有所不同。Posix.1要求PIPE_BUF至少为512字节,red hat 7.2中为4096)
- 要保证write操作是原子操作,一次写入的字节数n就不能超过PIPE_BUF;非阻塞模式下当然还需要有>=n的空余空间,否则写入失败。一次写入大于PIPE_BUF的数据,不管是不是阻塞模式都不保证是原子操作
- 非阻塞模式下,能一次性写入的最大数据量就是管道的容量即65536字节了,注意这里的一次性写入是指write不返回的情况下,和原子操作不是一回事。如果写入的更长,也只能写这么多,write函数会返回已写入的长度
- 如果写端不存在,读的时候会返回0,代表读到了文件尾。如果读端不存在,写的时候就会产生SIGPIPE信号(忽略此信息的情况下write会返回失败,errno为EPIPE)
在linux终端输入*man 7 pipe*命令,可以查找到管道容量的最大和最小字节数
管道的实现机制:(转自:http://www.th7.cn/system/lin/201702/203314.shtml)
管道是由内核管理的一个缓冲区,它的一端连接一个进程的输出,另一端连接一个进程的输入。管道的缓冲区不需要很大,它被设计为环形的数据结构,当两个进程都终止后,管道的生命周期也会被结束
从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:
管道是一个固定大小的缓冲区,在Linux中,该缓冲区的大小为一页,即4kb,使它的大小不会像普通文件那样不加检验的增长。在Linux中,内核使用struct pipe_inode_info结构体来描述一个管道,这个结构体定义在pipe_fs_i.h中。
struct pipe_inode_info结构体
struct pipe_inode_info {//管道等待队列,当pipe为空/满时指向等待的读者和写者 wait_queue_head_t wait; //pipe中非空缓冲区的数量和当前pipe的入口 unsigned int nrbufs, curbuf;//临时释放的页也叫高速缓存区页框指针 struct page *tmp_page;//读进程的标志或ID号 unsigned int readers;//写进程的标志或ID号 unsigned int writers;//在等待队列中睡眠的写进程的个数 unsigned int waiting_writers;//reader的总数 unsigned int r_counter;//writer的总数 unsigned int w_counter;//用于通过信号进行异步I/O通知 struct fasync_struct *fasync_readers; struct fasync_struct *fasync_writers;//pipe对应的inode struct inode *inode;//pipe的环形缓冲区 struct pipe_buffer bufs[PIPE_BUFFERS];};
缓冲区的个数:
#define PIPE_BUFFERS (16)
管理缓冲区的结构
struct pipe_buffer {//包含当前pipe_buffer数据的页 struct page *page; //页中所包含的数据的偏移量,长度 unsigned int offset, len;//与buffer相关的操作 const struct pipe_buf_operations *ops;//pipe_buffer标志 unsigned int flags; unsigned long private;};