溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Linux內(nèi)存映射指的是什么

發(fā)布時間:2022-01-27 10:10:42 來源:億速云 閱讀:223 作者:kk 欄目:開發(fā)技術(shù)

Linux內(nèi)存映射指的是什么,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

內(nèi)存映射就是用戶空間的一段內(nèi)存區(qū)域映射到內(nèi)核空間,映射成功后,用戶對這段內(nèi)存區(qū)域的修改可以直接反映到內(nèi)核空間,相反,內(nèi)核空間對這段區(qū)域的修改也直接反映用戶空間。

一. 內(nèi)存映射原理

由于所有用戶進(jìn)程總的虛擬地址空間比可用的物理內(nèi)存大很多,因此只有最常用的部分才與物理頁幀關(guān)聯(lián)。這不是問題,因為大多數(shù)程序只占用實際可用內(nèi)存的一小部分。在將磁盤上的數(shù)據(jù)映射到進(jìn)程的虛擬地址空間的時,內(nèi)核必須提供數(shù)據(jù)結(jié)構(gòu),以建立虛擬地址空間的區(qū)域和相關(guān)數(shù)據(jù)所在位置之間的關(guān)聯(lián),linux軟件系統(tǒng)多級頁表映射機(jī)制 Linux內(nèi)存映射指的是什么 注:上圖中的最右側(cè)page,代表軟件層面的頁幀率,并非真正的物理內(nèi)存。真正的物理內(nèi)存也會分頁,名稱為頁框,頁幀到頁框的轉(zhuǎn)換則是有MMU自動完成的。以下討論均不考慮MMU(MMU完成的工作作為黑箱看待)。

二. Linux的頁表實現(xiàn)

一級頁表

一個32位邏輯地址空間的計算機(jī)系統(tǒng),總地址4G字節(jié),頁大小為4KB,則有4G/4K = 1M個頁,那么頁表中需要存放1M個條目(每個條目代表一個頁)。假設(shè)每個條目占4B,則需要4M字節(jié)內(nèi)存來存放頁表。 每個進(jìn)程都需要管理所有4G內(nèi)存,所以每個進(jìn)程都要4M來存放頁表,極其浪費。

二級頁表

Linux內(nèi)存映射指的是什么 虛擬地址到物理頁地址的轉(zhuǎn)換過程如下:

  • 結(jié)合在CR3寄存器中存放的頁目錄(page directory, PGD)的這一頁的物理地址,再加上從虛擬地址中抽出高10位叫做頁目錄表項(內(nèi)核也稱這為pgd)的部分作為偏移, 即定位到可以描述該地址的pgd;

  • 從該pgd中可以獲取可以描述該地址的頁表的物理地址,再加上從虛擬地址中抽取中間10位作為偏移, 即定位到可以描述該地址的pte;

  • 在這個pte中即可獲取該地址對應(yīng)的頁的物理地址, 加上從虛擬地址中抽取的最后12位,即形成該頁的頁內(nèi)偏移, 即可最終完成從虛擬地址到物理地址的轉(zhuǎn)換。

其中 虛擬地址的組成: DIRECTORY [22:31] 可表示1024個頁目錄(PGD) TABLE[12:21] 可表示1024個頁表(PTE) OFFSET[22:31] 可表示4096個物理內(nèi)存 因此最大映射物理內(nèi)存大小為 102410244096 = 4G/Byte

三級頁表

當(dāng)X86引入物理地址擴(kuò)展(Pisycal Addrress Extension, PAE)后,可以支持大于4G的物理內(nèi)存(36位),但虛擬地址依然是32位,原先的頁表項不適用,它實際多4 bytes被擴(kuò)充到8 bytes,這意味著,每一頁現(xiàn)在能存放的pte數(shù)目從1024變成512了(4k/8)。相應(yīng)地,頁表層級發(fā)生了變化,Linus新增加了一個層級,叫做頁中間目錄(page middle directory, PMD) 辦法是針對使用2級頁表的架構(gòu),把PMD抽象掉,即虛設(shè)一個PMD表項。這樣在page table walk過程中,PGD本直接指向PTE的,現(xiàn)在不了,指向一個虛擬的PMD,然后再由PMD指向PTE。這種抽象保持了代碼結(jié)構(gòu)的統(tǒng)一。 該方法其實就是在原有虛擬地址組成中的幾位作為PMD索引: |31| —–PGD—–|29|—–PMD—–| 20|—–PTE—–|11|—-OFFSET|—–|0|

四級頁表

硬件在發(fā)展,3級頁表很快又捉襟見肘了,原因是64位CPU出現(xiàn)了, 比如X86_64, 它的硬件是實實在在支持4級頁表的。它支持48位的虛擬地址空間(不過Linux內(nèi)核最開始只使用47位)。如下: Linux內(nèi)存映射指的是什么 需注意軟件的頁表映射依賴于硬件所支持的映射級別,目前ARM64支持2/3/4 級映射,假如ARM配置的映射級別為3級,那么linux的映射表中 PGD=PUD,即實際為三級映射。

文件索引節(jié)點

設(shè)文件索引節(jié)點中有7 個地址項,其中4 個地址項是直接地址索引,2 個地址項是一級間 接地址索引,1 個地址項是二級間接地址索引,每個地址項大小為4B,若磁盤索引塊和磁盤數(shù)據(jù)塊 大小均為256B,請計算可表示的單個文件最大長度。

每個索引塊上可以存放的索引項為256B/4B=26 =64個。直接索引的數(shù)據(jù)塊有4 塊;兩個一級 間接索引指向的數(shù)據(jù)塊有2×26=27 = 128個;一個二級間接索引指向的數(shù)據(jù)塊有1×26×26=212 個。所以單個文件最大可以有4+27+212(=4228)個數(shù)據(jù)塊。 文件大小為 4228×256B=(4+27+212)×28=33KB+1MB=1057KB

三. 虛擬空間分布

linux將整個虛擬空間分為用戶空間與內(nèi)核空間,且每個用戶進(jìn)程均占用一份完整的用戶虛擬空間 Linux內(nèi)存映射指的是什么 上圖為64位系統(tǒng)的虛擬地址空間分布用戶空間大小為512G,32位系統(tǒng)用戶空間結(jié)束地址為0xc0000000,大小為3G.

用戶空間

用戶虛擬地址空間大小為3G,每個應(yīng)用程序均獨占完整的3G虛擬空間。這里應(yīng)該可以看出,真是的物理內(nèi)存不可能等于虛擬內(nèi)存,因為同一時間會有眾多進(jìn)程運行,每一進(jìn)程均擁有3G內(nèi)存,而實際物理內(nèi)存不可能滿足所有進(jìn)程的需求。此時上面說到的內(nèi)存映射便發(fā)揮了作用。

  • 一般而言,一個進(jìn)程雖然擁有3G虛擬內(nèi)存訪問權(quán)限,但是實際需要的內(nèi)存可能很少,另外一個進(jìn)程大部分的主體,比如代碼段,只讀數(shù)據(jù)段等并不需要實時駐留在內(nèi)存中,因為這些內(nèi)容在大多數(shù)時間是“死的”。因此內(nèi)存映射的第一個作用就是: 只會將某一進(jìn)程此刻需要的內(nèi)存大小映射到物理內(nèi)存,其它暫時不需要的內(nèi)容交換到硬盤存儲即可。當(dāng)進(jìn)程需要使用在硬盤中的內(nèi)容或者需要動態(tài)申請內(nèi)存時,操作系統(tǒng)會利用缺頁操作,觸發(fā)一次內(nèi)存映射,將另外的物理內(nèi)存映射進(jìn)虛擬內(nèi)存,供程序使用,這樣對于進(jìn)程而言,則認(rèn)為內(nèi)存總是夠用的。

  • 各個進(jìn)程均擁有3G虛擬內(nèi)存,那么操作系統(tǒng)是如何做到各進(jìn)程所使用的實際物理內(nèi)存不會互相占用呢?實際上,各個進(jìn)程均有自己的內(nèi)存映射表。任意一個時刻,在一個CPU上只有一個進(jìn)程在運行。所以對于此CPU來講,在這一時刻,整個系統(tǒng)只存在一個4GB的虛擬地址空間,這個虛擬地址空間是面向此進(jìn)程的。當(dāng)進(jìn)程發(fā)生切換的時候,虛擬地址空間也隨著切換。由此可以看出,每個進(jìn)程都有自己的虛擬地址空間,只有此進(jìn)程運行的時候,其虛擬地址空間才被運行它的CPU所知。在其它時刻,其虛擬地址空間對于CPU來說,是不可知的。所以盡管每個進(jìn)程都可以有4 GB的虛擬地址空間,但在CPU眼中,只有一個虛擬地址空間存在。虛擬地址空間的變化,隨著進(jìn)程切換而變化。

內(nèi)核空間

Linux內(nèi)存映射指的是什么 內(nèi)核空間具有相對獨立的特性。即內(nèi)核的虛擬地址空間范圍是自己獨有的,不與任何用戶進(jìn)程共享(內(nèi)核實質(zhì)也是一個進(jìn)程)。這樣可保證內(nèi)核空間的安全性。但是由于內(nèi)核虛擬地址空間較小0xc000000~0xFFFFFFFF 僅有1G大小,這一大小往往小于實際的物理內(nèi)存,因此內(nèi)核空間需要額外的方式來訪問到所有物理內(nèi)存。

Linux內(nèi)存映射指的是什么 由上圖可知,內(nèi)核空間又分為三大部分: ZONE_DMA :0XC000000 + 16M 線性映射區(qū) 該區(qū)域的物理頁面專門供I/O設(shè)備的DMA使用。之所以需要單獨管理DMA的物理頁面,是因為DMA使用物理地址訪問內(nèi)存,不經(jīng)過MMU,并且需要連續(xù)的緩沖區(qū),所以為了能夠提供物理上連續(xù)的緩沖區(qū),必須從物理地址空間專門劃分一段區(qū)域用于DMA。例如dma_alloc_coherent函數(shù)獲取的內(nèi)存就是ZONE_DMA內(nèi)存 ZONE_NORMAL :0XC100000 + 880M 線性映射區(qū) 該區(qū)域的物理頁面是內(nèi)核能夠直接使用的,比如內(nèi)核程序中代碼段、全局變量以及kmalloc獲取的堆內(nèi)存等。從此處獲取內(nèi)存一般是連續(xù)的,但是不能太大。 ZONE_HIGHMEM :0xF8000000 + 28M 該區(qū)域比較負(fù)責(zé)又可細(xì)分為三部分:

  • 非連續(xù)內(nèi)存區(qū) 非連續(xù)內(nèi)存分配是指將物理地址不連續(xù)的頁框映射到線性地址連續(xù)的線性地址空間,主要應(yīng)用于大容量的內(nèi)存分配。采用這種方式分配內(nèi)存的主要優(yōu)點是避免了外部碎片,而缺點是必須打亂內(nèi)核頁表,而且訪問速度較連續(xù)分配的物理頁框慢。函數(shù)vmalloc即是通過該部分虛擬地址來映射物理內(nèi)存。

  • 永久內(nèi)核映射區(qū) 如果是通過 alloc_page() 獲得了高端內(nèi)存對應(yīng)的 page,如何給它找個線性空間? 內(nèi)核專門為此留出一塊線性空間,從 PKMAP_BASE 到 FIXADDR_START ,用于映射高端內(nèi)存。在 2.6內(nèi)核上,這個地址范圍是 4G-8M 到 4G-4M 之間。這個空間起叫”內(nèi)核永久映射空間”或者”永久內(nèi)核映射空間”。這個空間和其它空間使用同樣的頁目錄表,對于內(nèi)核來說,就是 swapper_pg_dir,對普通進(jìn)程來說,通過 CR3 寄存器指向。通常情況下,這個空間是 4M 大小,因此僅僅需要一個頁表即可,內(nèi)核通過來 pkmap_page_table 尋找這個頁表。通過 kmap(),可以把一個 page 映射到這個空間來。由于這個空間是 4M 大小,最多能同時映射 1024 個 page。因此,對于不使用的的 page,及應(yīng)該時從這個空間釋放掉(也就是解除映射關(guān)系),通過 kunmap() ,可以把一個 page 對應(yīng)的線性地址從這個空間釋放出來。

  • 臨時固定映射區(qū) fixmap和pkmap的最大區(qū)別是fixmap不會被阻塞,因此可以在中斷上下文中使用。實際上每次fixmap都是可以成功執(zhí)行映射的,原因是fixmap不會動態(tài)申請映射,它是以固定映射的方式進(jìn)行的,如果我們傳入了一個映射type,它就肯定會執(zhí)行對應(yīng)的映射,而把之前的映射沖刷掉,因此fixmap的映射關(guān)系是靠內(nèi)核代碼來進(jìn)行維護(hù)的,所以內(nèi)核需要保證同一個映射區(qū)不被重復(fù)使用,從而出現(xiàn)錯誤。

前 面我們解釋了高端內(nèi)存的由來。 Linux將內(nèi)核地址空間劃分為三部分ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM,高端內(nèi)存HIGH_MEM地址空間范圍為 0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB)。那么如內(nèi)核是如何借助128MB高端內(nèi)存地址空間是如何實現(xiàn)訪問可以所有物理內(nèi)存?

當(dāng)內(nèi)核想訪問高于896MB物理地址內(nèi)存時,從0xF8000000 ~ 0xFFFFFFFF地址空間范圍內(nèi)找一段相應(yīng)大小空閑的邏輯地址空間,借用一會。借用這段邏輯地址空間,建立映射到想訪問的那段物理內(nèi)存(即填充內(nèi)核PTE頁面表),臨時用一會,用完后歸還。這樣別人也可以借用這段地址空間訪問其他物理內(nèi)存,實現(xiàn)了使用有限的地址空間,訪問所有所有物理內(nèi)存。 例 如內(nèi)核想訪問2G開始的一段大小為1MB的物理內(nèi)存,即物理地址范圍為0×80000000 ~ 0x800FFFFF。訪問之前先找到一段1MB大小的空閑地址空間,假設(shè)找到的空閑地址空間為0xF8700000 ~ 0xF87FFFFF,用這1MB的邏輯地址空間映射到物理地址空間0×80000000 ~ 0x800FFFFF的內(nèi)存。

四.內(nèi)核空間與用戶空間交互

系統(tǒng)調(diào)用

系統(tǒng)調(diào)用的可實現(xiàn)進(jìn)程由用戶態(tài)切換至內(nèi)核態(tài)。但是通過上面分析已經(jīng)了解,用戶進(jìn)程與內(nèi)核進(jìn)程相互分離的,那么在進(jìn)行系統(tǒng)調(diào)用時參數(shù)傳遞是如何實現(xiàn)的呢? 解決這一辦法的思路是:數(shù)據(jù)拷貝。因為無論是用戶地址空間還是內(nèi)核地址空間均為虛擬地址空間,因此在做參數(shù)傳遞時,只需要將在用戶地址空間存放的參數(shù)拷貝至內(nèi)核地址空間即可。完成拷貝的函數(shù)就是大家熟知的copy_from_user() &copy_to_user(). Linux內(nèi)存映射指的是什么 上圖表示某一用戶進(jìn)程將一組數(shù)據(jù)傳遞給內(nèi)核,內(nèi)核經(jīng)過處理后將數(shù)據(jù)返回至用戶進(jìn)程。該過程涉及到數(shù)據(jù)在兩塊內(nèi)存區(qū)域的轉(zhuǎn)移:用戶虛擬內(nèi)存地址對應(yīng)的物理內(nèi)存與內(nèi)核虛擬內(nèi)存地址對應(yīng)的物理內(nèi)存。

mmap函數(shù)

函數(shù)原型:

 void *mmap{
  void *addr; //映射區(qū)首地址,傳NULL
  size_t length; //映射區(qū)的大小
   //會自動調(diào)為4k的整數(shù)倍
   //不能為0
   //一般文件多大,length就指定多大
  int prot; //映射區(qū)權(quán)限
   //PROT_READ 映射區(qū)比必須要有讀權(quán)限
   //PROT_WRITE
   //PROT_READ | PROT_WRITE
  int flags; //標(biāo)志位參數(shù)
   //MAP_SHARED 修改了內(nèi)存數(shù)據(jù)會同步到磁盤
   //MAP_PRIVATE 修改了內(nèi)存數(shù)據(jù)不會同步到磁盤
  int fd; //要映射的文件對應(yīng)的fd
  off_t offset;  //映射文件的偏移量,從文件的哪里開始操作
   //映射的時候文件指針的偏移量
   //必須是4k的整數(shù)倍
   //一般設(shè)置為0

用戶空間mmap調(diào)用 用戶空間讀寫文件時,需經(jīng)過內(nèi)核,數(shù)據(jù)拷貝多了一次。通過mmap函數(shù),可以建立用戶虛擬空間到文件所在物理頁的直接映射建立該映射后,可以像直接操作內(nèi)存一樣讀寫文件(比如讀寫數(shù)組),減少一次用戶到內(nèi)核的數(shù)據(jù)拷貝。

     fd = open(argv[1], O_RDWR);
     /* 將文件映射至進(jìn)程的地址空間 ,mmaped 為文件所在物理頁對應(yīng)的虛擬內(nèi)存地址*/
     mmaped = (char *)mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
     /* 映射完后, 關(guān)閉文件也可以操縱內(nèi)存 */
     close(fd);
     mmaped[5] = '$';
     msync(mmaped, sb.st_size, MS_SYNC)
 1234567

Linux內(nèi)存映射指的是什么 內(nèi)核空間mmap實現(xiàn) 直接打開一個磁盤文件,實際是調(diào)用了文件系統(tǒng)提供的mmap版本。但是對于開發(fā)linux驅(qū)動的工程師來說,需自己實現(xiàn)對應(yīng)設(shè)備文件的mmap函數(shù),例如framebuffer這種設(shè)備需要較高頻率大數(shù)據(jù)讀寫,因此不能容忍內(nèi)核空間到用戶空間的數(shù)據(jù)拷貝,因此驅(qū)動需開發(fā)自己的mmap函數(shù)供用戶進(jìn)程調(diào)用,驅(qū)動mmap實現(xiàn)流大致為:

  1. 通過kmalloc, get_free_pages, vmalloc等分配一段虛擬地址

  2. 如果是使用kmalloc, get_free_pages分配的虛擬地址,那么使用virt_to_phys()將其轉(zhuǎn)化為物理地址,再將得到的物理地址通過”phys>>PAGE_SHIFT”獲取其對應(yīng)的物理頁面幀號?;蛘咧苯邮褂胿irt_to_page從虛擬地址獲取得到對應(yīng)的物理頁面幀號。 如果是使用vmalloc分配的虛擬地址,那么使用vmalloc_to_pfn獲取虛擬地址對應(yīng)的物理頁面的幀號。

  3. 對每個頁面調(diào)用SetPageReserved()標(biāo)記為保留才可以。

  4. 通過remap_pfn_range為物理頁面的幀號建立頁表,并映射到用戶空間。

      //內(nèi)存分配
      buffer = (unsigned char *)kmalloc(PAGE_SIZE,GFP_KERNEL);  
      //將該段內(nèi)存設(shè)置為保留
      SetPageReserved(virt_to_page(buffer));
      //得到物理地址
      phys = virt_to_phys(buffer);
      //將用戶空間的一個vma虛擬內(nèi)存區(qū)映射到以page開始的一段連續(xù)物理頁面上
      remap_pfn_range(vma,
                      vma->vm_start,
                      phys >> PAGE_SHIFT,//第三個參數(shù)是頁幀號,由物理地址右移PAGE_SHIFT得>到
                      vma->vm_end - vma->vm_start,
                      vma->vm_page_prot)
     123456789101112

mmap函數(shù)并非實現(xiàn)用戶空間與內(nèi)核空間的交互,而是用戶空間試圖繞過內(nèi)核空間,直接操作物理頁

四. 完整的linux系統(tǒng)下內(nèi)存分布圖

Linux內(nèi)存映射指的是什么

Linux有哪些版本

Linux的版本有:Deepin、UbuntuKylin、Manjaro、LinuxMint、Ubuntu等版本。其中Deepin是國內(nèi)發(fā)展最好的Linux發(fā)行版之一;UbuntuKylin是基于Ubuntu的衍生發(fā)行版;Manjaro是基于Arch的Linux發(fā)行版;LinuxMint默認(rèn)的Cinnamon桌面類似Windows XP簡單易用;Ubuntu則是以桌面應(yīng)用為主的Linux操作系統(tǒng)。

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI