前面我们介绍了什么是IPC,以及IPC中得管道,现在我们来介绍IPC得另一种方式mmap
原理:将一个文件或者其它对象映射进内存。
1.使用普通文件提供的内存映射 【一般用于无血缘关系进程】
2.【一般用于有血缘关系的进程】使用特殊文件提供匿名内存映射,【MAP_ANONYMOUS or MAP_ANON 在 UINX中是没有的但是呢 UNIX中也有 匿名映射的方式(linux中也可以这么用)就是用这两个文件/dev/zero, /dev/null(这两个稍后在匿名映射中介绍)】
我们先来介绍介绍关于mmap的 API
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 【创建映射区】
int munmap(void *addr, size_t length); 【取消内存映射】
参数addr:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
参数length:代表将文件中多大的部分映射到内存。
参数prot:映射区域的保护方式。可以为以下几种方式的组合:
- PROT_EXEC 执行
- PROT_READ 读取
- PROT_WRITE 写入
- PROT_NONE 不能存取
参数flags:影响映射区域的各种特性。必须要指定MAP_SHARED 或MAP_PRIVATE。
- MAP_SHARED - 映射区域数据与文件对应,允许其他进程共享
- MAP_PRIVATE - 映射区域生成文件的copy,修改不同步文件
- MAP_ANONYMOUS - 建立匿名映射。此时会忽略参数fd,不涉及文件,而 且映射区域无法和其他进程共享。 MAP_DENYWRITE - 允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
- MAP_LOCKED - 将映射区域锁定住,这表示该区域不会被置swap 【操作系统的页面置换】
参数fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。
参数offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。
函数原型 int msync ( void * addr, size_t len, int flags) 【同步】
头文件 #include<sys/mman.h>
- addr:文件映射到进程空间的地址;
- len:映射空间的大小;
- flags:【前面的文件IO我以及详细的介绍了 异步同步,和同步的原理】刷新的参数设置,可以取值MS_ASYNC/ MS_SYNC 其中: 取值为MS_ASYNC(异步)时,调用会立即返回,不等到更新的完成; *取值为MS_SYNC(同步)时,调用会等到更新完成之后返回;
void * mremap(void *old_address, size_t old_size , size_t new_size, int flags); 【重新映射】
头文件 #include <unistd.h>
#include <sys/mman.h>
- addr: 上一次已映射到进程空间的地址;
- old_size: 旧空间的大小;
- new_size: 重新映射指定的新空间大小;
- flags: 取值可以是0或者MREMAP_MAYMOVE,0代表不允许内核移动映射区域,MREMAP_MAYMOVE则表示内核可以根据实际情况移动映射区域以找到一个符合 new_size大小要求的内存区域
- 返回值 ,成功则返回0;失败则返回-1;
接下来我们来看看【有名映射】代码Demo
#include <iostream>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <wait.h>using namespace std;#define NEW_MMAP_SIZE 4096static int Debug = 1;int
main(int argc, char*argv[])
{pid_t pid;int fd;fd = open("./mmap.txt", O_RDWR);struct stat st;fstat(fd, &st); // get file informationint file_lenth = st.st_size;int new_lenth = file_lenth + NEW_MMAP_SIZE;if(Debug)printf("file_lenth:%d, new_lenth:%d \n", file_lenth, new_lenth);ftruncate(fd, new_lenth); // extendint *start = NULL;start = (int*)mmap(NULL, sizeof(int),PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 4096);if(start == MAP_FAILED){perror("mmap error");close(fd);exit(1);}close(fd);pid = fork();if(pid == 0){// readsleep(1);printf("data: %d \n", *start);}else if(pid > 0){// write*start = 250;wait(NULL);// mremapmsync(start, sizeof(int), MS_SYNC); // sync datamunmap(start, sizeof(int));while(1)sleep(1);}else{perror("fork error");munmap(start, sizeof(int));exit(1);}return 0;
}
【匿名映射】代码Demo
#include <iostream>
#include <sys/mman.h>
#include <unistd.h>
#include <string>#include <sys/types.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>// #define OFFSETOF(_Ty, number) reinterpret_cast<unsigned>(&(((_Ty*)0)->number))using namespace std;static int Debug = 1;struct student_t{// string name_; // segment fault // string There is memory movement at the bottomchar name_[32];int age_;char stu_num_[32];// string stu_num_; // segment fault
};ostream&
operator<<(ostream& out, const struct student_t& stu)
{out << "name: " << stu.name_ << '\n'<< "age: " << stu.age_ << '\n' << "stu_num: " << stu.stu_num_ << endl;return out;
}/*
istream&
operator>>(istream& in, struct student_t* stu)
{printf("please input name: \n");in >> stu->name_; fflush(stdin);printf("please input age: \n");in >> stu->age_; fflush(stdin);printf("please input stu_num: \n");in >> stu->stu_num_; fflush(stdin);
}
*/int
main(int argc, char*argv[])
{// anonymousstudent_t* start = NULL;pid_t pid;#if 1 // LINUXstart = (student_t*)mmap(NULL, sizeof(student_t),PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS,-1, 0);if(start == MAP_FAILED){perror("mmap error");exit(1);}#else // UNIXint fd;fd = open("/dev/zero", O_RDWR);start = mmap(NULL, sizeof(struct student_t), PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 0);#endifpid = fork();if(pid < 0){perror("fork error");munmap(start, sizeof(struct student_t));exit(1);}else if(pid == 0){ // son readsleep(1);if(Debug)printf("I am child, ID: %u \n", getpid());cout << *start << endl;cout << "offsetof:" << offsetof(student_t, age_) << endl;}else{ // parent writeif(Debug)printf("I am parent, ID: %u \n", getpid());start->age_ = 21;strncpy(start->name_, "libero", 7);strncpy(start->stu_num_, "201710913207", 15);waitpid(pid, NULL, 0);msync(start,sizeof(student_t), MS_SYNC); // sync datamunmap(start, sizeof(student_t));}return 0;
}
总结:【重点】
- 映射区的权限应该小于等于文件的权限,并且在创建映射区的时候会自动读取一次文件一遍创建映射去(文件的权限必须有读权限)
- offset必须是4096的整数倍数,因为是mmu提供的映射服务,mmu中最小的又是 4k.
- 当MAP_SHARED:要求:映射区的权限应该 <= 文件打开的权限(处于对映射区的保护),而MAP_PRIVATE则没所谓,因为mmap中的权限是对内存的限制。
- 映射区的释放与文件的关闭无关。只要映射创建成功,文件可以立即关闭【只是换了种方式操作文件】
- 特别注意:当映射文件大小为0 时,不能创建映射区,所以用于映射的文件必须有实际的大小,mmap使用时常常出现总线错误,通常是由于共享文件存储空间大小引起的。
- 上面的匿名映射我也给大家注释了BUG,用于了一个string 类型的数据,string底层是有扩容机制的,比如说push_back之类的,如果原先的 容量不够了 他会在内部调用 realloc这个函数,在另一个地方把开辟一个更大的空间,把原来的 数据拷贝过来 在释放原来的空间,所以mmap谨慎用有扩容机制的 容器之类的东西。应为 mmap提供的内存是固定那一块的 你突然 跳出去了 就有问题,我亲身测试 为 段错误
- munmap传入的一定是mmap返回的 地址,禁止对返回值的 ++之类的操作
- mmap创建映射区出错的概率非常的高,一定要检查返回值
接下来文章讲解信号机制【非常多的陷阱,后面详细的介绍】