mmap(2) - Linux 手册页
名称
mmap, munmap - 将文件或设备映射到内存或取消映射概要
#include <sys/mman.h> void *mmap(void *addr, size_t lengthint " prot ", int " flags , int fd, off_t offset);int munmap(void *addr, size_t length);有关特性测试宏要求的相关信息,请参阅“注意”部分。
描述
如果 addr 为 NULL,则由内核选择创建映射的地址;这是创建新映射时最具可移植性的方法。如果 addr 不为 NULL,则内核将其视为关于映射放置位置的建议;在 Linux 上,映射将在附近的页面边界处创建。新映射的地址将作为调用的结果返回。
文件映射的内容(与匿名映射相对;参见下文的 MAP_ANONYMOUS)是通过文件描述符 fd 指向的文件(或其他对象)中从偏移量 offset 开始的 length 字节进行初始化的。offset 必须是 sysconf(_SC_PAGE_SIZE) 所返回的页面大小的倍数。
prot 参数描述了所需的内存映射保护属性(且不得与文件的打开模式冲突)。它可以是 PROT_NONE,也可以是以下一个或多个标志的按位或:
- PROT_EXEC
- 页面可以执行。
- PROT_READ
- 页面可以读取。
- PROT_WRITE
- 页面可以写入。
- PROT_NONE
- 页面不可访问。
flags 参数决定了映射的更新是否对映射同一区域的其他进程可见,以及更新是否会被传回到底层文件。此行为通过在 flags 中包含以下值之一来确定:
- MAP_SHARED
- 共享此映射。对映射的更新对映射同一文件的其他进程可见,并会同步到底层文件。在调用 msync(2) 或 munmap() 之前,文件可能不会立即被更新。
- MAP_PRIVATE
- 创建一个私有的写时复制(copy-on-write)映射。对映射的更新对映射同一文件的其他进程不可见,也不会被传回到底层文件。在 mmap() 调用之后对文件所做的更改是否在映射区域中可见,这是未定义的。
这两个标志在 POSIX.1-2001 中都有说明。
此外,可以将零个或多个以下值与 flags 进行或运算:
- MAP_32BIT(自 Linux 2.4.20, 2.6 起)
- 将映射置于进程地址空间的前 2GB 内。此标志仅在 x86-64 架构上支持 64 位程序。添加此标志是为了允许线程栈分配在内存的前 2GB 内,从而提高某些早期 64 位处理器上的上下文切换性能。现代 x86-64 处理器已不存在此性能问题,因此在这些系统上无需使用此标志。当设置了 MAP_FIXED 时,MAP_32BIT 标志将被忽略。
- MAP_ANON
- MAP_ANONYMOUS 的同义词。已弃用。
- MAP_ANONYMOUS
- 该映射不与任何文件关联;其内容初始化为零。fd 和 offset 参数被忽略;然而,某些实现要求如果指定了 MAP_ANONYMOUS(或 MAP_ANON),则 fd 必须为 -1,可移植应用程序应确保这一点。MAP_ANONYMOUS 与 MAP_SHARED 的结合使用仅在 Linux 内核 2.4 及更高版本中支持。
- MAP_DENYWRITE
- 此标志被忽略。(很久以前,它表示对底层文件的写入尝试应失败并返回 ETXTBUSY。但这曾是拒绝服务攻击的来源。)
- MAP_EXECUTABLE
- 此标志被忽略。
- MAP_FILE
- 兼容性标志。已忽略。
- MAP_FIXED
- 不要将 addr 视为建议:将映射精确放置在该地址。addr 必须是页面大小的倍数。如果由 addr 和 len 指定的内存区域与任何现有映射重叠,则现有映射的重叠部分将被丢弃。如果无法使用指定的地址,mmap() 将失败。由于要求映射的固定地址移植性较差,因此不鼓励使用此选项。
- MAP_GROWSDOWN
- 用于栈。向内核虚拟内存系统指示该映射应在内存中向下扩展。
- MAP_HUGETLB(自 Linux 2.6.32 起)
- 使用“大页面(huge pages)”分配映射。有关更多信息,请参阅 Linux 内核源文件 Documentation/vm/hugetlbpage.txt。
- MAP_LOCKED(自 Linux 2.5.37 起)
- 以 mlock(2) 的方式将映射区域的页面锁定在内存中。在较旧的内核中会忽略此标志。
- MAP_NONBLOCK(自 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(自 Linux 2.5.46 起)
- 为映射填充(预故障)页表。对于文件映射,这会引发文件的预读。稍后对映射的访问不会因缺页异常而阻塞。自 Linux 2.6.23 起,MAP_POPULATE 仅支持私有映射。
- MAP_STACK(自 Linux 2.6.27 起)
- 在适合进程或线程栈的地址处分配映射。此标志目前是一个空操作,但在 glibc 线程实现中使用,以便如果某些架构要求对栈分配进行特殊处理,则以后可以为 glibc 透明地实现支持。
- MAP_UNINITIALIZED(自 Linux 2.6.33 起)
- 不清除匿名页面。此标志旨在提高嵌入式设备上的性能。此标志仅在内核配置了 CONFIG_MMAP_ALLOW_UNINITIALIZED 选项时才有效。由于安全隐患,该选项通常仅在嵌入式设备(即用户对其内存内容拥有完全控制权的设备)上启用。
在上述标志中,只有 MAP_FIXED 在 POSIX.1-2001 中指定。然而,大多数系统也支持 MAP_ANONYMOUS(或其同义词 MAP_ANON)。
某些系统记录了额外的标志 MAP_AUTOGROW、MAP_AUTORESRV、MAP_COPY 和 MAP_LOCAL。
由 mmap() 映射的内存会在 fork(2) 后保留,并具有相同的属性。
文件是以页面大小的倍数进行映射的。对于非页面大小倍数的文件,剩余内存会在映射时被置零,并且对该区域的写入不会写入文件。更改映射底层文件的大小对映射文件中相应区域的页面产生的影响是未定义的。
munmap()
munmap() 系统调用删除指定地址范围的映射,并导致对该范围内地址的后续引用生成无效的内存引用。该区域在进程终止时也会自动取消映射。另一方面,关闭文件描述符不会取消映射该区域。地址 addr 必须是页面大小的倍数。包含指定范围一部分的所有页面都将被取消映射,后续对这些页面的引用将生成 SIGSEGV。如果指定范围不包含任何已映射的页面,则不是错误。
文件映射的时间戳更改
对于文件映射,映射文件的 st_atime 字段可以在 mmap() 和相应取消映射之间的任何时间更新;如果尚未更新,则首次引用映射页面时会更新该字段。对于使用 PROT_WRITE 和 MAP_SHARED 映射的文件,其 st_ctime 和 st_mtime 字段将在写入映射区域后、以及(如果存在的话)后续调用带有 MS_SYNC 或 MS_ASYNC 标志的 msync(2) 之前进行更新。
返回值
成功时,mmap() 返回指向映射区域的指针。失败时,返回 MAP_FAILED(即 (void *) -1),并适当地设置 errno。成功时,munmap() 返回 0,失败时返回 -1,并设置 errno(通常为 EINVAL)。错误
- EACCES
- 文件描述符引用的是非普通文件。或者请求了 MAP_PRIVATE,但 fd 未以读取模式打开。或者请求了 MAP_SHARED 且设置了 PROT_WRITE,但 fd 未以读/写 (O_RDWR) 模式打开。或者设置了 PROT_WRITE,但文件为仅追加模式。
- EAGAIN
- 文件已被锁定,或者锁定的内存过多(参见 setrlimit(2))。
- EBADF
- fd 不是有效的文件描述符(且未设置 MAP_ANONYMOUS)。
- EINVAL
- 不接受 addr、length 或 offset(例如,它们太大,或者未在页面边界对齐)。
- EINVAL
- (自 Linux 2.6.12 起)length 为 0。
- EINVAL
- flags 未包含 MAP_PRIVATE 或 MAP_SHARED,或者同时包含了这两个值。
- ENFILE
- 已达到系统打开文件总数的限制。
- ENODEV
- 指定文件的底层文件系统不支持内存映射。
- ENOMEM
- 没有可用内存,或者超过了进程的最大映射数量。
- EPERM
- prot 参数请求 PROT_EXEC,但映射区域所属的文件系统挂载为 no-exec。
- ETXTBSY
- 设置了 MAP_DENYWRITE,但 fd 指定的对象是以写入模式打开的。
- EOVERFLOW
- 在 32 位架构上使用大文件扩展(即使用 64 位 off_t):用于 length 的页面数加上用于 offset 的页面数会导致 unsigned long(32 位)溢出。
使用映射区域可能会导致以下信号
- SIGSEGV
- 尝试写入映射为只读的区域。
- SIGBUS
- 尝试访问缓冲区中与文件不对应的部分(例如,超出文件末尾,包括其他进程截断文件的情况)。
符合
SVr4, 4.4BSD, POSIX.1-2001。可用性
在支持 mmap()、msync(2) 和 munmap() 的 POSIX 系统上,<unistd.h> 中定义的 _POSIX_MAPPED_FILES 值大于 0。(另请参阅 sysconf(3)。)说明
本页描述由 glibc mmap() 包装函数提供的接口。最初,此函数调用同名的系统调用。自内核 2.4 起,该系统调用已被 mmap2(2) 取代,如今 glibc 的 mmap() 包装函数会以适当调整过的 offset 值调用 mmap2(2)。在某些硬件架构(例如 i386)上,PROT_WRITE 隐含 PROT_READ。PROT_READ 是否隐含 PROT_EXEC 是架构相关的。如果程序打算在新映射中执行代码,应始终设置 PROT_EXEC。
创建映射的可移植方法是将 addr 指定为 0 (NULL),并从 flags 中省略 MAP_FIXED。在这种情况下,系统为映射选择地址;所选地址不会与任何现有映射冲突,也不会为 0。如果指定了 MAP_FIXED 标志且 addr 为 0 (NULL),则映射地址将为 0 (NULL)。
某些 flags 常量仅在定义了 _BSD_SOURCE 或 _SVID_SOURCE 时定义。(要求 _GNU_SOURCE 也足够,且明确要求该宏在逻辑上更合理,因为这些标志都是 Linux 特有的。)相关标志为:MAP_32BIT、MAP_ANONYMOUS(及其同义词 MAP_ANON)、MAP_DENYWRITE、MAP_EXECUTABLE、MAP_FILE、MAP_GROWSDOWN、MAP_HUGETLB、MAP_LOCKED、MAP_NONBLOCK、MAP_NORESERVE、MAP_POPULATE 和 MAP_STACK。
错误
在 Linux 上,不存在 MAP_NORESERVE 下暗示的那种保证。默认情况下,任何进程都可能在系统内存不足时随时被杀死。在 2.6.7 之前的内核中,MAP_POPULATE 标志仅在 prot 指定为 PROT_NONE 时有效。
SUSv3 指定如果 length 为 0,mmap() 应失败。然而,在 2.6.12 之前的内核中,mmap() 在这种情况下成功:不创建任何映射且调用返回 addr。自内核 2.6.12 起,mmap() 在此情况下会因 EINVAL 错误而失败。
POSIX 规定,系统应始终将对象末尾的部分页面填零,且系统永远不会向对象末尾之后写入任何修改。在 Linux 上,当您向对象末尾之后的此类部分页面写入数据时,即使在文件关闭和取消映射后,数据仍会保留在页面缓存中,并且即使数据从未写入文件本身,后续的映射也可能会看到修改后的内容。在某些情况下,可以通过在取消映射之前调用 msync(2) 来解决此问题;然而,这在 tmpfs 上不起作用(例如,在使用 shm_overview(7) 中记录的 POSIX 共享内存接口时)。
示例
以下程序将第一个命令行参数指定文件的部分内容打印到标准输出。要打印的字节范围通过第二个和第三个命令行参数中的偏移量(offset)和长度(length)值指定。该程序创建文件所需页面的内存映射,然后使用 write(2) 输出所需的字节。
#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 & ~(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); } exit(EXIT_SUCCESS); }
参见
getpagesize(2), mincore(2), mlock(2), mmap2(2), mprotect(2), mremap(2), msync(2), remap_file_pages(2), setrlimit(2), shmat(2), shm_open(3), shm_overview(7)B.O. Gallmeister, POSIX.4, O'Reilly, 第 128-129 页和 389-391 页。