mmap
存储映射
存储映射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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
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;
}