IPC(进程间通信,InterProcess Communication)
进程间通信方式有管道、
管道
管道的概念
- 本质
- 内核缓冲区
- 伪文件:不占用磁盘空间
- 特点
- 两部分
- 读端,写端,对应两个文件描述符
- 数据写端流入,读端流出
- 操作管道的进程被销毁之后,管道占用的内存空间自动被释放了
- 管道默认是阻塞的,读写都是阻塞的。
- 两部分
- 管道的原理
- 内部实现方式:队列
- 环形队列
- 队列的特点是先进先出
- 内核缓冲区大小
- 默认为4k
- 大小会根据实际情况做适当调整
- 内部实现方式:队列
- 管道的局限性
- 队列
- 数据只能读取一次,不能重复读取
- 半双工
- 匿名管道
- 适用于有血缘关系的进程
- 队列
- 创建匿名管道
- int pipe(int fd[2]);
- 返回值:如果匿名管道创建成功,返回0。
- man pipe 2查看说明文档
- fd[0]为读端文件描述符,fd[1]为写端文件描述符
- int pipe(int fd[2]);
- 父子进程使用管道通信
- 父进程间通信,实现ps aux | grep bash
- 数据重定向:dup2
- exclp
- 注意事项:父进程读,关闭写端;父进程写,关闭读端
- 兄弟进程间通信,实现ps aux | grep bash
- 父子进程间通信是否需要sleep函数
- 父进程写,写得慢
- 子进程 读,读得快
- 兄弟进程间通信
- 父进程-资源回收
- 父进程间通信,实现ps aux | grep bash
管道的读写行为
- 读操作
- 有数据
- read(fd)-正常读,返回读出的字节数
- 无数据
- 写端全部关闭
read解除阻塞,返回0,相当于读文件读到了尾部 - 写端尚未全部关闭
read阻塞
- 写端全部关闭
- 有数据
- 写操作
- 读端全部关闭
- 管道破裂,进程被终止
内核给当前进程发信号SIGPIPE
kill -l 可以查看信号
- 管道破裂,进程被终止
- 读端尚未全部关闭
- 缓冲区写满了
write阻塞 - 缓冲区没有满
write继续写直到缓冲区满
- 缓冲区写满了
- 读端全部关闭
- 如何设置非阻塞
- 默认读写两端都阻塞
- 设置读端为非阻塞pipe(int fd[2])
- fcntl - 变参函数
- 复制文件描述符-dup
- 修改文件属性-open的时候对应的flag
- 设置方法:
获取原来的flags
int flags = fcntl(fd[0], F_GETFL);
// 设置新的flags
flags |= O_NONBLOCK;
fcntl(fd[0], F_SETFL, flags);
查看管道缓冲区大小
- ulimit -a
- ulimit用于限制shell启动进程所占用的资源,包括一各种类型的限制:所创建的内核文件的大小、进程数据块的大小、shell进程所创建文件的大小、内存锁住的大小、常驻内存集的带下、打开文件描述符的数量、分配堆栈的最大大小、CPU时间、单个用户的最大线程数、Shell进程所能使用的最大虚拟内存。
- fpathconf函数
FIFO
特点
- 有名管道
- 在磁盘上有这样一个文件ls -l -> p
- 伪文件,在磁盘大小永远为0
- 在内核中有一个对应的缓冲区
- 半双工的通信方式
使用场景
- 没有亲缘关系的进程间通信
创建方式
- 命令:mkfifo 管道名
- 函数:mkfifo
fifo文件可以使用IO函数进行操作
- open/close
- read/write
- 不能执行lseek
进程间通信
两个不相干的进程A(a.c) B(b.c)
a.c -> read
- int fd = open(“myfifo”, O_RDONLY);
- read(buf, sizeof(buf));
- close(fd);
b.c –> write
int fd1 = open(“myfifo”, O_WRONLY);
write(fd1, “hello world”);
内存映射区
mmap创建内存映射区。
mmap-创建内存映射
作用:将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
函数头文件#include<mman.h>
函数原型:
1
void* mmap(void * adrr, size_t length, int prot, int flags, int fd, off_t offset);
void *mmap(void *adrr, // 映射区首地址,传入NULL
size_t length, // 映射区的大小,不能为0,实际分配的应该是4k的倍数,为什么呢?
int prot, // 映射区权限- PROT_READ // 映射区必须有读权限
- PROT_WRITE // 可选
- PROT_READ | PROT_WRITE
int flags, // 标志位参数
- MAP_SHARED
- 修改了内存数据会同步到磁盘
- MAP_PRIVATE
- 修改了内存数据,数据不会同步到磁盘
int fd, // 文件描述符
- 要映射的文件对应的文件描述符,这个文件描述符是通过对文件进行open操作得到的。
off_t offset, // 映射文件的偏移量
- 映射时文件指针的偏移量,必须是4k的整数倍
);
返回值
- 调用成功时,返回映射区的首地址
- 调用失败时,返回MAP_FAILED,其实就是一个(void*)-1
munmap-释放内存映射区
- 函数原型:int munmap(void* addr, size_t length);
- addr-mmap的返回值,映射区的首地址
- length-mmap的第二个参数,映射区的长度
- 函数原型:int munmap(void* addr, size_t length);
思考题
- 如果open时指定O_RDONLY,mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
- mmap调用失败
- open文件指定的权限应该大于等于mmap第三个参数prot指定的权限
- 如果文件偏移量为1000会怎么样?
- 文件偏移量必须是4k(4096)的整数倍
- mmap什么时候会调用失败?
- 第二个参数length=0
- 第三个参数必须指定PROT_READ
- fd对应的打开权限必须大于等于prot指定的权限
- 偏移量必须是4096的整数倍
- 可以open的时候O_CREAT一个新文件来创建映射区?
- 可以,但是需要做文件拓展
- lseek
- truncate(path, length)
- 可以,但是需要做文件拓展
- mmap后关闭文件描述符,对内存映射区有没有影响?
- 没有影响
- 对ptr越界操作,ptr是mmap的返回值,即映射区的首地址?
- 段错误(操作非法内存)
- 如果open时指定O_RDONLY,mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
进程间通信
- 有亲缘关系的
- 父子进程共享内存映射区
- 没有亲缘关系的
- 如何通信?
- 有亲缘关系的
mmap实现内存映射
- 必须有一个文件
- 文件数据什么时候有用
- 单纯文件映射
- 进程间通信
- 文件数据是没有用的
父子进程永远共享的有哪些?
- 文件描述符
- 内存映射区
示例
1 |
|
lseek当第三个参数whence为SEEK_END,则该文件的文件偏移量设置为文件长度加offset。若函数成功执行,返回新的文件偏移量。所以,当whence为SEEK_END, offset为0时,返回当前文件的长度。
参考文献
[1]ulimit:https://man.linuxde.net/ulimit