mmap 创建共享内存映射

所谓内存映射指的是 让一个磁盘文件与内存中的一个缓冲区相映射,进程访问这块内存时,就等同于访问文件对应映射部分,不必再调用 read / write 。

我们可以使用mmap函数来建立内存和文件某一部分的映射关系。

mmap 创建共享内存映射_第1张图片


目录

一、共享内存映射的创建 / 释放

1、创建共享内存映射:mmap​​​​​​​

2、释放共享内存映射:munmap

二、mmap使用及其注意事项

1、mmap使用

(1) 打开文件

(2) mmap建立映射

(3) 通过映射向文件写入内容

(4) 完整代码

2、mmap使用注意事项

(1) 映射区只要建立成功,文件可以立即关闭

(2) 映射的文件大小必须大于0,否则会报总线错误

(3) 文件偏移量必须为0或者4K的整数倍

(4) 映射空间大小可以大于文件大小,但要注意不要访问区外部分

(5) 设置的映射空间大小 ≠ 实际分配的映射空间大小


一、共享内存映射的创建 / 释放

1、创建共享内存映射:mmap

mmap 函数的作用是创建共享内存映射。mmap函数的参数较多,几乎每一个参数都有注意事项,下面介绍的重点是mmap函数的参数。

(1) addr

用户可以手动指定要映射的内存地址,一般设置为NULL,让OS自动选择合适的内存地址,如果最后映射建立成功,mmap会返回内存中映射区的首地址。

(2) length

为内存中映射地址空间分配的字节数(length > 0)。这里分为了两种情况:

  • length > 文件映射部分大小
  • length < 文件大小

当 length < 文件映射部分大小 时,文件有一部分无法映射到内存。

mmap 创建共享内存映射_第2张图片

当 length > 文件映射部分大小 时,有一部分无法映射到文件,这就意味着,即便向这部分内存写入内容,也不会反馈给文件。

mmap 创建共享内存映射_第3张图片

因此,一般建议设置的映射空间大小直接和文件大小保持一致。文件大小的计算可以使用lseek函数。

// 起始偏移量为0,将文件指针移动到末尾(SEEK_END)
// 返回的结果就是 文件指针相对于起始位置的字节数
int size = lseek(fd, 0, SEEK_END);

(3) prot

指定内存映射空间的访问权限。其实就是要以何种形式来访问这块映射空间,如可读、可写、可执行等,可选值如下:

可选值 含义
PROT_READ 可读
PROT_WRITE 可写
 PROT_EXEC 可执行
PROT_NONE 不可访问

(4) flags

指定内存映射空间的映射方式。可以是共享,代表其他进程可以看到;可以是私有,代表其他进程看不到;也可以是匿名,一般用于有血缘关系之间的进程。可选值如下:

可选值 含义
MAP_SHARED 共享
MAP_PRIVATE 私有
MAP_ANONYMOUS 匿名

注意: MAP_SHARED 和  MAP_PRIVATE必选其一

(5) fd

指定要映射的文件。当一个文件被成功打开的时候,就会有一个文件描述符来唯一的指向该文件。如果是匿名映射,填 -1

:为什么建立内存映射需要打开文件?

:在创建映射区的过程中,隐藏着一次对映射文件的读操作,目的是将部分文件内容读取到映射区。(读取多少,取决于偏移量的设置)

(6) offset

表示映射文件的偏移量。设置为0表示从头开始映射。当你向映射区写入内容的时候,其实是向文件的起始映射位置开始写入,而不是从文件起始位置开始写入。

mmap 创建共享内存映射_第4张图片

映射部分:向映射区写入的内容,会映射到文件的对应部分

剩余部分:内存是以页为单位进行分配的,一页大小是4K,映射区的大小如果小于4K,剩下没有映射的就是“剩余部分”,访问该部分的时候,不会有任何问题,因为这块内存还在一页的范围里。但是你向该部分写入的内容,不会映射到文件,因为在文件里不存在对应的映射区域。

区外部分:该部分是内存没有分配给你的,访问这部分会报总线错误。

(6) 返回值

 映射创建成功,返回创建映射区的首地址,失败返回 MAP_FAILED(((void *) -1)),设置errno值

2、释放共享内存映射:munmap

释放共享内存映射的时候,需要提供映射区的首地址,以及你手动分配的大小,注意这里要填的不是实际分配的大小,而是 mmap 第二个参数填入的值。

mmap 创建共享内存映射_第5张图片

二、mmap使用及其注意事项

1、mmap使用

(1) 打开文件

映射文件必须以读写 (O_RDWR) 的形式打开,同时该文件的大小必须要大于0,否则会出现

int fd = open("./log.txt", O_RDWR);
if(fd < 0)
{
    perror("open");
    return -1;
}

(2) mmap建立映射

针对mmap 的各个参数,设置如下:

  • 第一个参数设为NULL,交由OS来分配内存
  • 第二个参数设为文件大小
  • 第三个参数设为可写 PROT_WRITE
  • 第四个参数设为共享 MAP_SHARED
  • 第五个参数填入上面得到的文件描述符
  • 第六个参数设为0,代表从文件起始位置开始映射
int len = lseek(fd, 0, SEEK_END);
void* addr = mmap(NULL, len, PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED)
{
    perror("mmap");
    return -1;
}

(3) 通过映射向文件写入内容

我们向内存映射空间中写入一个字符串,然后看一下文件中是否有对应的字符串

const char* str = "hello, world";
memcpy(addr, str, strlen(str));

(4) 完整代码

#include 
#include 
#include 
#include 
#include 
#include 

int main(){
	int fd = open("./log.txt", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		return -1;
	}

	int len = lseek(fd, 0, SEEK_END);
	void* addr = mmap(NULL, len, PROT_WRITE, MAP_SHARED, fd, 0);
	if(addr == MAP_FAILED)
	{
		perror("mmap");
		return -1;
	}

	const char* str = "hello, world";
	memcpy(addr, str, strlen(str));
	return 0;
}

2、mmap使用注意事项

(1) 映射区只要建立成功,文件可以立即关闭

在创建映射区的过程中,隐藏着一次对映射文件的读操作,目的是将部分文件内容读取到映射区。映射建立成功也就意味着读写完毕,此时只需要向内存映射中写入即可。

mmap 创建共享内存映射_第6张图片

(2) 映射的文件大小必须大于0,否则会报总线错误

如果映射文件的大小为0,那就说明我们无论怎么向映射里写入内容,都不会反馈到文件中,那就失去了映射的意义。

mmap 创建共享内存映射_第7张图片

(3) 文件偏移量必须为0或者4K的整数倍

(4) 映射空间大小可以大于文件大小,但要注意不要访问区外部分

映射空间的大小可以大于文件大小,只是有一部分空间无法映射到文件,向这部分空间写入的内容不会反馈给文件。

mmap 创建共享内存映射_第8张图片

(5) 设置的映射空间大小 ≠ 实际分配的映射空间大小

实际在分配内存的时候,是以页为单位进行分配的,每页大小是4K,实际大小会根据文件大小来定。并不是你设置了多少,内存就给你分配多少。

如果文件大小是2000字节,即便mmap第二个参数填入的是6000,映射空间的大小就是 4K,此时需要注意,超出4K部分属于区外部分,访问区外部分会报总线错误。

mmap 创建共享内存映射_第9张图片

如果文件大小是6000字节,大于了一页的大小,此时就会分配两页大小,映射空间大小就是8K。

mmap 创建共享内存映射_第10张图片

你可能感兴趣的:(Linux,基础,java,开发语言)