# 写在前面
文件实现进程间通信,使用文件也可以完成 `IPC`,理论依据是,`fork`后,父子进程共享文件描述符,也就共享打开的文件。~~这很容易想明白吧~~,在内部的原理就是内存映射 ~~(我猜的)~~。
# 存储映射I/O
`存储映射I/O(Memory-mapped I/O)` 使一i个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区取数据,就相当于读文件中的相应字节。与此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样就可以在不使用`read`和`write`函数的情况下,使用地址(指针)完成 I/O 操作。
# 共享内存
## 概述
共享内存是进程间通信中最简单的方式之一 ~~(放洋屁)~~。共享内存允许两个或更多进程访问同一块内存,就如同 `malloc()` 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。

## 共享内存的特点
- 共享内存使进程间共享数据的一种最快的方法,一个进程向共享的内存区域写入了数据,共享这个内存区域的所有进程都可以立即看到其中的内容
- 使用共享内存要注意多个进程对一个给定存储访问的互斥。若一个进程正在向共享内存区域写数据,则在它完成这一步操作前,别的进程不应该去读、写这些数据
## 创建共享内存映射
函数原型:`void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);`
作用:创建共享内存映射
头文件:
- #include <sys/mman.h>
参数:
- addr:要创建的共享内存映射区的首地址,通常传`NULL`代表系统自动分配首地址
- length:要创建的共享内存映射区的长度
- port:共享内存映射区的读写属性,`PROT_READ`、`PROT_WRITE`、`PROT_READ|PROT_WRITE`
- flags:标注共享内存的共享属性,`MAP_SHARED`、`MAP_PRIVATE`
- fd:用于创建共享内存映射区的那个文件的 文件描述符
- offset:偏移位置,默认0,表示映射文件全部,如需更改必须是 4k 的整数倍
flags参数说明
> <H3>MAP_SHARED</H3>
>这一标志说明了本进程对映射区所进行的存储操作的配置。此标志指定存储操作修改映射文 件,也就是说,存储操作相当于对该文件`write`。必须指定本标志或下一个标志(MAP_PRIVATE),但不能同时指定两者
> <H3>MAP_PRIVATE</H3>
>本标志说明,对映射区的存储操作导致创建该映射文件的一个私有副本。所有后来对该映射区的引用都是引用该副本,而不是原始文件。(此标志的一种用途是用于调试程序,它将依程序文件的正文部分映射至一存储区,但允许用户修改其中的指令。任何修改只影响程序文件的副本,但不影响原文件)
返回值:
- 成功:返回创建的共享内存映射区的首地址
- 失败:返回`MAP_FAILED (void*(-1))`,并设置`errno`
## 注意事项
- 用于创建映射区的文件大小为 0,实际指定非0大小创建映射区,出 “总线错误”。
- 用于创建映射区的文件大小为 0,实际制定0大小创建映射区, 出 “无效参数”`mmap error: Invalid argument`。
- 用于创建映射区的文件读写属性为,只读,映射区属性为读、写,出 `Permission denied`
- 创建映射区,需要用于映射区的文件需要 `read`权限。当访问权限指定为 `MAP_SHARED`时,mmap的读写权限应 `<= `文件`open` 的权限,且只写不行。
- 文件描述符fd,在`mmap`创建映射区完成后即可关闭,后续通过指针访问文件。
- `offset`必须是 4096 的整数倍(MMU映射的最小单位4K)
- 对申请的映射区内存,不能越界访问
- 映射区访问权限为`MAP_PRIVATE`,对内存所作的修改只在内村上有效,不会反映到物理磁盘上
- 映射区访问权限为`MAP_PRIVATE`,只需要`open`文件时,有读权限即可
## 案例
1. 使用mmap实现父子进程间通信
2. 父进程 先 创建映射区。 open( O_RDWR) mmap( MAP_SHARED );
3. 指定 MAP_SHARED 权限
4. fork() 创建子进程。
5. 一个进程读, 另外一个进程写。
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>
int var = 100;
int main(int argc, char *argv[]){
pid_t pid;
int *p;
int fd;
fd = open("testmmp",O_RDWR|O_CREAT|O_TRUNC,0644); //打开一个文件
if(fd < 0){
perror("file error");
exit(1);
}
int len = 10;
ftruncate(fd, len);
p = (int *)mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); //创建共享内存映射
if(p == MAP_FAILED){
perror("mmap error");
exit(1);
}
pid = fork();
if(pid < 0){
perror("fork error");
exit(1);
} else if(pid == 0) {
var = 200;
*p = 5000; //在子进程中向共享内存映射区写入数据
printf("this is child var = %d *p = %d\n", var, *p);
} else {
wait(NULL); //从父进程读出来,理论上var没变,*p应该被更改
printf("this is parent var = %d *p = %d\n", var, *p);
}
close(fd);
return 0;
}
```
执行结果确实如此,也印证了,父子进程共享 mmap 建立的映射映射区

使用mmap在无血缘关系的进程间通信
1. 两个进程 打开同一个文件,创建映射区。
2. 指定flags 为 MAP_SHARED。
3. 一个进程写入,另外一个进程读出。
**【注意】**:<font color="#FF0000">无血缘关系进程间通信。mmap:数据可以重复读取。fifo:数据只能一次读取</font>
```c
//读端
#include <sys/mman.h>
#include <sys/wait.h>
int var = 100;
int main(int argc, char *argv[]){
int *p, fd;
pid_t pid;
fd = open("test.txt", O_RDWR|O_CREAT|O_TRUNC,0644);
int len = 10;
ftruncate(fd, len);
p = (int *)mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
int i = 0;
while(1)
printf("%d\n", *p);
return 0;
}
```
```c
//写端口
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>
int var = 100;
int main(int argc, char *argv[]){
int *p, fd;
pid_t pid;
fd = open("test.txt", O_RDWR|O_CREAT|O_TRUNC,0644);
int len = 10;
ftruncate(fd, len);
p = (int *)mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
if(p == MAP_FAILED){
perror("mmap error");
exit(1);
}
int i = 0;
for(i = 0;i < 10;i++){
*p = i;
sleep(5);
}
return 0;
}
```
运行测试一下就可以了,结果很明显.
## 匿名映射
只能用于 血缘关系进程间通信。
`p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);`
# 回收共享内存映射区
作用:释放映射区
函数原型:int munmap(void *addr, size_t length);
参数:
- addr:mmap 函数的返回值,且必须是没有更改过的
- length:大小
返回值:
- 成功:返回 0
- 失败: 返回 -1
共享存储映射