虛擬內存與mmap,brk


 1. 基本概念及相關術語

1.1 基本概念

        虛擬內存使得應用程序認為它擁有連續的可用的內存(一個連續完整的地址空間),而實際上,它通常是被分隔成多個物理內存碎片,還有部分暫時存儲在外部磁盤存儲器上,在需要時進行數據交換。即將不完整,不連續的物理內存映射為連續的虛擬內存。虛擬內存主要有以下三個作用:

(1) 它將主存看成是磁盤的一個高速緩存,只在主存中保存活動區域(通常一個進程只有執行上下文被加載到主存,其余的在磁盤中,隨用隨加載);

(2) 為每個進程提供一致的地址空間,簡化了內存管理;

(3) 它保護每個進程的地址空間不被其他進程破壞(在頁表的PTE條目中加入額外控制信息實現內存保護)。

  虛擬內存有兩個重要的地址,虛擬地址(virtual address, VA)和物理地址(physical address)。在訪問某個對象時,CPU給出虛擬地址,通過查詢計算得到物理地址,然后訪問物理地址上的對象。整個過程如下圖:

                

 

 

                                            圖1  CPU訪問主存

1.2 相關術語

在表述虛擬內存相關概念時,有些約定的縮寫和表達方式

  • N=2n:虛擬地址數量,n表示虛擬地址位數;
  • M=2m:物理地址數量,m表示物理地址位數;
  • P=2p:頁大小,p表示頁偏移量的位數;
  • VPO(virtual page offset):虛擬地址頁偏移;
  • VPN(virtual page number):虛擬地址頁號
  • PPO(physical page offset):物理地址頁偏移
  • PPN(physical page number):物理地址頁號。
  • 頁表(Page Table, PT):記錄虛擬地址到物理地址的映射的表
  • 頁表項(Page Table Entry, PTE):頁表中一行,PTE的索引即VPN;
  • 頁表項地址(PTEA):在CPU中有個頁表基址寄存器,記錄頁表起始地址,頁表基址寄存器+PTE索引=PTEA;
  •  MMU(Memory Management Unit):內存管理單元,用於虛擬地址到物理地址尋址的硬件。

一個頁表的常見結構如下圖:

                          

 

 

                                      圖2 頁表常見結構(有效位表示該PTE是否有VP到PP的映射)

eg: 給定一個32位虛擬地址空間和一個24位物理地址空間,,對於下面的頁大小,確定VPN,VPO,PPN,PPO的位數。

P VPN位數 VPO位數 PPN位數 PPO位數
1KB 22 10 14

10

4KB 20 12 12

12

注:VPO表示對象在頁中的偏移,VPO=PPO,VPO位數=log2(P),VPN表示虛擬頁號,對應PTE表索引,PPN表示物理頁號。

 

 

  一個虛擬地址翻譯成物理地址,方法如下圖:

       

 

                            圖3 虛擬地址翻譯為物理地址

  地址翻譯時,給定虛擬地址,低p位表示頁偏移,其中VPO=PPO,高n-p位表示虛擬頁號,即PTE的索引號,找到對應PTE記錄,得到物理頁號PPN,跟PPO組合得到物理地址。所以訪問一個對象,首先訪問頁表,從虛擬地址轉化為物理地址,再從訪問物理地址得到對象。由於頁表和物理地址都在內存中,因此存在兩次內存訪問。

1.3 地址翻譯加速

  從1.2中得知為了訪問對象,需要兩次內存訪問,每次內存訪問一般幾十到幾百個周期,為了加快地址翻譯,減少內存訪問次數,有兩種輔助設備:SRAM緩存和TLB緩存。

  SRAM緩存:在CPU和主存(DRAM)之間,還有L1, L2, L3三級高速緩存(SRAM)。因此,可以將部分PTE條目和對象存到SRAM中,減少內存訪問次數,添加了SRAM的訪問機制如下圖。

                     

 

                                     圖4 加入SRAM的對象訪問過程

可見,在訪問時,優先訪問SRAM獲取PTE和數據,沒有再訪問主存,還沒有則引起缺頁中斷。SRAM的訪問通常幾個時間周期。

  TLB緩存:在MMU中的虛擬地址緩存器,稱為翻譯后備寄存器(Translantion Lookaside Buffer)。每一行由一個或多個PTE條目組成,其中TLBI用於行號索引,TLBT用於同一行某個PTE的選擇。

                                                

 

                                                          圖5 虛擬地址在TLB中的含義

比如某一時刻,TLB中的快照如下:

                                    

 

                                                                         圖6 TLB快照,四組,四路組相聯 

頁面大小64字節,虛擬地址長度14位,物理地址長度為12位。給定虛擬地址0x03d4,其二進制表示為0b 00 0011 1101 0100,低6位0b 01 0100為VPO,因為四路組相聯,所以第6-7位為TLBI(TLB索引),為0b 11,剩余為TLBT(TLB標記),TLBI表示TLB表的行號,找到TLBT為0x03的位置,得到PPN為0D。結合VPO,得到物理地址為0b 0011 0101 0100,即0x0354。加入TLB之后的對象訪問過程如下:

                              

 

                                                    圖7 加入TLB的對象訪問過程

 2. Linux虛擬內存

2.1 Linux虛擬內存組織機制

Linux系統為每個進程維護一個單獨的地址空間,如圖8(a)所示,同時為每個進程維護一個結構體,其中包含虛擬內存相關信息,如圖8(b)所示。

         

 

 

  (a) Linux進程的虛擬內存                                                      (b)管理虛擬內存的結構體

                              圖8 Linux虛擬內存

其中vm_prot描述虛擬內存頁的讀寫權限,vm_flags記錄該虛擬頁是共享還是私有等其他常見信息。

2.2 內存映射

  Linux系統將虛擬內存和一個磁盤對象關聯起來,以初始化虛擬內存區域的內容,稱為內存映射。有兩種類型的內存映射:

(1) 映射到Linux文件系統中的普通文件;

(2) 映射到匿名文件,匿名文件是由內核創建的全是二進制0的文件,CPU第一次使用該虛擬頁面時,內核就選擇一個物理頁面進行覆蓋(整個過程沒有跟磁盤發生數據交互)。

  一個對象映射到虛擬內存中,要么以共享對象存在,要么以私有對象存在。不論哪一種模式,在物理內存中只有一份副本共享對象一個進程的寫操作,其他進程都可見,並且能反映到磁盤上;私有對象一個進程的寫操作,其他進程不可見,並且不能反映到磁盤上

         

 

 

 

            (a) 內存映射到共享區域                                                  (b) 內存映射到私有區域

                                                  圖9 多個進程映射同一對象

  對於多個進程內存映射到私有區域時,物理內存只有一份副本,此時采用一種"寫時復制"策略。即進程在寫時,復制修改的部分到內存其他區域。這樣對其他進程來說,對象沒有修改過。

2.3 mmap函數

  mmap函數提供用戶級的內存映射,該函數能夠把某個磁盤文件映射到內存中,函數的主要格式如下:

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
start:內存起始地址,通常為NULL,讓系統自己選擇
length:內存的長度
prot:
    PROT_READ:數據可讀
    PROT_WRITE:數據可寫
    PROT_EXEC:數據可執行
    PROT_NONE:數據不可訪問
flags:
    MAP_SHARED:共享對象,進程間可察覺修改,並能反映到磁盤
    MAP_PRIVATE:私有對象,一切操作只在本進程可見,修改不會寫入磁盤
    MAP_FIXED:基本不用
fd:映射的文件的描述符,通常應先打開文件,再調用mmap,此后關閉文件映射仍然存在
offset:文件偏移量,一般為0
該函數返回內存中對應的地址

調用mmap之后,內存與磁盤文件之間就建立了映射關系,如下圖所示:

 

 

munmap用於解除映射關系

int munmap(void* start,size_t length);

使用mmap的作用主要有以下兩個:

(1) 將磁盤文件映射到內存中,這樣所有讀寫均針對內存讀寫(可以使用memcpy等內存操作函數,而不是read,write等IO操作函數),加快訪問速度;

(2) 在無親緣關系的進程間提供共享內存。

使用mmap函數,需要注意以下問題

(1) 在文件映射之前,必須打開該文件,而且mmap的prot權限不能超過打開的權限。比如open打開時只設置了讀文件,那么prot就不能設置PROT_WRITE;

(2) 內存映射通常都是按虛擬內存的頁為基本單位的。比如一個頁512字節,但是映射的文件只有12字節。那么剩下的500字節會自動填充為零,即時修改了后面的500字節,也不會寫入到文件(所以較好的操作是直到文件大小,直接加長文件);

(3) 如果試圖訪問不存在的映射關系,比如頁面大小512字節,實際文件大小為12字節,用mmap映射的時候映射1000個字節,那么實際可操作的結果如下:

 

 

(4) 將內存寫入磁盤的操作通常由頁守護進程完成,如果想人為控制將內存數據寫入磁盤,可以調用以下函數:

#include <sys/mman.h>

int msync(void *addr,size_t len,int flags); 

flags:
    MS_ASYC:異步寫入
    MS_SYC:同步寫入,等待寫入之后才會返回

(5) 進程終止或調用munmap時解除映射關系,關閉文件描述符不會解除映射關系。

 

下面舉一個簡單的例子:父子進程同時修改一個文件寫入數據:

 1 #include <sys/mman.h>
 2 #include <stdio.h>
 3 #include <fcntl.h>
 4 #include <unistd.h>
 5 #include <sys/wait.h>
 6 #include <sys/types.h>
 7 #include <stdlib.h>
 8 #include <string.h>
 9 
10 #define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH)
11 
12 int main()
13 {
14     int fd;
15     if((fd=open("map.txt",O_RDWR|O_CREAT|O_TRUNC,FILE_MODE))<0)
16     {
17         printf("open file failed\n");
18         exit(1);
19     }
20 
21 
22     if(ftruncate(fd,50)<0)  //文件大小50字節
23     {
24         printf("ftruncate error\n");
25         exit(1);
26     }
27     
28     char *buf;//起始地址
29     
30     buf=(char*)mmap(NULL,50,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
31     close(fd);
32     pid_t pid;
33     if((pid=fork())<0)
34         printf("fork error\n");
35     
36     char* msg="hello world\n";
37     char* msg1= "good news";
38     if(pid==0) //子進程
39     {
40         memcpy(buf,msg,strlen(msg));
41         exit(0);
42     }
43     else
44     {
45         int stat;
46         wait(&stat);
47         memcpy(buf+strlen(msg),msg1,strlen(msg1));
48     }
49     return 0;
50     
51 }

第22-26行就是申請文件大小為50字節,那么實際內存可修改的部分就是buf~(buf+49)。注釋該段再執行就會報SIGBUS錯誤。

執行結果是當前目錄多了map.txt,其內容為:

hello world

good news

 2.4 Linux進程分配內存的方式

關於此部分詳細介紹參考博文:https://www.cnblogs.com/vinozly/p/5489138.html

  簡單來說,當我們調用分配內存的函數時(如malloc),底層通過調用brk()或mmap()實現。當遇到小於128KB的內存時,調用brk()函數將數據段堆的_edata地址往高地址推(即圖8a中brk指向的指針,此時只分配虛擬內存,沒有物理內存。當產生缺頁中斷時,才調用物理內存)。當申請內存大於128KB時,調用mmap()在堆棧之間的共享區域分配內存(此部分內存可以單獨釋放)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM