系统调用(mmap和munmap)
mmap
内存映射类型
mmap()
系统调用用于在调用进程的虚拟地址空间中 创建内存映射,主要分为两种类型:
-
文件映射:将文件的一部分直接映射到虚拟内存中,允许通过内存访问文件内容,映射的分页会在需要时自动加载
-
匿名映射:没有对应文件,分页初始化为0,可以视为一个内容总是为0的虚拟文件映射
映射内存可以被多个进程共享,具体情况包括:
- 共享映射(
MAP_SHARED
):修改内容对所有共享进程可见,直接影响底层文件 - 私有映射(
MAP_PRIVATE
):修改内容对其他进程不可见,使用 写时复制 技术确保每个进程的修改独立
映射类型总结
映射类型 | 变更可见性 | 主要用途 |
---|---|---|
私有文件映射 | 不可见 | 初始化内存区域,如文本和数据段 |
私有匿名映射 | 不可见 | 分配零填充内存 |
共享文件映射 | 可见 | 内存映射 I/O,进程间共享内存 (IPC) |
共享匿名映射 | 可见 | 进程间共享内存 (IPC),仅限相关进程 |
其他说明
-
通过
fork()
创建的子进程会继承映射,但在执行exec()
时映射会丢失 -
每次调用
mmap()
创建的新映射是独立的,尤其是在匿名映射的情况下
函数原型
#include <sys/mman.h>
void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
-
参数说明:
- addr:期望的映射起始地址,通常设置为 NULL 让系统自动选择地址
- length:要映射的字节数,通常是文件的大小
- prot:映射区域的保护标志,指定访问权限,可以是以下的按位或组合:
- PROT_READ:可读
- PROT_WRITE:可写
- PROT_EXEC:可执行
- PROT_NONE:不可访问
- flags:映射的选项,例如:
- MAP_SHARED:共享映射,多个进程可以访问相同的映射
- MAP_PRIVATE:私有映射,写入时复制,写入不会影响原文件
- MAP_ANONYMOUS:不与任何文件关联的匿名映射
- fd:要映射的文件描述符,使用
open
系统调用获取 - offset:文件映射的起始偏移量,必须是页面大小的倍数
-
返回值:
- 成功时返回映射区域的指针,失败时返回 MAP_FAILED,并设置 errno
munmap
munmap()
系统调用用于从进程的虚拟地址空间中 删除一个映射
函数原型
#include <sys/mman.h>
int munmap(void *addr, size_t length);
-
参数说明:
- addr:待解除映射的起始地址,必须与分页边界对齐
- length:指定解除映射区域的大小,必须为非负整数,通常应为系统分页大小的倍数
-
返回值:
- 如果指定范围内不存在映射,
munmap()
将无效并返回 0(表示成功)
- 如果指定范围内不存在映射,
补充说明
-
解除映射:
- 通常解除整个映射,可以将
addr
设置为mmap()
返回的地址,并使length
与mmap()
使用的值相同 - 也可以部分解除映射,可能导致映射收缩或分割
- 通常解除整个映射,可以将
-
内存锁:
- 解除映射时,内核会删除在指定范围内的所有内存锁(由
mlock()
或mlockall()
创建)
- 解除映射时,内核会删除在指定范围内的所有内存锁(由
-
自动解除:
- 进程终止或执行
exec()
时,所有映射会自动解除
- 进程终止或执行
-
注意事项:
- 在解除共享文件映射之前,应先调用
msync()
确保内容写入底层文件
- 在解除共享文件映射之前,应先调用
文件映射
这张图表示由参数offset
和length
决定哪些文件区域被映射到虚拟内存中
共享/私有文件映射
多个进程共享同一区域的内存映射,共享文件映射 所有的修改都是可见的,同时也会反映到底层文件,私有文件映射 的修改仅调用进程自己可见,并且不会反应到底层文件,这是使用 写时复制 的技术实现,即,当要对该内存映射做修改时,内核会复制一份相同的给进程,从而使其真正的独立出来
内存映射I/O
内存映射 I/O 是一种将文件的内容映射到进程的虚拟内存地址空间的技术,使得程序可以通过 直接访问内存来执行文件 I/O 操作,而无需使用传统的 read()
和 write()
系统调用
关键特点
-
共享文件映射:映射的内存内容源自文件,并且对映射内容的任何更改都会自动反映到文件中。这意味着可以通过简单的内存访问操作来进行文件 I/O
-
结构化数据类型:通常,程序会定义一个结构化数据类型,与磁盘文件的内容对应,以便于访问和处理映射的内存内容
举个例子,假设磁盘文件中存储的是员工信息,那我可以先定义一个员工结构体
struct Employee { int id; // 员工ID char name[50]; // 员工姓名 float salary; // 员工薪资 };
一旦文件被映射到内存,程序可以直接通过访问结构体的字段来读取和修改员工信息,而不需要手动处理字节的偏移和数据格式
struct Employee *employees = mmap(NULL, fileSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); printf("Employee ID: %d\n", employees[0].id); // 直接访问第一个员工的ID
优势
-
简化应用逻辑:使用内存访问替代传统的
read()
和write()
调用,可以使一些应用程序的逻辑更为简洁和直观。 -
提高性能:
- 减少数据传输:传统的
read()
和write()
操作需要两次数据传输:一次在文件和内核缓冲区之间,另一次在内核缓冲区和用户空间之间。而使用mmap()
可以省去第二次传输,用户进程可以直接访问内存中的数据。 - 共享内存:当使用
mmap()
时,内核空间和用户空间共享同一个缓冲区,避免了在用户空间和内核空间之间复制数据的开销。如果多个进程在同一文件上进行 I/O,它们可以共享同一内核缓冲区,从而节省内存使用。
- 减少数据传输:传统的
边界情况
-
文件内容更大,超过映射区域
-
文件内容小于映射区域
同步映射区域:msync()
msync()
系统调用用于 显式控制共享内存映射与底层文件之间的同步。虽然内核会自动将 MAP_SHARED
映射内容的更改写入文件,但默认情况下并 不保证同步的时间
作用
- 数据完整性:在数据库等应用中,调用
msync()
可以强制将数据写入磁盘,以确保数据完整性 - 可见性:确保在可写映射上进行的更新对执行
read()
的其他进程可见
函数原型
#include <sys/mman.h>
int msync(void *addr, size_t length, int flags);
-
参数
- addr:需要同步的内存区域的起始地址,必须分页对齐
- length:同步区域的大小,向上舍入到系统分页大小的下一个整数倍
- flags:指定同步行为,可以是以下值:
- MS_SYNC:执行同步写入,调用会阻塞直到所有修改的数据页写入磁盘
- MS_ASYNC:执行异步写入,修改的数据页将在将来的某个时间写入磁盘,立即对其他进程可见
- MS_INVALIDATE:使映射数据的缓存副本失效,确保下一次访问时从文件读取更新的内容
匿名映射
匿名映射
匿名映射是一种没有对应文件的内存映射。可以通过以下两种方式在Linux中创建匿名映射:
-
使用
MAP_ANONYMOUS
:- 在
mmap()
的flags
中指定MAP_ANONYMOUS
,同时将fd
设置为 -1。这个值会被忽略,但为可移植性,建议遵循这个约定 - 需要在代码中定义
_BSD_SOURCE
或_SVID_SOURCE
来使用MAP_ANONYMOUS
- 在
-
使用
/dev/zero
:- 打开
/dev/zero
设备文件并将其文件描述符传递给mmap()
。该设备始终返回0,写入的数据会被丢弃
- 打开
无论使用哪种方法,得到的映射都会被初始化为0,且 offset
参数会被忽略
匿名映射类型
-
MAP_PRIVATE 匿名映射:
- 用于分配进程私有的内存块,并将其初始化为0。
glibc
的malloc()
函数在分配大于MMAP_THRESHOLD
(默认128 KB,可调整)的内存时使用此映射,以提高内存管理效率并减少内存碎片
-
MAP_SHARED 匿名映射:
- 允许相关进程(如父子进程)共享一块内存区域而无需对应的映射文件
- 如果在创建共享映射后调用
fork()
,子进程会继承该映射,从而实现进程间的内存共享
代码示例
#ifdef USE_MAP_ANON
#define _BSD_SOURCE // 获取 USE_MAP_ANON 定义
#endif
int *addr; // 假设为int
#ifdef USE_MAP_ANON
addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
#else
int fd = open("/dev/zero", O_RDWR);
addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
#endif
标签:MAP,映射,文件,mmap,内存,进程
From: https://www.cnblogs.com/dylaris/p/18491457