【Linux内核】内存映射原理
【Linux內核】內存映射原理
物理地址空間
物理地址是處理器在總線上能看到的地址,使用RISC(Reduced Instruction Set Computing精簡指令集)的處理器通常只實現一個物理地址空間,外圍設備和物理內存使用統一的物理空間,
有些架構的處理器把分配給外圍設備的物理地址稱為設備內存
處理器通過外圍設備控制器里面的寄存器來訪問外圍設備,寄存器分為控制寄存器,狀態寄存器和數據寄存器
外圍設備的寄存器通常被常備連續的編址,處理器對外圍設備寄存器的編址方式分為兩種:I/O映射方式,內存映射方式
- IO映射方式:x86處理器專門為外圍設備單獨提供一個空間,通過單獨的指令(如in out)來訪問這個空間的地址
- 內存映射方式:外圍設備和物理內存使用同一個物理空間,處理器以相同方式訪問物理內存和外圍設備,那么應用如何訪問外圍設備呢? 操作系統提供單獨的函數將外圍設備對應的地址映射到應用的虛擬內存中,從而使應用能訪問設備
ARM64架構分為兩種內存類型:
-
正常內存(Normal Memory) :包括物理內存和只讀存在器(ROM) ;
對于正常內存來講可以設置共享屬性,包括緩沖區的共享屬性,共享屬性分為不可共享,內部共享和外部共享.不可共享:制備處理器一個核心使用,內部共享:可以被多個核使用,外部共享:可以被DMI(DMI是指Direct Media InterfaceI(直接媒體接口))等共享1
-
設備內存(Device Memory)∶指分配給外圍設備寄存器的物理地址區域
設備內存的共享屬性總是外部共享
內存映射原理
? 創建內存映射時,在進程的用戶虛擬地址空間中分配一個虛擬內存區域,內核采用一個延遲物理內存分配策略,在進程第一次訪問虛擬頁的時候產生缺頁異常2
? 如果是文件映射,那么分配物理頁把文件指定的區域數據讀到物理頁當中,然后把應用的虛擬頁表映射到物理頁,
? 如果是匿名映射,就分配物理頁,讓后把虛擬頁映射到物理頁
內存映射即在進程的虛擬地址空間創建一個映射,分為兩種:
-
文件映射:文件支持內存映射,把文件的一段區域映射進程的虛擬空間,文件源式存儲設備上的文件
兩個進程可以使用共享的文件映射實現共享內存
-
匿名映射3:沒有文件支持的映射,把物理內存映射到進程里的虛擬空間,無數據源
匿名映射通常是私有映射,只可能出現在父進程和子進程之間
在進程的虛擬地址空間中,代碼段和數據段是私有文件映射
未初始化的數據段,堆棧是私有的匿名映射
線程啟動映射過程,并且在虛擬地址空間中為內存映射創建一個映射區,先在用戶空間調用mmap函數,并且在內存虛擬空間中找到一段連續空閑的符合要求的虛擬地址,對這個區域初始化,并插入進程虛擬空間地址的鏈表,讓后再內核中系統調用,實現文件的物理地址和進程虛擬地址之間一一對應的關系,進程開始查詢文件內容,這是雖然進程虛擬內存與文件物理地址已經建立映射關系,但由于文件相關區域還沒有加載到物理內存中,這是就會引發缺頁中斷,請求將磁盤內容調入內存當中,先在進程緩存空間swapcathe4中進行查找,如果沒有找到,就通過nopage()把缺頁從磁盤調入內存,如果你對內存進行寫入改變內存內容,一段時間后,系統就會將改變的臟頁寫入硬盤,也可以使用函數強制及時同步.
數據結構
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 addresswithin 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;/** 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. */unsigned long vm_flags; /* Flags, see 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);/* notification that a previously read-only page is about to become* writable, if an error is returned it will cause a SIGBUS */int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);/* same as page_mkwrite when using VM_PFNMAP|VM_MIXEDMAP */int (*pfn_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf); }系統調用
? 應用程序通常使用C標準庫提供的函數malloc()申請內存,glibc庫的內存分配器ptmalloc使用brk或mmap向內核以頁為單位申請虛擬內存然后把頁劃分為小塊共應用程序使用
? 應用程序可以使用mmap向內核申請虛擬內存
1、mmap()----創建內存映射
#include <sys/mman.h> void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset);系統調用mmap():進程創建匿名的內存映射,把內存的物理頁映射到進程的虛擬地址空間。進程把文件映射到進程的虛擬地址空間,可以像訪問內存一樣訪問文件,不需要調用系統調用read()/write()訪問文件,從而避免用戶模式和內核模式之間的切換,提高讀寫文件速度。 兩個進程針對同一個文件創建共享的內存映射,實現共享內存。
2、munmap()----刪除內存映射
#include <sys/mman.h> int munmap(void *addr, size_t len);3、mprotect()----設置虛擬內存區域的訪問權限
#include <sys/mman.h> int mprotect(void *addr, size_t len, int prot);1、進程啟動映射過程,并且在虛擬地址空間中為映射創建虛擬映射區域;
2、調用內核空間的系統調用函數mmap(不同于用戶空間函數),實現文件物理地址和進程虛擬的一一映射關系;
3、進程發起對這片映射空間的訪問,引發缺頁異常,實現文件內容到物理內存(主存)的拷貝。
上代碼:
#include <errno.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.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++){temp = temp + 1;(*(p_map + i)).name[1] = '\0';memcpy((*(p_map + i)).name, &temp, 1);(*(p_map + i)).age = 30 + i;}printf("Initialize.\n");sleep(15);munmap(p_map, sizeof(people) * 10);printf("UMA OK.\n"); } #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); }**我所有的學習筆記都在Github倉庫里:https://github.com/fanxing-6/CPP-learning-notes **
,如果訪問Github有問題也可以訪問Gitee:CPP-learning-notes: C++學習筆記 (gitee.com)
本人能力有限,筆記難免有疏漏,如果有錯誤,歡迎各位關注公眾號與我交流,一定會及時回復
DMI是Intel(英特爾)公司開發用于連接主板南北橋的總線,取代了以前的Hub-Link總線。DMI采用點對點的連接方式,時鐘頻率為100MHz,由于它是基于PCI-Express總線,因此具有PCI-E總線的優勢。DMI實現了上行與下行各1GB/s的數據傳輸率,總帶寬達到2GB/s,這個高速接口集成了高級優先服務,允許并發通訊和真正的同步傳輸能力。它的基本功能對于軟件是完全透明的,因此早期的軟件也可以正常操作。 ??
缺頁異常,頁缺失Page fault,指的是硬錯誤、硬中斷、分頁錯誤、尋頁缺失、缺頁中斷、頁故障等)指的是當軟件試圖訪問已映射在虛擬地址空間中,但是目前并未被加載在物理內存中的一個分頁時,由中央處理器的內存管理單元所發出的中斷。通常情況下,用于處理此中斷的程序是操作系統的一部分。如果操作系統判斷此次訪問是有效的,那么操作系統會嘗試將相關的分頁從硬盤上的虛擬內存文件中調入內存。而如果訪問是不被允許的,那么操作系統通常會結束相關的進程。雖然其名為“頁缺失”錯誤,但實際上這并不一定是一種錯誤。而且這一機制對于利用虛擬內存來增加程序可用內存空間的操作系統(比如Microsoft Windows和各種類 Unix 系統)中都是常見且有必要的。 ??
mmap是一種內存映射文件的方法,即將一個文件或者其它對象映射到進程的地址空間,實現文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關系。實現這樣的映射關系后,進程就可以采用指針的方式讀寫操作這一段內存,而系統會自動回寫臟頁面到對應的文件磁盤上,即完成了對文件的操作而不必再調用read,write等系統調用函數。相反,內核空間對這段區域的修改也直接反映用戶空間,從而可以實現不同進程間的文件共享。進程在用戶空間調用庫函數mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);來實現內存映射。從原型可知,存在一個參數為fd,根據fd,存在一種情況叫匿名映射,所謂匿名映射,表示不存在fd這么個真實的文件。實現匿名映射的方式主要有以下兩種: 1、BSD 提供匿名映射的辦法是fd =-1,同時 flag 指定為MAP_SHARE|MAP_ANON。 ptr = mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON,-1,0);2、SVR4 提供匿名映射的辦法是 open /dev/zero設備文件,把返回的文件描述符,作為mmap的fd參數。 fd = open("/dev/zero",O_RDWR); /dev/zero 是一個特殊的文件,當你讀它的時候,它會提供無限的空字符(NULL, ASCII NUL, 0x00) 一個作用是用它作為源,產生一個特定大小的空白文件。匿名內存映射適用于具有親屬關系的進程之間;由于父子進程之間的這種特殊的父子關系,在父進程中先調用mmap(),然后調用fork(),那么,在調用fork() 之后,子進程繼承了父進程的所有資源,當然也包括匿名映射后的地址空間和mmap()返回的地址,這樣父子進程就可以通過映射區域進行通信了;這里不是一般的繼承關系,一般來說,子進程單獨維護從父進程繼承下來的一些變量,而mmap()返回的地址卻是由父子進程共同維護的;對于具有親屬關系的進程之間實現共享內存的最好方式應該是采用匿名映射的方式。此時,不必指定具體的條件,只要設置相應的標志即可。 ??
總結
以上是生活随笔為你收集整理的【Linux内核】内存映射原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 30个Python极简代码,10分钟ge
- 下一篇: 计算机系统基础:磁盘调度知识笔记