tft每日頭條

 > 圖文

 > 什麼是内存映射文件

什麼是内存映射文件

圖文 更新时间:2025-04-22 22:38:51
a. 内存映射原理

内存映射即在進程的虛拟地址空間中創建一個映射,分為兩種:

  • 文件映射:文件支持的内存映射,把文件的一個區間映射到進程的虛拟地址空間,數據源是存儲設備上的文件。
  • 匿名映射:沒有文件支持的内存映射,把物理内存映射到進程的虛拟地址空間,沒有數據源。

創建内存映射時,在進程的用戶虛拟地址空間中分配一個虛拟内存區域。内核采用延遲分配物理内存的策略,在進程第一次訪問虛拟頁的時候,産生缺頁異常。如果是文件映射,那麼分配物理頁,把文件指定區間的數據讀到物理頁中,然後在頁表中把虛拟頁映射到物理頁。

如果是匿名映射,就分配物理頁,然後在頁表中把虛拟頁映射到物理頁。

内核必須提供數據結構,以建立虛拟地址空間的區域和相關數據所在位置之間的關聯。例如,在映射文本文件時,映射的虛似内存區必須關聯到文件系統在硬盤上存儲文件内容的區域。

什麼是内存映射文件(一文搞懂内存映射原理及使用方法)1

當然,給出的圖示是簡化的,因為文件數據在硬盤上的存儲通常并不是連續的,而是分布到若幹小的區域。内核利用address_space數據結構,提供一組方法從後備存儲器讀取數據。例如,從文件系統讀取。因此address_space形成了一個輔助層,将映射的數據表示為連續的線性區域,提供給内存管理子系統。按需分配和填充頁稱之為按需調頁法( demand paging)。它基于處理器和内核之間的交互,使用的各種數據結構如圖。

什麼是内存映射文件(一文搞懂内存映射原理及使用方法)2

  • 進程試圖訪問用戶地址空間中的一個内存地址,但使用頁表無法确定物理地址(物理内存中沒有關聯頁)。
  • 處理器接下來觸發一個缺頁異常,發送到内核。
  • 内核會檢查負責缺頁區域的進程地址空間數據結構,找到适當的後備存儲器,或者确認該訪問實際上是不正确的。
  • 分配物理内存頁,并從後備存儲器讀取所需數據填充。
  • 借助于頁表将物理内存頁并入到用戶進程的地址空間,應用程序恢複執行。

這些操作對用戶進程是透明的。換句話說,進程不會注意到頁是實際在物理内存中,還是需要通過按需調頁加載。

更多Linux内核視頻教程文檔資料免費領取後台私信【内核】自行獲取。

什麼是内存映射文件(一文搞懂内存映射原理及使用方法)3

b.數據結構

虛拟内存區域分配給進程的一個虛拟地址範圍,内核使用結構體vm_area_struct描述虛拟内存區域,主要核心成員如下:

我們知道struct mm_struct很重要,該結構提供了進程在内存中布局的所有必要信息。另外,它還包括下列成員,用于管理用戶進程在虛拟地址空間中的所有内存區域。

/*mm_types.h*/ struct mm_struct { struct vm_area_struct *mmap; /* list of VMAs */ struct rb_root mm_rb; u32 vmacache_seqnum; /* per-thread vmacache */ ..... }

用戶虛拟地址空間中的每個區域由開始和結束地址描述。現存的區域按起始地址以遞增次序被歸入鍊表中。掃描鍊表找到與特定地址關聯的區域,在有大量區域時是非常低效的操作(數據密集型的應用程序就是這樣)。因此vm_area_struct的各個實例還通過紅黑樹管理,可以顯著加快掃描速度。增加新區域時,内核首先搜索紅黑樹,找到剛好在新區域之前的區域。因此,内核可以向樹和線性鍊表添加新的區域,而無需掃描鍊表。

[外鍊圖片轉存失敗,源站可能有防盜鍊機制,建議将圖片保存下來直接上傳(img-zknBf3AO-1638714343843)(C:\Users\wangzhen\AppData\Roaming\Typora\typora-user-images\image-20211205101738426.png)]

b.1 虛拟内存區域的數據結構

每個區域表示為vm_area_struct的一個實例,其定義(簡化形式)如下:

/* * This struct defines a memory VMM memory area. There is one of these * per VM-area/task. A VM area is any part of the process virtual memory * space that has a special rule for the page-fault handlers (ie a shared * library, the executable area etc). */ struct vm_area_struct { /* The first cache line has the info for VMA tree walking. */ //這兩個成員分别用來保存該虛拟内存空間的首地址和末地址後的第一個字節的地址 unsigned long vm_start; /* Our start address within vm_mm. */ unsigned long vm_end; /* The first byte after our end address within vm_mm. */ /* linked list of VM areas per task, sorted by address */ struct vm_area_struct *vm_next, *vm_prev; //各進程的虛拟内存區域鍊表,按地址排序 struct rb_node vm_rb; //采用 紅黑樹(每個進程結構體mm_struct中都創建一顆紅黑樹,将VMA作為一個節點加入到紅黑樹中,提升搜索速度) /* * Largest free memory gap in bytes to the left of this VMA. * Either between this VMA and vma->vm_prev, or between one of the * VMAs below us in the VMA rbtree and its ->vm_prev. This helps * get_unmapped_area find a free area of the right size. */ unsigned long rb_subtree_gap; /* Second cache line starts here. */ //指向内存描述符,即虛拟内存區域所屬的用戶虛拟地址空間 struct mm_struct *vm_mm; /* The address space we belong to. */ //保護位,即訪問權限 pgprot_t vm_page_prot; /* Access permissions of this VMA. */ /* #define VM_READ 0x00000001 #define VM_WRITE 0x00000002 #define VM_EXEC 0x00000004 #define VM_SHARED 0x00000008 */ unsigned long vm_flags; /* Flags, see mm.h. */ /* * For areas with an address space and backing store, * linkage into the address_space->i_mmap interval tree. */ struct { struct rb_node rb; unsigned long rb_subtree_last; } shared; /* * A File's MAP_private vma can be in both i_mmap tree and anon_vma * list, after a COW of one of the file pages. A MAP_SHARED vma * can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack * or brk vma (with NULL file) can only be in an anon_vma list. */ //把虛拟内存區域關聯的所有的anon_vma實例串聯起來,一個虛拟内存區域會關聯到父進程的anon_vma實例和自己的anon_vma實例 struct list_head anon_vma_chain; /* Serialized by mmap_sem & * page_table_lock */ //指向一個anon_vma實例,結構anon_vma用來組織匿名頁被映射到的所有的虛拟地址空間 struct anon_vma *anon_vma; /* Serialized by page_table_lock */ /* Function pointers to deal with this struct. */ /* <mm.h> struct vm_operations_struct { void (*open)(struct vm_area_struct * area); void (*close)(struct vm_area_struct * area); int (*mremap)(struct vm_area_struct * area); int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf); int (*pmd_fault)(struct vm_area_struct *, unsigned long address, pmd_t *, unsigned int flags); void (*map_pages)(struct vm_area_struct *vma, struct vm_fault *vmf); int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf); int (*pfn_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf); int (*access)(struct vm_area_struct *vma, unsigned long addr, void *buf, int len, int write); const char *(*name)(struct vm_area_struct *vma); struct page *(*find_special_page)(struct vm_area_struct *vma, unsigned long addr); */ const struct vm_operations_struct *vm_ops; /* Information about our backing store: */ //文件偏移,單位是頁 unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */ struct file * vm_file; /* File we map to (can be NULL). */ void * vm_private_data; /* was vm_pte (shared mem) 指向内存區的私有數據*/ #ifndef CONFIG_MMU struct vm_region *vm_region; /* NOMMU mapping region */ #endif #ifdef CONFIG_NUMA struct mempolicy *vm_policy; /* NUMA policy for the VMA */ #endif struct vm_userfaultfd_ctx vm_userfaultfd_ctx; };

c. 系統調用和mmap内存映射

c.1 系統調用

應用程序通常使用C标準庫提供的函數malloc()申請内存,glibc庫的内存分配器ptmalloc使用brk或mmap向内核以頁為單位申請虛拟内存,然後把頁劃分成小内存塊分配給應用程序。默認的阈值是128kb,如果應用程序申請的内存長度小于阈值,ptmalloc分配器使用brk向内核申請虛拟内存,否則ptmalloc分配器使用mmap向内核申請虛拟内存。應用程序可以直接使用mmap向内核申請虛拟内存。

我們已經熟悉了内存映射相關的數據結構和地址空間操作,在本節中,我們将進一步讨論在建立映射時内核和應用程序之間的交互。就我們所知, C标準庫提供了mmap 函數建立映射。在内核一端,提供了兩個系統調用mmap和mmap2。兩個函數的參數相同。

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

這兩個調用都會在用戶虛拟地址空間中的pos位置,建立一個長度為len的映射,其訪問權限通過prot定義。 flags是一個标志集,用于設置一些參數。相關的文件通過其文件描述符fd标識。

mmap和mmap2之間的差别在于偏移量的語義( off)。在這兩個調用中,它都表示映射在文件中開始的位置。對于mmap,位置的單位是字節,而mmap2使用的單位則是頁( PAGE_SIZE)。因此即使文件比可用地址空間大,也可以映射文件的一部分。通常C标準庫隻提供一個函數,由應用程序用來創建内存映射。接下來該函數調用在内部轉換為适合于體系結構的系統調用。可使用munmap系統調用删除映射。因為不需要文件偏移量,因此不需要munmap2系統調用,隻需提供映射的虛拟地址。

  1. 參數start:指向欲映射的内存起始地址,通常設為 NULL,代表讓系統自動選定地址,映射成功後返回該地址。
  2. 參數length:代表将文件中多大的部分映射到内存。
  3. 參數prot:映射區域的保護方式。可以分為以下幾種方式的組合:
  • PROT_EXEC 映射區域可被執行
  • PROT_READ 映射區域可被讀取
  • PROT_WRITE 映射區域可被寫入
  • PROT_NONE 映射區域不能存取

參數flags:影響映射區域的各種特性。在調用mmap()時必須要指定MAP_SHARED 或MAP_PRIVATE。

MAP_FIXED 如果參數start所指的地址無法成功建立映射時,則放棄映射,不對地址做修正。通常不鼓勵用此旗标。

MAP_SHARED對映射區域的寫入數據會複制回文件内,而且允許其他映射該文件的進程共享。

MAP_PRIVATE 對映射區域的寫入操作會産生一個映射文件的複制,即私人的“寫入時複制”(copy on write)對此區域作的任何修改都不會寫回原來的文件内容。

MAP_ANONYMOUS建立匿名映射。此時會忽略參數fd,不涉及文件,而且映射區域無法和其他進程共享。

MAP_DENYWRITE隻允許對映射區域的寫入操作,其他對文件直接寫入的操作将會被拒絕。

MAP_LOCKED 将映射區域鎖定住,這表示該區域不會被置換(swap)。

參數fd:要映射到内存中的文件描述符。如果使用匿名内存映射時,即flags中設置了MAP_ANONYMOUS,fd設為-1。有些系統不支持匿名内存映射,則可以使用fopen打開/dev/zero文件,然後對該文件進行映射,可以同樣達到匿名内存映射的效果。

參數offset:文件映射的偏移量,通常設置為0,代表從文件最前方開始對應,offset必須是分頁大小的整數倍。

返回值:若映射成功則返回映射區的内存起始地址,否則返回MAP_FAILED(-1),錯誤原因存于errno 中。

錯誤代碼:

  • EBADF 參數fd 不是有效的文件描述詞
  • EACCES 存取權限有誤。如果是MAP_PRIVATE 情況下文件必須可讀,使用MAP_SHARED則要有PROT_WRITE以及該文件要能寫入。
  • EINVAL 參數start、length 或offset有一個不合法。
  • EAGAIN 文件被鎖住,或是有太多内存被鎖住。
  • ENOMEM 内存不足。

我們回顧一下mmap内存映射原理的三個階段:

  1. 進程啟動映射過程,并且在虛拟地址空間為映射創建虛拟映射區域;
  2. 調用内核空間的系統調用函數mmap(不同于用戶空間函數),實現文件物理地址和進程虛拟的一一映射關系;
  3. 進程發起對這片映射空間的訪問,引發缺頁異常,實現文件内容到物理内存(主存)的拷貝。

munmap()----删除内存映射 #include <sys/mman.h> int munmap(void *addr, size_t len);

mprotect()----設置虛拟内存區域的訪問權限 #include <sys/mman.h> int mprotect(void *addr, size_t len, int prot);

//進程1 #include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <string.h> #include <stdio.h> #include <unistd.h> #include <errno.h> typedef struct { /* data */ char name[4]; int age; }people; void main(int argc,char**argv) { int fd,i; people *p_map; char temp; fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777); lSEEK(fd,sizeof(people)*5-1,SEEK_SET); write(fd,"",1); p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(p_map==(void*)-1) { fprintf(stderr,"mmap : %s \n",strerror(errno)); return ; } close(fd); temp='A'; for(i=0;i<10;i ) { (*(p_map i)).name[1]='\0'; memcpy((*(p_map i)).name,&temp,1); (*(p_map i)).age=30 i; temp=temp 1; } printf("Initialize.\n"); sleep(15); munmap(p_map,sizeof(people)*10); printf("UMA OK.\n"); }

//進程2 #include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <string.h> #include <stdio.h> #include <unistd.h> #include <errno.h> typedef struct { /* data */ char name[4]; int age; }people; void main(int argc,char**argv) { int fd,i; people *p_map; fd=open(argv[1],O_CREAT|O_RDWR,00777); p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(p_map==(void*)-1) { fprintf(stderr,"mmap : %s \n",strerror(errno)); return ; } for(i=0;i<10;i ) { printf("name:%s age:%d\n",(*(p_map i)).name,(*(p_map i)).age); } munmap(p_map,sizeof(people)*10); }

//mprotect #include <unistd.h> #include <signal.h> #include <malloc.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/mman.h> #define handle_error(msg) do{ perror(msg); exit(EXIT_FAILURE);}while(0) static char *buffer; static void handler(int sig,siginfo_t *si,void *unused) { printf("Get SIGSEGV at address : %p\n",si->si_addr); exit(EXIT_FAILURE); } int main(int argc,char *argv[]) { int pagesize; struct sigaction sa; sa.sa_flags=SA_SIGINFO; sigemptyset(&sa.sa_mask); sa.sa_sigaction=handler; if(sigaction(SIGSEGV,&sa,NULL)==-1) handle_error("siaction"); pagesize=sysconf(_SC_PAGE_SIZE); if(pagesize==-1) handle_error("sysconf"); buffer=memalign(pagesize,4*pagesize); if(buffer==NULL) handle_error("memalign"); printf("start of region : %p\n",buffer); if(mprotect(buffer pagesize*2,pagesize,PROT_READ)==-1) handle_error("mprotect"); for(char *p=buffer;;) *(p )='A'; printf("for completed.\n"); exit(EXIT_SUCCESS); return 0; }

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关圖文资讯推荐

热门圖文资讯推荐

网友关注

Copyright 2023-2025 - www.tftnews.com All Rights Reserved