目录
存储映射
存储映射I/O(Memory-mapped I/O)使一个磁盘文件与存储空间中的一个缓冲区相映射. 于是当从缓冲区中取数据, 就相当于读文件中的相应字节. 与此类似, 将数据存入缓冲区, 则相应的字节就自动写入文件. 这样, 就可在不适用read和write函数的情况下, 使用地址(指针)完成I/O操作
使用这种方法, 首先应通知内核, 将一个指定文件映射到存储区域中. 这个映射工作可以通过mmap函数来实现
作用: 将磁盘文件的数据映射到内存, 用户通过修改内存就能修改磁盘文件
mmap创建内存映射
匿名映射
通过使用我们发现, 使用映射区来完成文件读写操作十分方便, 父子进程间通信也较容易. 但缺陷是, 每次创建映射区一定要依赖一个文件才能实现. 通常为了建立映射区要open一个temp文件, 创建好了再unlink, close掉, 比较麻烦. 可以直接使用匿名映射来代替. 其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定
使用MAP_ANONYMOUS (或MAP_ANON), 如: int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
需注意的是, MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏. 在类Unix系统中如无该宏定义, 可使用如下两步来完成匿名映射区的建立
fd = open("/dev/zero", O_RDWR);p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);
mmap无血缘关系进程间通信
实质上mmap是内核借助文件帮我们创建了一个映射区, 多个进程之间利用该映射区(需借助文件)完成数据传递. 由于内核空间多进程共享, 因此无血缘关系的进程间也可以使用mmap来完成通信. 只要设置相应的标志位参数flags即可. 若想实现共享, 当然应该使用MAP_SHARED了
基础API
mmap
void *mmap( void *addr, // 建立映射区首地址, 由linux内核指定, 传NULL size_t length, // 映射区的大小, 4k的整数倍, 不能为0, 一般文件有多大length就为多大 int prot, // 映射区的权限, PROT_READ(映射区 必须 要有读权限), PROT_WRITE int flags, // 标志位参数, MAP_SHARED(数据同步到磁盘), MAP_PRIVATE(数据不会同步到磁盘), 有血缘关系通信需是MAP_SHARED int fd, // 文件描述符, 要映射文件对应的fd(open得来的) off_t offset // 映射文件的偏移量, 映射时文件指针的偏移量, 必须是4k的整数倍, 一般为0);
返回值: 成功, 映射区首地址; 失败, MAP_FAILED
思考问题
如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功? 不能对ptr做操作, 可以复制一份, 对复制的指针进行操作 如果open是O_RDONLY, mmap时prot参数指定PROT_READ|PROT_WRITE会怎样 mmap调用失败, open文件指定权限应该大于等于mmap第三个参数prot指定的权限 如果文件偏移量为1000会怎样 必须是4096的整数倍 如果不检测mmap的返回值会怎么样 没什么影响 mmap什么情况下会调用失败 第二个参数length=0; 第三个参数没有指定PROT_READ; 第五个参数fd对应的open权限必须大于port权限; offset必须是4096的整数倍 可以open的时候O_CREAT一个新文件来创建映射区吗 可以, 需要做文件拓展(lseek, truncate) mmap后关闭文件描述符, 对mmap映射有无影响 没有影响 对ptr越界操作会怎样 段错误munmap
int munmap(void *addr, size_t length);
参数:
addr: mmap的返回值 length: mmap的第二个参数进程间通信, 不阻塞, 数据直接才内存中处理,
有血缘关系, 父子进程共享内存映射区, 可以创建匿名映射区, 可以不需要磁盘文件进行通信 没有血缘关系 不能使用匿名映射的方式, 只能借助磁盘文件创建映射区 A(a.c) B(b.c) a.c:int fd1 = open("XXX"); void *ptr = mmap(,,,fd1, 0);
对映射区(ptr)进行读写操作 b.c: int fd2 = open("hello"); void ptr* = mmap(,,,fd2, 0);
对映射区(ptr)进行写操作 示例程序
利用内存映射区读文件
#include#include #include #include #include #include #include #include int main(int argc, const char* argv[]) { // 打开一个文件 int fd = open("english.txt", O_RDWR); int len = lseek(fd, 0, SEEK_END); // 创建内存映射区 void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { perror("error"); exit(1); } printf("%s", (char*)ptr); // 释放内存映射区 munmap(ptr, len); close(fd); return 0;}
MAP_PRIVATE与MAP_SHARED测试
父子进程共享:
- 打开的文件
- mmap建立的映射区(但必须要使用MAP_SHARED)
#include#include #include #include #include #include #include #include #include int main(int argc, const char* argv[]) { // 打开一个文件 int fd = open("english.txt", O_RDWR); int len = lseek(fd, 0, SEEK_END); // 通信测试 //void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); // MAP_PRIVATE, 父子进程不可通信 void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // MAP_SHARED, 父子进程可通信 if (ptr == MAP_FAILED) { perror("error"); exit(1); } close(fd); //printf("%s", (char*)ptr); pid_t pid = fork(); if (pid == -1) { perror("fork error"); exit(1); } if (pid > 0) { // 父进程写数据 strcpy((char*)ptr, "haha, hehe"); // 回收 wait(NULL); } else if (pid == 0) { // 读数据 printf("%s\n", (char*)ptr); } // 释放内存映射区 //ptr++; int ret = munmap(ptr, len); if (ret == -1) { perror("mmap error"); exit(1); } return 0;}
有血缘关系匿名映射区通信
#include#include #include #include #include #include #include #include #include int main(int argc, const char* argv[]) { // 创建匿名内存映射区 int len = 4096; void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); // MAP_SHARED可通信 //void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); // MAP_PRIVATE, 不可通信 if (ptr == MAP_FAILED) { perror("error"); exit(1); } //printf("%s", (char*)ptr); pid_t pid = fork(); if (pid == -1) { perror("fork error"); exit(1); } if (pid > 0) { // 父进程写数据 strcpy((char*)ptr, "haha"); // 回收 wait(NULL); } else if (pid == 0) { // 读数据 printf("%s\n", (char*)ptr); } // 释放内存映射区 //ptr++; int ret = munmap(ptr, len); if (ret == -1) { perror("mmap error"); exit(1); } return 0;}
无血缘关系利用内存映射区通信
读端
#include#include #include #include #include #include #include int main(int argc, const char *argv[]) { int fd = open("temp", O_RDWR | O_CREAT, 0664); ftruncate(fd, 4096); int len = lseek(fd, 0, SEEK_END); void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { perror("mmap"); exit(1); } while (1) { sleep(1); printf("%s\n", (char*)ptr+1024); } // 释放 int ret = munmap(ptr, len); if (ret == -1) { perror("munmap"); exit(1); } return 0;}
写端
#include#include #include #include #include #include #include int main(int argc, const char *argv[]) { int fd = open("temp", O_RDWR | O_CREAT, 0664); void *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { perror("mmap"); exit(1); } while (1) { char *p = (char *)ptr; p += 1024; strcpy(p, "haha, I'm fine ~\n"); sleep(2); } int ret = munmap(ptr, 4096); if (ret == -1) { perror("munmap"); exit(1); } return 0;}