mmap,munmap-将文件或设备映射(消取映射)到内存
#include
#include
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);
-
mmap()
mmap()将在调用者进程的虚拟地址空间上创建一个映射。映射的开始地址由addr指定,长度由length(必须大于 0)指定。
若addr为NULL,内核将确定一个page-aligned(页对齐)的起始地址来创建映射,这是常用方式;若addr为不空,内核将其视为选择开始地址的参照,内核将在此addr附近page边界视为起始(add不能小于proc/sys/vm/mmap_min_addr的值)。若此addr已经存在映射,内核会选择一个可能取决于也可能不取决于addr的新地址。新映射的地址作为调用结果返回。
对于文件映射(区别于匿名映射)而言,其内容对应于由fd指向的文件中以offset偏移为起始内容。offset的大小必须是sysconf(_SC_PAGE_SIZE)
所返回的page大小的倍数。
mmap返回后,fd可以马上关闭,而不影响映射。
prot参数描述了对映射的保护,可以是以下标志的按位或:
- PROT-EXEC
Pages may be executed. - PROT-READ
Pages may be read. - PROT-WRITE
Pages may be written. - PROT_NONE
Pages may not be accessed.
flags参数
用于描述对映射更新对于映射同一区域的其他进程(原文用的是process)是否可见(现实上应该是考虑Pagecache),以及是否将更新进行到基础文件。由此必包含下面三个flags之一:
MAP_SHARED
共享映射, 映射的更新对映射同一区域的其他进程是可见的,并且(文件映射)被传递到底层文件.(要精确控制何时将更新传递到底层文件需要使用 msync(2).)
在实现上,共享映射的物理内存直接对应于文件的pagecahe,因此当一个进程修改映射区时,即是修改了pagecahe,那么其他映射了文件相同区域的就会感知到变化,因为他们本质上共享了pageache这块内存。
MAP_ SHARED_ VALIDATE
此标志提供与 MAP_SHARED 相同的行为,区别在于MAP_SHARED 会忽略标志中的未知标志,而使用MAP_SHARED_VALIDATE 创建映射时,内核会验证所有传递的标志,如果存在未知标志,则映射失败并返回错误 EOPNOTSUPP。 这种映射类型也需要能够使用一些映射标志(例如,MAP_SYNC)。
MAP_PRIVATE
创建私有的copy_on_write的映射。映射的更新对同区域其他进程不可见,也不会传递到基础文件但是映射后文件本身被修改,是否被能够被看到是unspecified 。
在实现上,没一个私有影射都是对文件pagecache的一个COW,这样的话,每个进程之间的映射就是隔离的,彼此并不会感知到变化。至于文件修改之后出现未定义是因为,这取决于文件修改发生在COW之前还是之后。
以下标志,可用0个或多个进行“或”运算
MAP_ 32BIT
将映射放入进程地址空间的前2GB。仅用于64位的系统,目的是为了前2GB内存中的某些位置分配线程堆栈,从而提高早期64位处理器的上下文切换性能。现代系统已不再有此性能问题。设置MAP_ FIXED后将忽略此标志。
MAP_ANON
同下(MAP_ANONYMOUS),为兼容。
MAP_ANONYMOUS
映射没有任何文件支持,其内容初始化为0(应该是访问时再初始化), fd参数被忽略。因此现实要求定义为-1,offset参数应为0。可以和MAP_SHARED结合使用。
匿名页同文件页一样,可以同MAP_PRIVATE或MAP_SHARED连用,即共享匿名页和私有匿名页。私有匿名页是最常见的,使用malloc()申请的动态内存使用的就是私有匿名页,通过fork()产生的子进程访问私有匿名页时采用COW的方案,这也符合MAP_PRIVATE的特性。共享匿名页则常用于共享内存的实现,通过fork()产生的子进程共享访问父进程的内存映射。
文件页的访问,只要是不同进程映射了相同的文件就可以访问相同的文件页,但是对于匿名页,只能由父子进程之间才能访问到具体的变量从而访问匿名页。
MAP_DENYWRITE
MAP_EXECUTABLE
MAP_FILE
Ignored.
MAP_FIXED
直接使用addr作为起始地址,但addr必须要对齐:对于大多数架构,页面大小的倍数就足够了; 但是,某些架构可能会施加额外的限制。。若addr+Len会与其他映射重叠,则现有自己存在映射的重叠部分将被丢弃。MAP_ FIXED不利于软件移植.
MAP_FIXED_NOPEPLACE
同上,区别在于当与现有映射冲突时,将不会覆盖而是返回错误EEXIST,基于此可用于原子的尝试建立映射(一个线程成功,其他失败)。
旧内核不支持此Flag时,将返回"non-MAP_FIXED"的结果,即返回值与设定地址不同,这样就表示设定的addr是不可用的。
MAP_GROWSDOWN
标志用于堆栈。它向内核的虚拟内存指示,此映射在需要扩展时,是向下的低地址空间扩展。返回地址比实际在虚拟地址空间中创建的内存区域要低一页。低出来的这一页就是堆栈的保护页(guard page),当访问到guard page时,映射将向下一页一页的扩展,直到扩展到一个较低的映射的高端页,此时就不能再扩展了,访问guard page会产生SIGSEGV信号的错误。
MAP_ HUGETLB
使用大页创建映射(huge page).请参阅 Linux 内核源文件 Documentation/admin-guide/mm/hugetlbpage.rst 以及下面的 NOTES了解更多信息。
MAP_ HUGE_2MB,MAP_HUGE_IGB
与 MAP_HUGETLB 结合使用,用于指定需要使用多大( 2 MB 或 1 GB)的huge page。
More generally, the desired huge page size can be configured by encoding the base-2 logarithm of the desired page size in the six bits at the offset MAP_HUGE_SHIFT. (A value of zero in this bit field provides the default huge page size; the default huge page size can be discovered via the Hugepagesize field exposed by /proc/meminfo.) Thus, the above two constants are defined as:
#define MAP_HUGE_2MB (21 << MAP_HUGE_SHIFT)
#define MAP_HUGE_1GB (30 << MAP_HUGE_SHIFT)
可以通过列出 /sys/kernel/mm/hugepages 中的子目录来确定系统支持的大页面大小范围。
MAP_POPULATE
Populate是指预填充映射的page,对于文件来说即进行预读,此操作将避免Pagefault。如果无法填充映射(例如,由于使用 MAP_HUGETLB 时映射的大页面数量的限制),则 mmap() 调用不会失败。MAP_POPULATE 自 Linux 2.6.23 起支持私有映射。
MAP_NONBLOCK
此标志仅与 MAP_POPULATE 结合使用才有意义。从 Linux 2.6.23 开始,MAP_NONBLOCK导致 MAP_POPULATE 什么也不做。
MAP_LOCKED
作用同mlock(),即避免内存放入交换空间,此flags将进行映射的预填充,但如果填充失败并不会返回错误,因此语义不如mlock()强,可以用mmap()+mlock()
MAP_NORESERVE
不为此映射保留交换空间。 保留交换空间时,可以保证在物理内存不足时也可以修改(写)映射,即进行swap。 若没有保留交换空间,如果没有可用的物理内存,则可能会在写入时产生 SIGSEGV。 另请参阅 proc(5) 中对文件 /proc/sys/vm/overcommit_memory 的讨论。 在 2.6 之前的内核中,此标志仅对私有可写映射有效。
MAP_ STACk
在适合进程或线程堆栈的地址处分配映射,也就是高地址处。
这个标志目前在 Linux 上是一个 no-op。 但是,通过使用此标志,应用程序可以确保在将来这个标志可用时,能够得到支持。考虑到某些架构可能需要对堆栈分配进行特殊处理以及移植性的考量(MAP_STACK 在一些其他系统,如BSD,上存在且有影响),此标志它在 glibc线程实现中使用。
MAP_ SYNC
仅在MAP_ SHARED_VALIDATE映射类型下可用。仅支持DAX文件。对于其他文件,创建失败。
Shared file mappings with this flag provide the guarantee that while some memory is mapped writable in the address space of the process, it will be visible in the same file at the same offset even after the system crashes or is rebooted. In conjunction with the use of appropriate CPU instructions, this provides users of such mappings with a more efficient way of making data modifications persistent.(可以保证系统崩溃或重启后,不丢失?)
MAP_ UNINITIALIZED
不清楚医名页,旨在提高嵌入式设备性能。仅当使用 CONFIG_MMAP_ALLOW_UNINITIALIZED 选项配置内核时,才会使用此标志。 由于安全隐患,该选项通常仅在嵌入式设备(即可以完全控制用户内存内容的设备)上启用。
-
munmap()
删除指定区域(addr,addr+len)的映射,映射删除后再访问将导致无效内存引用。进程终止时,将自动取消映射,此外关闭fd不会引起取消。
消除映射的起始地址必须是页大小倍数,但len可以不是。All pages containing a part of the indicated range are unmapped, and subsequent references to these pages will generate SIGSEGV. It is not an error if the indicated range does not contain any mapped pages.
注意unmmap不是取消由mmap创建的,而取消任意
成功时, mmap() 返回一个指向映射区域的指针。 出错时,返回值 MAP_FAILED(即 (void *) -1),并设置 errno 以指示错误。
成功时,munmap() 返回 0。失败时,它返回 -1,并且设置 errno 以指示错误(可能是 EINVAL)。
EACCES
文件描述符是指非常规文件。 或者已请求文件映射,但 fd 未打开以供读取。 或者请求了 MAP_SHARED 并设置了 PROT_WRITE,但 fd 未在读/写 (O_RDWR) 模式下打开。 或者设置了 PROT_WRITE,但文件是append-only。EAGAIN
The file has been locked, or too much memory has been locked (see setrlimit(2)).
文件已被锁定,或太多内存已被锁定(请参阅 setrlimit(2))。EBADF
fd 不是有效的文件描述符(并且未设置 MAP_ANONYMOUS)。EEXIST
MAP_FIXED_NOREPLACE 在标志中指定,并且 addr 和长度覆盖的范围与现有映射冲突。EINVAL
addr、length 或 offset不标准(例如,它们太大,或未在页面边界上对齐)。EINVAL
length == 0EINVAL
标志不包含 MAP_PRIVATE、MAP_SHARED 或 MAP_SHARED_VALIDATE。ENFILE
已达到系统范围内打开文件总数的限制。ENODEV
指定文件的底层文件系统不支持内存映射。ENOMEM
没有可用的内存。ENOMEM
已超出进程的最大映射数。 在 munmap() 取消映射现有映射中间的区域时,也会发生此错误,因为这会导致被取消映射区域两侧产生两个较小的映射。ENOMEM
超出进程的 RLIMIT_DATA 限制(在 getrlimit(2) 中描述)。EOVERFLOW
On 32-bit architecture together with the large file extension (i.e., using 64-bit off_t): the number of pages used for length plus number of pages used for offset would overflow unsigned long (32 bits).EPERM
prot 参数要求 PROT_EXEC 但映射区域属于挂载 no-exec 的文件系统上的文件EPERM
操作被 file seal 阻止; 请参见 fcntl(2)。EPERM
指定了 MAP_HUGETLB 标志,但调用者没有特权(没有 CAP_IPC_LOCK capability)并且不是 sysctl_hugetlb_shm_group 组的成员; 参见 /proc/sys/vm/sysctl_hugetlb_shm_group 中的描述ETXTBSY
设置了MAP_DENYWRITE,但 fd 指定的对象已打开以进行写入。
使用映射区域可能会产生以下信号:
- SIGSEGV
尝试写入只读映射。 - SIGBUS
试图访问超出映射文件末尾的缓冲区页面。 有关如何处理与不是页面大小倍数的映射文件末尾相对应的页面中的字节的说明,请参阅 NOTES。
Interface | Attribute | Value |
---|---|---|
mmap(), munmap() | Thread safety | MT-Safe |
由 mmap() 映射的内存 ,会在fork(2)产生的子进程中保留,且具有相同的属性。
文件以页面大小的倍数进行映射。 对于不是页面大小倍数的文件,映射结束时部分页面中的剩余字节在映射时为零,并且对该区域的修改不会写出到文件中。映射之后更改文件的大小,对映射区域的影响是不可知的,这个应该是取决于是否已经加载了pagecache。
在某些硬件架构(例如 i386)上,PROT_WRITE 意味着 PROT_READ。 PROT_READ 是否隐含 PROT_EXEC 取决于体系结构。如果可移植程序打算在新映射中执行代码,则应始终设置 PROT_EXEC。
创建映射的可移植方式是将 addr 指定为 0 (NULL),并从标志中省略 MAP_FIXED。在这种情况下,系统选择映射的地址;选择地址以免与任何现有映射冲突,并且不会为 0。如果指定了 MAP_FIXED 标志,并且 addr 为 0 (NULL),则映射地址将为 0 (NULL)。
Certain flags constants are defined only if suitable feature test macros are defined (possibly by default): _DEFAULT_SOURCE with glibc 2.19 or later; or _BSD_SOURCE or _SVID_SOURCE in glibc 2.19 and earlier. (Employing _GNU_SOURCE also suffices, and requiring that macro specifically would have been more logical, since these flags are all Linux-specific.) The relevant flags are: MAP_32BIT, MAP_ANONYMOUS (and the synonym MAP_ANON), MAP_DENYWRITE, MAP_EXECUTABLE, MAP_FILE, MAP_GROWSDOWN, MAP_HUGETLB, MAP_LOCKED, MAP_NONBLOCK, MAP_NORESERVE, MAP_POPULATE, and MAP_STACK.
An application can determine which pages of a mapping are currently resident in the buffer/page cache using mincore(2).
Using MAP_FIXED safely
MAP_FIXED 的唯一安全用途是先前使用另一个映射保留了由 addr 和 length 指定的地址范围; 否则,使用 MAP_FIXED 是危险的,因为它强制删除预先存在的映射,使多线程进程很容易破坏自己的地址空间。
For example, suppose that thread A looks through /proc/
从 Linux 4.17 开始,多线程程序可以使用 MAP_FIXED_NOREPLACE 标志来避免在尝试在尚未被预先存在的映射保留的固定地址上创建映射时的上述危险。
Timestamps changes for file-backed mappings
For file-backed mappings, the st_atime field for the mapped file may be updated at any time between the mmap() and the corresponding unmapping; the first reference to a mapped page will update the field if it has not been already.
The st_ctime and st_mtime field for a file mapped with PROT_WRITE and MAP_SHARED will be updated after a write to the mapped region, and before a subsequent msync(2) with the MS_SYNC or MS_ASYNC flag, if one occurs.
Huge page (Huge TLB) mappings
For mappings that employ huge pages, the requirements for the arguments of mmap() and munmap() differ somewhat from the requirements for mappings that use the native system page size.
For mmap(), offset must be a multiple of the underlying huge page size. The system automatically aligns length to be a multiple of the underlying huge page size.
For munmap(), addr, and length must both be a multiple of the underlying huge page size.
C library/kernel differences
This page describes the interface provided by the glibc mmap() wrapper function. Originally, this function invoked a system call of the same name. Since kernel 2.4, that system call has been superseded by mmap2(2), and nowadays the glibc mmap() wrapper function invokes mmap2(2) with a suitably adjusted value for offset.
On Linux, there are no guarantees like those suggested above under MAP_NORESERVE. By default, any process can be killed at any moment when the system runs out of memory.
In kernels before 2.6.7, the MAP_POPULATE flag has effect only if prot is specified as PROT_NONE.
SUSv3 specifies that mmap() should fail if length is 0. However, in kernels before 2.6.12, mmap() succeeded in this case: no mapping was created and the call returned addr. Since kernel 2.6.12, mmap() fails with the error EINVAL for this case.
POSIX specifies that the system shall always zero fill any partial page at the end of the object and that system will never write any modification of the object beyond its end. On Linux, when you write data to such partial page after the end of the object, the data stays in the page cache even after the file is closed and unmapped and even though the data is never written to the file itself, subsequent mappings may see the modified content. In some cases, this could be fixed by calling msync(2) before the unmap takes place; however, this doesn't work on tmpfs(5) (for example, when using the POSIX shared memory interface documented in shm_overview(7)).
以下程序将指定的文件的一部分打印到标准输出,其第一个命令行参数指定文件路径。 要打印的字节范围是通过第二个和第三个命令行参数中的偏移量和长度值指定的。 该程序创建文件所需页面的内存映射,然后使用 write(2) 输出所需的字节。
源代码
#include
#include
#include
#include
#include
#include
#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;
/* Can't 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);
}