内存映射即在進程的虛拟地址空間中創建一個映射,分為兩種:
創建内存映射時,在進程的用戶虛拟地址空間中分配一個虛拟内存區域。内核采用延遲分配物理内存的策略,在進程第一次訪問虛拟頁的時候,産生缺頁異常。如果是文件映射,那麼分配物理頁,把文件指定區間的數據讀到物理頁中,然後在頁表中把虛拟頁映射到物理頁。
如果是匿名映射,就分配物理頁,然後在頁表中把虛拟頁映射到物理頁。
内核必須提供數據結構,以建立虛拟地址空間的區域和相關數據所在位置之間的關聯。例如,在映射文本文件時,映射的虛似内存區必須關聯到文件系統在硬盤上存儲文件内容的區域。
當然,給出的圖示是簡化的,因為文件數據在硬盤上的存儲通常并不是連續的,而是分布到若幹小的區域。内核利用address_space數據結構,提供一組方法從後備存儲器讀取數據。例如,從文件系統讀取。因此address_space形成了一個輔助層,将映射的數據表示為連續的線性區域,提供給内存管理子系統。按需分配和填充頁稱之為按需調頁法( demand paging)。它基于處理器和内核之間的交互,使用的各種數據結構如圖。
這些操作對用戶進程是透明的。換句話說,進程不會注意到頁是實際在物理内存中,還是需要通過按需調頁加載。
更多Linux内核視頻教程文檔資料免費領取後台私信【内核】自行獲取。
虛拟内存區域分配給進程的一個虛拟地址範圍,内核使用結構體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.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系統調用,隻需提供映射的虛拟地址。
參數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 中。
錯誤代碼:
我們回顧一下mmap内存映射原理的三個階段:
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每日頭條,我们将持续为您更新最新资讯!