mmap,munmap-將文件或設(shè)備映射(消取映射)到內(nèi)存
#include<syslmman.h>
#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()
mmap()將在調(diào)用者進(jìn)程的虛擬地址空間上創(chuàng)建一個映射。映射的開始地址由addr指定,長度由length(必須大于 0)指定。
若addr為NULL,內(nèi)核將確定一個page-aligned(頁對齊)的起始地址來創(chuàng)建映射,這是常用方式;若addr為不空,內(nèi)核將其視為選擇開始地址的參照,內(nèi)核將在此addr附近page邊界視為起始(add不能小于proc/sys/vm/mmap_min_addr的值)。若此addr已經(jīng)存在映射,內(nèi)核會選擇一個可能取決于也可能不取決于addr的新地址。新映射的地址作為調(diào)用結(jié)果返回。
對于文件映射(區(qū)別于匿名映射)而言,其內(nèi)容對應(yīng)于由fd指向的文件中以offset偏移為起始內(nèi)容。offset的大小必須是sysconf(_SC_PAGE_SIZE)所返回的page大小的倍數(shù)。
mmap返回后,fd可以馬上關(guān)閉,而不影響映射。
prot參數(shù)描述了對映射的保護(hù),可以是以下標(biāo)志的按位或:
- 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參數(shù)
用于描述對映射更新對于映射同一區(qū)域的其他進(jìn)程(原文用的是process)是否可見(現(xiàn)實(shí)上應(yīng)該是考慮Pagecache),以及是否將更新進(jìn)行到基礎(chǔ)文件。由此必包含下面三個flags之一:
MAP_SHARED
共享映射, 映射的更新對映射同一區(qū)域的其他進(jìn)程是可見的,并且(文件映射)被傳遞到底層文件.(要精確控制何時將更新傳遞到底層文件需要使用 msync(2).)
在實(shí)現(xiàn)上,共享映射的物理內(nèi)存直接對應(yīng)于文件的pagecahe,因此當(dāng)一個進(jìn)程修改映射區(qū)時,即是修改了pagecahe,那么其他映射了文件相同區(qū)域的就會感知到變化,因?yàn)樗麄儽举|(zhì)上共享了pageache這塊內(nèi)存。
MAP_ SHARED_ VALIDATE
此標(biāo)志提供與 MAP_SHARED 相同的行為,區(qū)別在于MAP_SHARED 會忽略標(biāo)志中的未知標(biāo)志,而使用MAP_SHARED_VALIDATE 創(chuàng)建映射時,內(nèi)核會驗(yàn)證所有傳遞的標(biāo)志,如果存在未知標(biāo)志,則映射失敗并返回錯誤 EOPNOTSUPP。 這種映射類型也需要能夠使用一些映射標(biāo)志(例如,MAP_SYNC)。
MAP_PRIVATE
創(chuàng)建私有的copy_on_write的映射。映射的更新對同區(qū)域其他進(jìn)程不可見,也不會傳遞到基礎(chǔ)文件但是映射后文件本身被修改,是否被能夠被看到是unspecified 。
在實(shí)現(xiàn)上,沒一個私有影射都是對文件pagecache的一個COW,這樣的話,每個進(jìn)程之間的映射就是隔離的,彼此并不會感知到變化。至于文件修改之后出現(xiàn)未定義是因?yàn)?,這取決于文件修改發(fā)生在COW之前還是之后。
以下標(biāo)志,可用0個或多個進(jìn)行“或”運(yùn)算
MAP_ 32BIT
將映射放入進(jìn)程地址空間的前2GB。僅用于64位的系統(tǒng),目的是為了前2GB內(nèi)存中的某些位置分配線程堆棧,從而提高早期64位處理器的上下文切換性能。現(xiàn)代系統(tǒng)已不再有此性能問題。設(shè)置MAP_ FIXED后將忽略此標(biāo)志。
MAP_ANON
同下(MAP_ANONYMOUS),為兼容。
MAP_ANONYMOUS
映射沒有任何文件支持,其內(nèi)容初始化為0(應(yīng)該是訪問時再初始化), fd參數(shù)被忽略。因此現(xiàn)實(shí)要求定義為-1,offset參數(shù)應(yīng)為0。可以和MAP_SHARED結(jié)合使用。
匿名頁同文件頁一樣,可以同MAP_PRIVATE或MAP_SHARED連用,即共享匿名頁和私有匿名頁。私有匿名頁是最常見的,使用malloc()申請的動態(tài)內(nèi)存使用的就是私有匿名頁,通過fork()產(chǎn)生的子進(jìn)程訪問私有匿名頁時采用COW的方案,這也符合MAP_PRIVATE的特性。共享匿名頁則常用于共享內(nèi)存的實(shí)現(xiàn),通過fork()產(chǎn)生的子進(jìn)程共享訪問父進(jìn)程的內(nèi)存映射。
文件頁的訪問,只要是不同進(jìn)程映射了相同的文件就可以訪問相同的文件頁,但是對于匿名頁,只能由父子進(jìn)程之間才能訪問到具體的變量從而訪問匿名頁。
MAP_DENYWRITE
MAP_EXECUTABLE
MAP_FILE
Ignored.
MAP_FIXED
直接使用addr作為起始地址,但addr必須要對齊:對于大多數(shù)架構(gòu),頁面大小的倍數(shù)就足夠了; 但是,某些架構(gòu)可能會施加額外的限制。。若addr+Len會與其他映射重疊,則現(xiàn)有自己存在映射的重疊部分將被丟棄。MAP_ FIXED不利于軟件移植.
MAP_FIXED_NOPEPLACE
同上,區(qū)別在于當(dāng)與現(xiàn)有映射沖突時,將不會覆蓋而是返回錯誤EEXIST,基于此可用于原子的嘗試建立映射(一個線程成功,其他失?。?。
舊內(nèi)核不支持此Flag時,將返回"non-MAP_FIXED"的結(jié)果,即返回值與設(shè)定地址不同,這樣就表示設(shè)定的addr是不可用的。
MAP_GROWSDOWN
標(biāo)志用于堆棧。它向內(nèi)核的虛擬內(nèi)存指示,此映射在需要擴(kuò)展時,是向下的低地址空間擴(kuò)展。返回地址比實(shí)際在虛擬地址空間中創(chuàng)建的內(nèi)存區(qū)域要低一頁。低出來的這一頁就是堆棧的保護(hù)頁(guard page),當(dāng)訪問到guard page時,映射將向下一頁一頁的擴(kuò)展,直到擴(kuò)展到一個較低的映射的高端頁,此時就不能再擴(kuò)展了,訪問guard page會產(chǎn)生SIGSEGV信號的錯誤。
MAP_ HUGETLB
使用大頁創(chuàng)建映射(huge page).請參閱 Linux 內(nèi)核源文件 Documentation/admin-guide/mm/hugetlbpage.rst 以及下面的 NOTES了解更多信息。
MAP_ HUGE_2MB,MAP_HUGE_IGB
與 MAP_HUGETLB 結(jié)合使用,用于指定需要使用多大( 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 中的子目錄來確定系統(tǒng)支持的大頁面大小范圍。
MAP_POPULATE
Populate是指預(yù)填充映射的page,對于文件來說即進(jìn)行預(yù)讀,此操作將避免Pagefault。如果無法填充映射(例如,由于使用 MAP_HUGETLB 時映射的大頁面數(shù)量的限制),則 mmap() 調(diào)用不會失敗。MAP_POPULATE 自 Linux 2.6.23 起支持私有映射。
MAP_NONBLOCK
此標(biāo)志僅與 MAP_POPULATE 結(jié)合使用才有意義。從 Linux 2.6.23 開始,MAP_NONBLOCK導(dǎo)致 MAP_POPULATE 什么也不做。
MAP_LOCKED
作用同mlock(),即避免內(nèi)存放入交換空間,此flags將進(jìn)行映射的預(yù)填充,但如果填充失敗并不會返回錯誤,因此語義不如mlock()強(qiáng),可以用mmap()+mlock()
MAP_NORESERVE
不為此映射保留交換空間。 保留交換空間時,可以保證在物理內(nèi)存不足時也可以修改(寫)映射,即進(jìn)行swap。 若沒有保留交換空間,如果沒有可用的物理內(nèi)存,則可能會在寫入時產(chǎn)生 SIGSEGV。 另請參閱 proc(5) 中對文件 /proc/sys/vm/overcommit_memory 的討論。 在 2.6 之前的內(nèi)核中,此標(biāo)志僅對私有可寫映射有效。
MAP_ STACk
在適合進(jìn)程或線程堆棧的地址處分配映射,也就是高地址處。
這個標(biāo)志目前在 Linux 上是一個 no-op。 但是,通過使用此標(biāo)志,應(yīng)用程序可以確保在將來這個標(biāo)志可用時,能夠得到支持??紤]到某些架構(gòu)可能需要對堆棧分配進(jìn)行特殊處理以及移植性的考量(MAP_STACK 在一些其他系統(tǒng),如BSD,上存在且有影響),此標(biāo)志它在 glibc線程實(shí)現(xiàn)中使用。
MAP_ SYNC
僅在MAP_ SHARED_VALIDATE映射類型下可用。僅支持DAX文件。對于其他文件,創(chuàng)建失敗。
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.(可以保證系統(tǒng)崩潰或重啟后,不丟失?)
MAP_ UNINITIALIZED
不清楚醫(yī)名頁,旨在提高嵌入式設(shè)備性能。僅當(dāng)使用 CONFIG_MMAP_ALLOW_UNINITIALIZED 選項(xiàng)配置內(nèi)核時,才會使用此標(biāo)志。 由于安全隱患,該選項(xiàng)通常僅在嵌入式設(shè)備(即可以完全控制用戶內(nèi)存內(nèi)容的設(shè)備)上啟用。
-
munmap()
刪除指定區(qū)域(addr,addr+len)的映射,映射刪除后再訪問將導(dǎo)致無效內(nèi)存引用。進(jìn)程終止時,將自動取消映射,此外關(guān)閉fd不會引起取消。
消除映射的起始地址必須是頁大小倍數(shù),但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創(chuàng)建的,而取消任意
成功時, mmap() 返回一個指向映射區(qū)域的指針。 出錯時,返回值 MAP_FAILED(即 (void *) -1),并設(shè)置 errno 以指示錯誤。
成功時,munmap() 返回 0。失敗時,它返回 -1,并且設(shè)置 errno 以指示錯誤(可能是 EINVAL)。
EACCES
文件描述符是指非常規(guī)文件。 或者已請求文件映射,但 fd 未打開以供讀取。 或者請求了 MAP_SHARED 并設(shè)置了 PROT_WRITE,但 fd 未在讀/寫 (O_RDWR) 模式下打開。 或者設(shè)置了 PROT_WRITE,但文件是append-only。EAGAIN
The file has been locked, or too much memory has been locked (see setrlimit(2)).
文件已被鎖定,或太多內(nèi)存已被鎖定(請參閱 setrlimit(2))。EBADF
fd 不是有效的文件描述符(并且未設(shè)置 MAP_ANONYMOUS)。EEXIST
MAP_FIXED_NOREPLACE 在標(biāo)志中指定,并且 addr 和長度覆蓋的范圍與現(xiàn)有映射沖突。EINVAL
addr、length 或 offset不標(biāo)準(zhǔn)(例如,它們太大,或未在頁面邊界上對齊)。EINVAL
length == 0EINVAL
標(biāo)志不包含 MAP_PRIVATE、MAP_SHARED 或 MAP_SHARED_VALIDATE。ENFILE
已達(dá)到系統(tǒng)范圍內(nèi)打開文件總數(shù)的限制。ENODEV
指定文件的底層文件系統(tǒng)不支持內(nèi)存映射。ENOMEM
沒有可用的內(nèi)存。ENOMEM
已超出進(jìn)程的最大映射數(shù)。 在 munmap() 取消映射現(xiàn)有映射中間的區(qū)域時,也會發(fā)生此錯誤,因?yàn)檫@會導(dǎo)致被取消映射區(qū)域兩側(cè)產(chǎn)生兩個較小的映射。ENOMEM
超出進(jìn)程的 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 參數(shù)要求 PROT_EXEC 但映射區(qū)域?qū)儆趻燧d no-exec 的文件系統(tǒng)上的文件EPERM
操作被 file seal 阻止; 請參見 fcntl(2)。EPERM
指定了 MAP_HUGETLB 標(biāo)志,但調(diào)用者沒有特權(quán)(沒有 CAP_IPC_LOCK capability)并且不是 sysctl_hugetlb_shm_group 組的成員; 參見 /proc/sys/vm/sysctl_hugetlb_shm_group 中的描述ETXTBSY
設(shè)置了MAP_DENYWRITE,但 fd 指定的對象已打開以進(jìn)行寫入。
使用映射區(qū)域可能會產(chǎn)生以下信號:
-
SIGSEGV
嘗試寫入只讀映射。 -
SIGBUS
試圖訪問超出映射文件末尾的緩沖區(qū)頁面。 有關(guān)如何處理與不是頁面大小倍數(shù)的映射文件末尾相對應(yīng)的頁面中的字節(jié)的說明,請參閱 NOTES。
| Interface | Attribute | Value |
|---|---|---|
| mmap(), munmap() | Thread safety | MT-Safe |
由 mmap() 映射的內(nèi)存 ,會在fork(2)產(chǎn)生的子進(jìn)程中保留,且具有相同的屬性。
文件以頁面大小的倍數(shù)進(jìn)行映射。 對于不是頁面大小倍數(shù)的文件,映射結(jié)束時部分頁面中的剩余字節(jié)在映射時為零,并且對該區(qū)域的修改不會寫出到文件中。映射之后更改文件的大小,對映射區(qū)域的影響是不可知的,這個應(yīng)該是取決于是否已經(jīng)加載了pagecache。
在某些硬件架構(gòu)(例如 i386)上,PROT_WRITE 意味著 PROT_READ。 PROT_READ 是否隱含 PROT_EXEC 取決于體系結(jié)構(gòu)。如果可移植程序打算在新映射中執(zhí)行代碼,則應(yīng)始終設(shè)置 PROT_EXEC。
創(chuàng)建映射的可移植方式是將 addr 指定為 0 (NULL),并從標(biāo)志中省略 MAP_FIXED。在這種情況下,系統(tǒng)選擇映射的地址;選擇地址以免與任何現(xiàn)有映射沖突,并且不會為 0。如果指定了 MAP_FIXED 標(biāo)志,并且 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 是危險的,因?yàn)樗鼜?qiáng)制刪除預(yù)先存在的映射,使多線程進(jìn)程很容易破壞自己的地址空間。
For example, suppose that thread A looks through /proc/<pid>/maps in order to locate an unused address range that it can map using MAP_FIXED, while thread B simultaneously acquires part or all of that same address range. When thread A subsequently employs mmap(MAP_FIXED), it will effectively clobber the mapping that thread B created. In this scenario, thread B need not create a mapping directly; simply making a library call that, internally, uses dlopen(3) to load some other shared library, will suffice. The dlopen(3) call will map the library into the process's address space. Furthermore, almost any library call may be implemented in a way that adds memory mappings to the address space, either with this technique, or by simply allocating memory. Examples include brk(2), malloc(3), pthread_create(3), and the PAM libraries ?http://www.linux-pam.org?.
從 Linux 4.17 開始,多線程程序可以使用 MAP_FIXED_NOREPLACE 標(biāo)志來避免在嘗試在尚未被預(yù)先存在的映射保留的固定地址上創(chuàng)建映射時的上述危險。
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)).
以下程序?qū)⒅付ǖ奈募囊徊糠执蛴〉綐?biāo)準(zhǔn)輸出,其第一個命令行參數(shù)指定文件路徑。 要打印的字節(jié)范圍是通過第二個和第三個命令行參數(shù)中的偏移量和長度值指定的。 該程序創(chuàng)建文件所需頁面的內(nèi)存映射,然后使用 write(2) 輸出所需的字節(jié)。
源代碼
#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;
/* 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);
}