MMAP - Linux手册页
Linux程序员手册 第2部分
更新日期: 2020-08-13
名称
mmap,munmap-将文件或设备映射或取消映射到内存中
语法
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length);
有关功能测试宏要求的信息,请参见注释。
说明
mmap()在调用进程的虚拟地址空间中创建一个新的映射。新映射的起始地址在addr中指定。 length参数指定映射的长度(必须大于0)。
如果addr为NULL,则内核选择创建映射的(页面对齐)地址;这是创建新映射的最便捷的方法。如果addr不为NULL,则内核将其用作放置映射的提示。在Linux上,内核将选择附近的页面边界(但始终大于/等于/ proc / sys / vm / mmap_min_addr指定的值),并尝试在此处创建映射。如果那里已经存在另一个映射,则内核会选择一个可能取决于或可能不取决于提示的新地址。调用的结果将返回新映射的地址。
使用从文件描述符fd引用的文件(或其他对象)中的offset offset开始的长度字节,初始化文件映射的内容(与匿名映射相反;请参阅下面的MAP_ANONYMOUS)。 offset必须是sysconf(_SC_PAGE_SIZE)返回的页面大小的倍数。
返回mmap()调用后,可以立即关闭文件描述符fd,而不会使映射无效。
prot参数描述了所需的映射内存保护(并且不得与文件的打开模式冲突)。它是PROT_NONE或以下一个或多个标志的按位或:
- PROT_EXEC
- 页面可以被执行。
- PROT_READ
- 页面可能会被读取。
- PROT_WRITE
- 页面可能被写入。
- PROT_NONE
- 页面可能无法访问。
The flags argument
flags参数确定对映射的更新对于映射同一区域的其他进程是否可见,以及是否将更新进行到基础文件。通过在标志中完全包含以下值之一来确定此行为:
- MAP_SHARED
- 分享此映射。映射的更新对于映射同一区域的其他进程是可见的,并且(在文件支持的映射的情况下)会一直传递到基础文件。 (要精确控制何时将更新传递到基础文件,需要使用msync(2)。)
- MAP_SHARED_VALIDATE(since Linux 4.15)
- 该标志提供与MAP_SHARED相同的行为,除了MAP_SHARED映射会忽略标志中的未知标志。相比之下,当使用MAP_SHARED_VALIDATE创建映射时,内核会验证所有传递的标志都是已知的,并且会针对未知标志使映射失败,并显示错误EOPNOTSUPP。还需要此映射类型才能使用某些映射标志(例如MAP_SYNC)。
- MAP_PRIVATE
- 创建一个私有的写时复制映射。映射的更新对于映射同一文件的其他进程不可见,也不会传递到基础文件。尚不确定在映射区域中是否可以看到mmap()调用后对文件所做的更改。
POSIX.1-2001和POSIX.1-2008中都描述了MAP_SHARED和MAP_PRIVATE。 MAP_SHARED_VALIDATE是Linux扩展。
此外,可以在标志中对以下值中的零个或多个进行"或"运算:
- MAP_32BIT(since Linux 2.4.20, 2.6)
- 将映射放入进程地址空间的前2 GB。对于64位程序,仅x86-64支持此标志。添加它是为了允许在前2 GB内存中的某个位置分配线程堆栈,以提高某些早期64位处理器上的上下文切换性能。现代x86-64处理器不再具有此性能问题,因此在那些系统上不需要使用此标志。设置MAP_FIXED时,将忽略MAP_32BIT标志。
- MAP_ANON
- MAP_ANONYMOUS的同义词;提供与其他实现的兼容性。
- MAP_ANONYMOUS
- 映射没有任何文件支持;它的内容初始化为零。 fd参数被忽略;但是,如果指定了MAP_ANONYMOUS(或MAP_ANON),则某些实现要求fd为-1,并且便携式应用程序应确保做到这一点。 offset参数应为零。仅从内核2.4开始,才支持将MAP_ANONYMOUS与MAP_SHARED结合使用。
- MAP_DENYWRITE
- 该标志被忽略。 (很早以前-Linux 2.0和更早版本--表示尝试写入底层文件应该会因ETXTBSY而失败。但这是拒绝服务攻击的源头。)
- MAP_EXECUTABLE
- 该标志被忽略。
- MAP_FILE
- 兼容性标志。忽略了。
- MAP_FIXED
- 不要将addr解释为提示:将映射恰好放在该地址处。 addr必须适当对齐:对于大多数体系结构,页面大小的倍数就足够了;但是,某些体系结构可能会施加其他限制。如果由addr和len指定的内存区域与任何现有映射的页面重叠,则现有映射的重叠部分将被丢弃。如果无法使用指定的地址,则mmap()将失败。
- 渴望可移植的软件应谨慎使用MAP_FIXED标志,请记住,允许进程的内存映射的确切布局在内核版本,C库版本和操作系统版本之间发生重大变化。仔细阅读NOTES中有关此标志的讨论!
- MAP_FIXED_NOREPLACE(since Linux 4.17)
- 此标志提供的行为类似于关于addr实施的MAP_FIXED,但不同之处在于MAP_FIXED_NOREPLACE永远不会破坏先前存在的映射范围。如果请求的范围将与现有映射冲突,则此调用将失败,并显示错误EEXIST。因此,此标志可用作原子(相对于其他线程)尝试映射地址范围的方式:一个线程将成功;一个线程将成功。所有其他人将报告失败。
- 请注意,无法识别MAP_FIXED_NOREPLACE标志的较旧内核通常会(在检测到与现有映射的冲突时)回落为" non-MAP_FIXED"类型的行为:它们将返回与所请求地址不同的地址。因此,向后兼容的软件应对照请求的地址检查返回的地址。
- MAP_GROWSDOWN
- 该标志用于堆栈。它向内核虚拟内存系统指示该映射应在内存中向下扩展。返回地址比在进程的虚拟地址空间中实际创建的内存区域低一页。触摸映射下方的"防护"页面中的地址将导致映射按页面增长。可以重复这种增长,直到该映射增长到下一个较低映射的高端的页面之内,此时触摸"防护"页面将产生SIGSEGV信号。
- MAP_HUGETLB(since Linux 2.6.32)
- 使用"大页面"分配映射。请参阅Linux内核源文件Documentation / admin-guide / mm / hugetlbpage.rst,以获取更多信息以及下面的NOTES。
- MAP_HUGE_2MB, MAP_HUGE_1GB(since Linux 3.8)
- 与MAP_HUGETLB结合使用,以在支持多个千兆字节页面大小的系统上选择其他的千兆字节页面大小(分别为2 MB和1 GB)。
- 更一般地,可以通过在偏移MAP_HUGE_SHIFT的六位中编码所需页面大小的以2为底的对数来配置所需的巨大页面大小。 (此位字段中的值为零提供默认的大页面大小;可以通过/ proc / meminfo公开的Hugepagesize字段发现默认的大页面大小。)因此,以上两个常量定义为:
#define MAP_HUGE_2MB (21 << MAP_HUGE_SHIFT) #define MAP_HUGE_1GB (30 << MAP_HUGE_SHIFT)
- 通过在/ sys / kernel / mm / hugepages中列出子目录,可以发现系统支持的大页面大小范围。
- MAP_LOCKED(since Linux 2.5.37)
- 将映射的区域标记为与mlock(2)相同的方式被锁定。此实现将尝试填充(预故障)整个范围,但如果失败,则mmap()调用不会因ENOMEM而失败。因此,以后可能会发生重大故障。因此,语义不如mlock(2)强。如果在初始化映射后无法接受主要故障时,应使用mmap()加mlock(2)。在较早的内核中,MAP_LOCKED标志被忽略。
- MAP_NONBLOCK(since Linux 2.5.46)
- 该标志仅与MAP_POPULATE结合使用才有意义。不要执行预读:仅为RAM中已经存在的页面创建页表条目。从Linux 2.6.23开始,此标志使MAP_POPULATE不执行任何操作。有一天,可以重新实现MAP_POPULATE和MAP_NONBLOCK的组合。
- MAP_NORESERVE
- 不要为该映射保留交换空间。当保留交换空间时,可以保证可以修改映射。如果没有保留交换空间,则在没有可用物理内存的情况下,可能会在写入时得到SIGSEGV。另请参见proc(5)中有关文件/ proc / sys / vm / overcommit_memory的讨论。在2.6之前的内核中,此标志仅对私有可写映射有效。
- MAP_POPULATE(since Linux 2.5.46)
- 填充(故障前)页表以进行映射。对于文件映射,这将导致文件上的预读。这将有助于减少以后对页面错误的阻止。仅从Linux 2.6.23开始,专用映射才支持MAP_POPULATE。
- MAP_STACK(since Linux 2.6.27)
- 在适合进程或线程堆栈的地址处分配映射。
- 该标志目前在Linux上是禁止操作的。但是,通过使用此标志,如果将来实现该标志,应用程序可以确保它们透明地获得支持。因此,它在glibc线程实现中使用,以允许某些体系结构可能(以后)需要对堆栈分配进行特殊处理的事实。采用此标志的另一个原因是可移植性:MAP_STACK在某些其他系统(例如某些BSD)上存在(并有影响)。
- MAP_SYNC(since Linux 4.15)
- 该标志仅在MAP_SHARED_VALIDATE映射类型下可用。 MAP_SHARED类型的映射将静默忽略此标志。仅支持DAX(永久内存的直接映射)的文件才支持此标志。对于其他文件,使用此标志创建映射会导致EOPNOTSUPP错误。
- 带有此标志的共享文件映射可确保在进程的地址空间中可写地映射某些内存时,即使在系统崩溃或重新启动后,该内存也将在同一文件中以相同的偏移量显示。结合使用适当的CPU指令,这为此类映射的用户提供了使数据修改持久化的更有效方法。
- MAP_UNINITIALIZED(since Linux 2.6.33)
- 不要清除匿名页面。该标志旨在提高嵌入式设备的性能。仅当内核使用CONFIG_MMAP_ALLOW_UNINITIALIZED选项配置时,才使用此标志。由于涉及安全性,通常仅在嵌入式设备(即完全控制用户内存内容的设备)上启用该选项。
在以上标志中,在POSIX.1-2001和POSIX.1-2008中仅指定MAP_FIXED。但是,大多数系统还支持MAP_ANONYMOUS(或其同义词MAP_ANON)。
munmap()
munmap()系统调用删除指定地址范围的映射,并导致对该范围内地址的进一步引用以生成无效的内存引用。进程终止时,该区域也会自动取消映射。另一方面,关闭文件描述符不会取消映射区域。
地址地址必须是页面大小的倍数(但长度不必是)。包含指示范围的一部分的所有页面均未映射,随后对这些页面的引用将生成SIGSEGV。如果指示的范围不包含任何映射的页面,则不是错误。
返回值
成功时,mmap()返回一个指向映射区域的指针。发生错误时,将返回值MAP_FAILED(即(void *)-1),并且将errno设置为指示错误原因。
如果成功,则munmap()返回0。如果失败,则返回-1,并且将errno设置为指示错误的原因(可能为EINVAL)。
错误说明
- EACCES
- 文件描述符是指非常规文件。或请求了文件映射,但未打开fd进行读取。或请求了MAP_SHARED并设置了PROT_WRITE,但在读/写(O_RDWR)模式下未打开fd。或设置了PROT_WRITE,但该文件是仅追加的。
- EAGAIN
- 文件已被锁定,或者太多的内存被锁定(请参阅setrlimit(2))。
- EBADF
- fd不是有效的文件描述符(并且未设置MAP_ANONYMOUS)。
- EEXIST
- MAP_FIXED_NOREPLACE是在标志中指定的,并且addr和length覆盖的范围与现有映射发生冲突。
- EINVAL
- 我们不喜欢addr,length或offset(例如,它们太大或在页面边界上未对齐)。
- EINVAL
- (从Linux 2.6.12开始)长度为0。
- EINVAL
- 标志不包含MAP_PRIVATE,MAP_SHARED或MAP_SHARED_VALIDATE。
- ENFILE
- 已达到系统范围内打开文件总数的限制。
- ENODEV
- 指定文件的基础文件系统不支持内存映射。
- ENOMEM
- 没有可用的内存。
- ENOMEM
- 将超过该进程的最大映射数。当在现有映射的中间取消映射区域时,munmap()也可能发生此错误,因为这会导致在该区域的任一侧取消两个较小的映射。
- ENOMEM
- (从Linux 4.7开始)超过了getrlimit(2)中描述的进程的RLIMIT_DATA限制。
- EOVERFLOW
- 在具有大文件扩展名的32位体系结构上(即使用64位off_t):用于长度的页面数加上用于偏移量的页面数将溢出无符号长整数(32位)。
- EPERM
- prot参数要求输入PROT_EXEC,但映射区域属于挂载为no-exec的文件系统上的文件。
- EPERM
- 该操作被文件封条阻止;参见fcntl(2)。
- ETXTBSY
- 设置了MAP_DENYWRITE,但打开了由fd指定的对象以进行写入。
使用映射区域可能会导致以下信号:
- SIGSEGV
- 尝试写入映射为只读的区域。
- SIGBUS
- 尝试访问与文件不对应的缓冲区部分(例如,超出文件末尾,包括另一个进程已截断文件的情况)。
属性
有关本节中使用的术语的说明,请参见attribute(7)。
Interface | Attribute | Value |
mmap(),munmap() | Thread safety | MT-Safe |
遵循规范
POSIX.1-2001,POSIX.1-2008,SVr4、4.4BSD。
在可使用mmap(),msync(2)和munmap()的POSIX系统上,将_POSIX_MAPPED_FILES定义为大于0的值。(另请参见sysconf(3)。)
备注
mmap()映射的内存在fork(2)中保留,并具有相同的属性。
文件被映射为页面大小的倍数。对于不是页面大小倍数的文件,映射时剩余内存将清零,并且对该区域的写入不会写出到该文件中。未指定在与文件的已添加或已删除区域相对应的页面上更改映射基础文件大小的影响。
在某些硬件体系结构(例如i386)上,PROT_WRITE表示PROT_READ。 PROT_READ是否隐含PROT_EXEC与体系结构有关。如果可移植程序打算在新映射中执行代码,则应始终设置PROT_EXEC。
创建映射的可移植方法是将addr指定为0(NULL),并从标志中省略MAP_FIXED。在这种情况下,系统将选择映射的地址。选择该地址是为了不与任何现有映射冲突,并且该地址将不会为0。如果指定了MAP_FIXED标志,并且addr为0(NULL),则映射的地址将为0(NULL)。
仅当定义了适当的功能测试宏时(可能默认情况下),才定义某些标志常量:_DEFAULT_SOURCE,带有glibc 2.19或更高版本;或glibc 2.19和更低版本中的_BSD_SOURCE或_SVID_SOURCE。 (使用_GNU_SOURCE也就足够了,并且因为这些标志都是特定于Linux的,因此特别需要该宏才更具逻辑性。)相关标志是:MAP_32BIT,MAP_ANONYMOUS(和同义词MAP_ANON),MAP_DENYWRITE,MAP_EXECUTABLE,MAP_SDOWN,MAP_FILE MAP_HUGETLB,MAP_LOCKED,MAP_NONBLOCK,MAP_NORESERVE,MAP_POPULATE和MAP_STACK。
应用程序可以使用mincore(2)确定当前映射的哪些页面驻留在缓冲区/页面缓存中。
Using MAP_FIXED safely
MAP_FIXED的唯一安全用法是先前使用另一个映射保留由addr和length指定的地址范围的位置;否则,使用MAP_FIXED是危险的,因为它会强制删除预先存在的映射,从而使多线程进程很容易破坏其自己的地址空间。
例如,假设线程A浏览/ proc // maps以便找到可以使用MAP_FIXED映射的未使用的地址范围,而线程B同时获取该地址范围的部分或全部。当线程A随后使用mmap(MAP_FIXED)时,它将有效地破坏线程B创建的映射。在这种情况下,线程B无需直接创建映射。只需在内部使用dlopen(3)加载其他共享库的库调用就足够了。 dlopen(3)调用会将库映射到进程的地址空间。此外,几乎任何库调用都可以通过使用此技术或通过简单分配内存的方式,将内存映射添加到地址空间中来实现。示例包括brk(2),malloc(3),pthread_create(3)和PAM库
从Linux 4.17开始,多线程程序可以使用MAP_FIXED_NOREPLACE标志来避免在尝试在尚未由现有映射保留的固定地址上创建映射时遇到的上述危险。
Timestamps changes for file-backed mappings
对于文件支持的映射,可以在mmap()和相应的取消映射之间的任何时间更新映射文件的st_atime字段;如果尚未映射字段,则对映射页面的第一次引用将更新该字段。
如果对PROT_WRITE和MAP_SHARED映射的文件的st_ctime和st_mtime字段在写入映射的区域之后且随后的带有MS_SYNC或MS_ASYNC标志的msync(2)之前发生更新,则将更新该字段。
Huge page (Huge TLB) mappings
对于使用大页面的映射,对mmap()和munmap()的参数要求与使用本机系统页面大小的映射要求有所不同。
对于mmap(),offset必须是基础巨大页面大小的倍数。系统自动将长度调整为基础巨大页面大小的倍数。
对于munmap(),addr和length必须均为基础巨大页面大小的倍数。
C library/kernel differences
本页描述了glibc mmap()包装函数提供的接口。最初,此函数调用了相同名称的系统调用。从内核2.4开始,该系统调用已被mmap2(2)取代,如今,glibc mmap()包装器函数以适当的偏移量调整值调用mmap2(2)。
BUGS
在Linux上,没有像上面在MAP_NORESERVE下建议的那样保证。默认情况下,当系统内存不足时,可以随时终止任何进程。
在2.6.7之前的内核中,只有将prot指定为PROT_NONE时,MAP_POPULATE标志才有效。
SUSv3指定如果length为0,则mmap()应该失败。但是,在2.6.12之前的内核中,mmap()在这种情况下成功:未创建任何映射,并且调用返回了addr。从内核2.6.12开始,对于这种情况,mmap()失败,并显示错误EINVAL。
POSIX指定系统应始终在对象末尾零填充任何部分页面,并且系统永远不会在对象末尾写入任何修改。在Linux上,当您在对象结束之后将数据写入该部分页面时,即使文件已关闭并取消映射,数据也仍保留在页面缓存中,即使数据从未写入文件本身,后续映射也可能会看到修改后的内容。在某些情况下,可以通过在取消映射发生之前调用msync(2)来解决此问题。但是,这不适用于tmpfs(5)(例如,当使用shm_overview(7)中记录的POSIX共享内存接口时)。
示例
以下程序将在其第一个命令行参数中指定的文件部分打印到标准输出。通过第二个和第三个命令行参数中的offset和length值指定要打印的字节范围。该程序创建文件所需页的内存映射,然后使用write(2)输出所需的字节。
Program source
#include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) int main(int argc, char *argv[]) { char *addr; int fd; struct stat sb; off_t offset, pa_offset; size_t length; ssize_t s; if (argc < 3 || argc > 4) { fprintf(stderr, "%s file offset [length]\n", argv[0]); exit(EXIT_FAILURE); } fd = open(argv[1], O_RDONLY); if (fd == -1) handle_error("open"); if (fstat(fd, &sb) == -1) /* To obtain file size */ handle_error("fstat"); offset = atoi(argv[2]); pa_offset = offset & ti(sysconf(_SC_PAGE_SIZE) - 1); /* offset for mmap() must be page aligned */ if (offset >= sb.st_size) { fprintf(stderr, "offset is past end of file\n"); exit(EXIT_FAILURE); } if (argc == 4) { length = atoi(argv[3]); if (offset + length > sb.st_size) length = sb.st_size - offset; /* Canaqt display bytes past end of file */ } else { /* No length arg ==> display to end of file */ length = sb.st_size - offset; } addr = mmap(NULL, length + offset - pa_offset, PROT_READ, MAP_PRIVATE, fd, pa_offset); if (addr == MAP_FAILED) handle_error("mmap"); s = write(STDOUT_FILENO, addr + offset - pa_offset, length); if (s != length) { if (s == -1) handle_error("write"); fprintf(stderr, "partial write"); exit(EXIT_FAILURE); } munmap(addr, length + offset - pa_offset); close(fd); exit(EXIT_SUCCESS); }
另外参见
ftruncate(2),getpagesize(2),memfd_create(2),mincore(2),mlock(2),mmap2(2),mprotect(2),mremap(2),msync(2),remap_file_pages(2), setrlimit(2),shmat(2),userfaultfd(2),shm_open(3),shm_overview(7)
proc(5)中以下文件的描述:/ proc / [pid] / maps,/ proc / [pid] / map_files和/ proc / [pid] / smaps。
B.O. Gallmeister,POSIX.4,O'Reilly,第128-129和389-391页。
出版信息
这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/。