arm的mmu學習【轉】

轉自:https://blog.csdn.net/qq_16777851/article/details/81074077

1.什麼是mmu

MMU是Memory Management Unit的縮寫,中文名是內存管理單元,它是中央處理器(CPU)中用來管理虛擬存儲器、物理存儲器的控制線路,同時也負責虛擬地址映射爲物理地址,以及提供硬件機制的內存訪問授權,多用戶多進程操作系統。

物理地址:(英語:physical address),也叫實地址(real address)、二進制地址(binary address),它是在地址總線上,以電子形式存在的,使得數據總線可以訪問主存的某個特定存儲單元的內存地址

虛擬地址:虛擬地址是相對於物理地址來說的。虛擬地址的提出,主要是爲了解決在操作系統中,多線程內存地址重複,大進程在小內存運行等問題 , 在32位系統中,虛擬地址空間中有4G,在操作系統中程序中使用的都是虛擬地址

2.mmu有什麼作用

簡單的說,mm的作用有兩點,地址翻譯和內存保護。

在處理器上我們會運行一個操作系統,如linux,windows等,用戶編寫的源程序,需要經過編譯,鏈接,生成可執行程序,人後被操作系統加載執行。在鏈接的時候同chan常我們要指定一個鏈接腳本,鏈接腳本的作用有很多,其中一個的作用是控制可執行文件的section的符號的內存佈局,也就是控制ke'z可執行程序將來要在內存中哪裏放置。操作系統會按照可執行程序的要求將其加載到內存的對應地址執行。假如用戶A編寫的應用程序的鏈接地址範圍是0x100-0x200,用戶B編寫的應用程序的鏈接地址範圍是0x100-0x200,這是很有可能的。因爲給操作系統提供應用程序的開發者很多,不可能爲每個開發者限定使用那些內存。這樣,執行程序A的時候就不能執行程序B,執行程序B的時候就不能執行程序A,因爲它們執行時會覆蓋對方內存中的程序。爲了解決這個問題,必須引入虛擬地址,爲此操作系統和處理器都做了處理,添加了mmu,讓其進行地址翻譯。在程序載入內存的時候,操作系統會爲其建立地址翻譯表,處理器執行不同應用程序的時候,使用不同的地址翻譯表。如下圖所示。

   ProgramA被加載到物理地址地址0x500-0x600處,ProgramB被加載到物理地址0x700-0x800處,同時建立了各自的地址翻譯表,當處理器要執行ProgramB時,會使用ProgramB對應的地址翻譯表,比如讀取ProgramB地址0x100處的指令,那麼經過地址翻譯表可知0x100對應實際內存的0x700處,所以實際讀取的就是0x700處的指令。同樣的,當處理器要執行ProgramA時,會使用ProgramA對應的地址翻譯表,這樣就避免了之前提到的內存衝突問題,有了MMU的支持,操作系統就可以輕鬆實現多任務了。

上圖CPU給出的地址稱之爲虛擬地址,經過MMU翻譯後的地址稱之爲物理地址。

 MMU的地址翻譯功能還可以爲用戶提供比實際大得多的內存空間。用戶在編寫程序的時候並不知道運行該程序的計算機內存大小,如果在鏈接的時候指定程序被加載到地址Addr處,而運行該程序的計算機內存小於Addr,那麼程序就無法執行,有了MMU後,程序員就不用關心實際內存大小,可以認爲內存大小就是“2^指令地址寬度”。MMU會將超過實際內存的虛擬地址翻譯爲物理地址進行訪問。

  地址翻譯表存儲在內存中,如果採用圖10.1中的方式:地址翻譯表的表項是一個虛擬地址對應一個物理地址,那麼會佔用太多的內存空間,爲此,需要修改翻譯方式,常用的有三種:頁式、段式、段頁式,這也是三種不同的內存管理方式。

頁式內存管理將虛擬內存、物理內存空間劃分爲大小固定的塊,每一塊稱之爲一頁,以頁爲單位來分配、管理、保護內存。此時MMU中的地址翻譯表稱爲頁表(Page Table),每個任務或進程對應一個頁表,頁表由若干個頁表項(PTE:Page Table Entry)組成,每個頁表項對應一個虛頁,內含有關地址翻譯的信息和一些控制信息。在頁式內存管理方式中地址由頁號和頁內位移兩部分組成,其地址翻譯方式如圖10.2所示。

使用虛擬地址中的虛頁號查詢頁表得到對應的物理頁號,然後與虛擬地址中的頁內位移組成物理地址。比如:頁大小是256字節,虛擬地址是0x104,可知對應的虛頁號是0x1,頁內位移是0x4,假如通過頁表翻譯得到的對應物理頁號是0x7,那麼0x104對應的物理地址就是0x704。使用頁表方式進行地址翻譯可以有效減少地址翻譯表佔用的內存空間,還是以圖10.1爲例,頁大小是256字節,此時每個程序對應的頁表就只有兩項,如圖10.3所示。

      段式內存管理將虛擬內存、物理內存空間劃分爲段進行管理,段的大小取決於程序的邏輯結構,可長可短,一般將一個具有共同屬性的程序代碼和數據定義在一個段中。每個任務和進程對應一個段表(Section Table),段表由若干個段表項(STE:Section Table Entry)組成,內含地址映像信息(段基址和段長度)等內容。在段式虛擬存儲器中,地址分爲段號、段內位移兩部分,使用段表進行地址翻譯的過程與使用頁表進行地址翻譯的過程是相似的。

      段頁式內存管理是在內存分段的基礎上再分頁,即每段分成若干個固定大小的頁。每個任務或進程對應有一個段表,每段對應有自己的頁表。在訪問存儲器時,由CPU經頁表對段內存儲單元進行尋址。

  2、內存保護

     內存保護也叫權限管理,除了具有地址翻譯的功能外,還提供了內存保護功能。採用頁式內存管理時可以提供頁粒度級別的保護,允許對單一內存頁設置某一類用戶的讀、寫、執行權限,比如:一個頁中存儲代碼,並且該代碼不允許在用戶模式下執行,那麼可以設置該頁的保護屬性,這樣當處理器在用戶模式下要求執行該頁的代碼時,MMU會檢測到並觸發異常,從而實現對代碼的保護。特別是在處理應用程序時,如果一個應用程序寫的比較爛,出現了指針越界或棧溢出,程序跑飛等情況,因爲不能訪問別的程序的地址,所以不會影響到別的應用程序的運行。比如在操作系統下,應用程序不能訪問寄存器,而操作系統可以。比如應用程序的只讀數據段不能被寫,否則會發生段錯誤。

3、大容量app在小資源系統運行

    在嵌入式系統中,假如內存容量只有256M大,而應用程序卻有1G大時,通常一個程序中,程序執行的比較多的是順序指令,所以在運行1G的程序時,操作系統會先加載一小部分到內存中,當執行完這一部分或發生跳轉發現內存中沒有要跳轉地址的指令時,操作系統再加載需要跳轉部分的程序到其鏈接地址(虛擬地址),加載完後再繼續執行。內次加載程序,都需要建立一個動態的地址映射表。當物理內存加載滿後,操作系統會選擇性的將最早之前加載入物理內存的程序置換到外部flash等存儲器中,再加載需要用到的一塊程序。因爲置換需要時間,所以當使用存儲容量較小內存的嵌入式系統後,讓其運行大程序,使用可能會有一定的卡頓現象。

 

3.arm的mmu

下圖中是arm支持的幾種頁表大小一級每種頁表可以管理的內存單元數量。

通常使用段式頁表作爲一級頁表,使用頁式式頁表作爲二級頁表。

 

在關閉了子頁(subpages)功能後可以使用下面三種作爲一級頁表。

下圖分別是超級段,和段以及粗頁表的描述。

粗頁以1k爲單位管理頁表,段以1M爲單位管理頁表,超級短以16M爲單位管理頁表。

上面三者都可以作爲一級頁表使用。

從上圖我們可以看到,超級頁表可以管理40位數據寬度也就是1T容量的內存。主要是爲64位系統而發明的。

結合上下圖的描述我們可以看到,supersection和section是通過bit18來區分的。

段和頁的區分是有bit【0,1】來區分的。

1.下面是超級段和段的區別。

 

在使能子頁(subpages)功能後,可以使用下面二種作爲一級頁表。

 

在說明一級頁錶轉換之前我們先引入一個概念。

首先,我們要分清ARM CPU上的三個地址:虛擬地址(VA,Virtual Address)、變換後的虛擬地址(MVA,Modified Virtual Address)、物理地址(PA,Physical Address)

   啓動MMU後,CPU覈對外發出虛擬地址VA,VA被轉換爲MVA供MMU使用,在這裏MVA被轉換爲PA;最後通過PA讀寫實際設備 

    MMU的作用就是負責虛擬地址(virtual address)轉化成物理地址(physical address)。 32位的CPU的虛擬地址空間達到4GB,在一級頁表中使用4096個描述符來表示這4GB的空間,每個描述符代表1M的虛擬地址,要麼存儲了它的對應物理地址的起始地址,要麼存儲了下一級頁表的地址。使用MVA[31:20]來索引一級頁表(4096個描述符)(因爲用MVA的高12位來索引,因此大小爲 2^12 = 4096)

    由協處理器CP15中的寄存器C2(高18位,即[31:14]爲轉換表基地址,低14位爲0)用來存放一級轉換表基地址,指向2^14=16KB整除的存儲器即16K對齊,這個存儲區稱爲一級轉換表;MVA的高12位,即位[31:20]作爲一級轉換表的地址索引,因此一級轉換表具有2^12=4096項,每一項的地址爲32位,最高的18位[31:14]爲寄存器C2的高18位,中間12位爲MVA的高12位[31:20],最低2位爲0b00。每一項的內容稱爲一個描述符,在段(Section)下,一級描述符的高12位爲大小爲1MB的段基地址,段內地址(偏移地址)爲MVA的低20位,即段內每個存儲器的地址是這樣組成:高12位爲一級描述符的高12位,低20位MVA的低20位。這樣,藉助於寄存器C2和一級描述符,將一個MVA轉換成一個PA。(在這裏一定要注意:MVA的高12位是用來索引4096個項的,然後使用項的內容(即描述符)的高12位爲段的高12位,類似於指針裏面存放地址,4096項類似指針,描述符類似指針裏面的內容)

  

下圖是使用一級頁表後,地址的轉換過程圖。(以段式頁表爲例)

從上往下看:

translation tabe base:簡稱ttb,稱爲轉換表基址,存放在cp15的c2寄存器的高18位,低12位爲0。所以將來我們寫程序存放ttb的基地址一定要以16kb對齊。

modified virtual address:簡稱mva,稱爲轉換後的虛擬地址(即在32bit系統中具有4G訪問空間的虛擬地址),它的高12bit總共4096個項,用來作爲該虛擬地址在ttb中的索引。它的低20位,是作爲將來找到對應的物理內存的偏移。其本省也是在虛擬內存中的偏移。

address of first-level descriptor:一級地址描述符,它是結合ttb,以及偏移量mva,找到的具體的頁表。即具體段(section)在那個位置。

first-level descriptor:以及頁表描述符,上一步既然知道了是存放在一級頁表的哪個位置了,直接取出其中的高12位,即找到了物理地址所在的段。

physcical address:既然上一步已經找到了物理地址所在的段,那麼只需要加上低20位的段內偏移即找到了具體的那個物理地址了。(偏移在物理地址和虛擬地址中是一樣的,區別只是在段地址)

2.粗頁表

一級頁表做粗頁表用的比較少,這裏就不分析了

3.二級頁表

二級頁表主要有兩種

大頁表,每頁管理64k

小頁表,每頁管理4k

和一級頁表一樣,使能或不使能subpages分爲兩種情況

前面包括一級頁表都沒有說,頁表沒一項的內容,這裏統一說明一下。

[0-1]用來識別頁表類型,比如段式,大頁,小頁等

[2-3]用來識別是否使能cache和write buffer

TEX是擴展的類型字段。

[4-5]是用來做權限管理的

其中S R是在cp15的c1協處理器中,用來做系統保護和rom保護的。

 

nG   S   XN

其餘的就是不同大小的頁的基地址。 

 

 

以64KB管理的二級粗頁表的映射形式。

以4KB管理的二級粗頁表的映射形式。

 

4.編程實踐

下面使用上面講的最詳細的段頁表爲例,在裸機的情況下開啓MMU,實現虛擬地址映射。

 在現代處理器中,爲了使內存的速度跟得上CPU的速度,通常在芯片內部做了緩存(cache)。在啓動了cache後,程序的運行效率會極大的提高。

arm中又把cache分爲指令cache,又稱(icache),和數據cache,又稱(dcache)。

其中icache可以隨時開啓,隨時關閉,但dcache必須在開啓了MMU後,才能啓動。

在啓動cache後,arm其實才可以稱爲哈佛結構(數據指令分開)

否則,在不開啓的情況下,其實還是馮洛伊曼結構。

 

我的ddr的地址範圍是0x3000000~0x4fffffff

爲了驗證我的MMU確實開啓了,所以把程序的鏈接地址改爲了0xB0000000,同時把0xB0000000起始的1M空間(我的裸機程序很小,遠小於1M)映射到了0x30000000

首先看一下我的鏈接腳本

  1.  
    SECTIONS
  2.  
    {
  3.  
    . = 0xb0000000;
  4.  
    __code_start = .;
  5.  
    . = ALIGN(4);
  6.  
    .text :
  7.  
    {
  8.  
    start.o
  9.  
    *(.text)
  10.  
    }
  11.  
    . = ALIGN(4);
  12.  
    .rodata :
  13.  
    {
  14.  
    *(.rodata)
  15.  
    }
  16.  
    . = ALIGN(4);
  17.  
    .data :
  18.  
    {
  19.  
    data_load_add = LOADADDR(.data);
  20.  
    data_start = .;
  21.  
    *(.data)
  22.  
    data_end = .;
  23.  
    }
  24.  
    . = ALIGN(4);
  25.  
    .bss :
  26.  
    {
  27.  
    bss_start = .;
  28.  
    *(.bss) *(.COMMON)
  29.  
    bss_end = .;
  30.  
    }
  31.  
    }

 

接下來是頁表的建立。

因爲我在裸機中並沒有使用很多東西,所以映射的不是所有4G空間,只映射了我用到的。

  1.  
     
  2.  
    #define MMU_SECTION_AP (0x3<<10)
  3.  
    #define MMU_SECTION_DOMAIN (0<<5)
  4.  
    #define MMU_SECTION_NCNB (0<<2)
  5.  
    #define MMU_SECTION_ECEB (0x3<<2)
  6.  
    #define MMU_SECTION_TYPE ((1<<4)|(1<<1))
  7.  
     
  8.  
    #define MMU_SECTION_IO (MMU_SECTION_AP|MMU_SECTION_DOMAIN|MMU_SECTION_NCNB|MMU_SECTION_TYPE)
  9.  
    #define MMU_SECTION_MEM (MMU_SECTION_AP|MMU_SECTION_DOMAIN|MMU_SECTION_ECEB|MMU_SECTION_TYPE)
  10.  
     
  11.  
     
  12.  
    #define MMU_IO 1
  13.  
    #define MMU_MEM 0
  14.  
     
  15.  
     
  16.  
     
  17.  
    /* 虛擬地址向物理地址映射 */
  18.  
    static void create_tlb(unsigned int *ttb,unsigned int va,unsigned int pa, int io)
  19.  
    {
  20.  
    int index;
  21.  
     
  22.  
    index = va / 0x100000;
  23.  
     
  24.  
    if(io)
  25.  
    ttb[index] = (pa & 0xfff00000) | MMU_SECTION_IO;
  26.  
    else
  27.  
    ttb[index] = (pa & 0xfff00000) | MMU_SECTION_MEM;
  28.  
    }
  29.  
     
  30.  
     
  31.  
    /* 創建一級頁表
  32.  
    * VA PA CB
  33.  
    * 0 0 11
  34.  
    *
  35.  
    * 512M
  36.  
    * 0x30000000 0x30000000 11
  37.  
    * ......
  38.  
    * 0x4ff00000 0x4ff00000 11
  39.  
    *
  40.  
    * 0xd0000000 0xd0000000 11
  41.  
    *
  42.  
    * SFR
  43.  
    * 0xe0000000 0xe0000000 00
  44.  
    * ......
  45.  
    * 0xfff00000 0xfff00000 00
  46.  
    *
  47.  
    * framebuffer
  48.  
    * 0x40000000 0x40000000 00
  49.  
    *
  50.  
    * link address
  51.  
    * 0xb0000000 0x30000000 11
  52.  
    */
  53.  
     
  54.  
    /* 創建一個一級的段頁表 */
  55.  
    void create_page_table(void)
  56.  
    {
  57.  
    /* 頁表在哪 0x4f000000 16k對齊 */
  58.  
    unsigned int *ttb = (unsigned int *)0x4f000000;
  59.  
    unsigned int va,pa;
  60.  
     
  61.  
    /* 1.irom */
  62.  
    create_tlb(ttb,0,0,MMU_MEM);
  63.  
     
  64.  
    /* 2.sdram 512M*/
  65.  
    va = 0x30000000;
  66.  
    pa = 0x30000000;
  67.  
    for( va = 0x30000000; va < 0x4fffffff; va += 0x100000 )
  68.  
    {
  69.  
    create_tlb(ttb,va,pa, MMU_MEM);
  70.  
    pa += 0x100000;
  71.  
    }
  72.  
     
  73.  
    /* 3.irom/iram */
  74.  
    create_tlb(ttb,0xd0000000,0xd0000000, MMU_MEM);
  75.  
     
  76.  
    /* 4.sfr */
  77.  
    va = 0xe0000000;
  78.  
    pa = 0xe0000000;
  79.  
    for( va = 0xe0000000; va < 0xfff00000; va += 0x100000)
  80.  
    {
  81.  
    create_tlb(ttb,va,pa, MMU_IO);
  82.  
    pa += 0x100000;
  83.  
    }
  84.  
     
  85.  
    /* 5.framebuffer */
  86.  
    create_tlb(ttb, 0x40000000,0x40000000, MMU_IO);
  87.  
     
  88.  
    /* 6. link address */
  89.  
    create_tlb(ttb,0xb0000000,0x30000000, MMU_MEM);
  90.  
    }
  91.  
     
  92.  
     
  93.  
     

 

下面是初始化部分(bootloader)

  1.  
     
  2.  
    __reset_exception:
  3.  
     
  4.  
    /* 開發板制鎖*/
  5.  
    ldr r0, = 0xe010e81c
  6.  
    ldr r1, = 0x301
  7.  
    str r1, [r0]
  8.  
     
  9.  
    /* 關閉看門狗 */
  10.  
    ldr r0, = 0xe2700000
  11.  
    mov r1, #0
  12.  
    str r1, [r0]
  13.  
     
  14.  
    /* 下面有調用c函數設置SVC棧地址 */
  15.  
    ldr sp, = 0xd0037d80
  16.  
     
  17.  
    /* 啓動icache */
  18.  
    bl enable_icache
  19.  
     
  20.  
    /* 設置時鐘 */
  21.  
    bl init_clock
  22.  
     
  23.  
    /* 初始化DDR */
  24.  
    bl sdram_init
  25.  
     
  26.  
    /* 創建頁表 */
  27.  
    bl create_page_table
  28.  
     
  29.  
    /* 使能mmu */
  30.  
    bl enable_mmu
  31.  
     
  32.  
    /* 代碼重定位 */
  33.  
    bl copy2sdram
  34.  
     
  35.  
    /* 清bss段 */
  36.  
    bl clear_bss
  37.  
     
  38.  
    /* 從iram跳轉到ddr */
  39.  
    ldr pc, = sdram
  40.  
    sdram:
  41.  
    bl uart0_init
  42.  
     
  43.  
    /* 開irq中斷 */
  44.  
    mrs r0, cpsr
  45.  
    bic r0, r0, #1<<7
  46.  
    msr cpsr, r0
  47.  
     
  48.  
    ldr sp, = 0x45000000
  49.  
     
  50.  
    /* 調用main函數 */
  51.  
    bl main
  52.  
     
  53.  
    b .
  54.  
     
  55.  
    enable_icache:
  56.  
    mrc p15, 0, r1, c1, c0, 0 @Read Control Regist
  57.  
    orr r1, r1,#(1<<12) @enable instructon cache
  58.  
    //bic r1, r1,#(1<<12)
  59.  
    mcr p15, 0, r1, c1, c0, 0
  60.  
    mov pc, lr
  61.  
     
  62.  
    enable_mmu:
  63.  
    /* translation table base write cp5 */
  64.  
    ldr r1, = 0x4f000000
  65.  
    mrc p15, 0, r2, c2, c0, 0 @ Read Translation Table Base Register
  66.  
    orr r2, r2, r1
  67.  
    mcr p15, 0, r2, c2, c0, 0 @ Write Translation Table Base Register
  68.  
     
  69.  
    /* set domain 0xffffffff */
  70.  
    ldr r0, = 0xffffffff
  71.  
    mcr p15, 0, r0, c3, c0, 0 @ Read Domain Access Control Register
  72.  
     
  73.  
     
  74.  
    /* enable i/d canche */
  75.  
    mrc p15, 0, r1, c1, c0, 0 @Read Control Regist
  76.  
    orr r1, r1,#(1<<12) @enable instructon cache
  77.  
    orr r1, r1,#(1<<2) @enable data cache
  78.  
    orr r1, r1,#(1<<0) @enable mmu
  79.  
    mcr p15, 0, r1, c1, c0, 0 @write Control Regist
  80.  
    mov pc, lr

初始化要注意的點:

1.sdram標號之前的代碼都應該是位置無關碼(不能使用全局變量,靜態變量,字符串,初始化過的局部數組等)。

2.因爲頁表放置在ddr中,所以創建頁表必須在ddr初始化之後。

3.因爲我的鏈接地址在0xB0000000,所以代碼重定位時必須要能使用0xB0000000的空間。所以我把啓動mmu放在了,重定位前面。同時開啓MMU時要使用頁表基地址,所以頁表也必須在開啓MMUq前先建立。

 

5.效果

啓動了MMU前,我的刷屏速度大概在每秒幾幀。

開啓了MMU和cache後,我的刷屏速度差不多可以達到每秒二十對幀。

 

有一點要說明的是,我把framebuffer的顯存映射成MEM即可以使用cache和buffer後,刷新速度感覺比映射成IO速度快了一倍。

主要原因是因爲我是一整屏的刷顏色,所以dcache很快就滿了,然後硬件自動把整個dcache刷回內存。所以速度比直接訪問的IO要快,當

每次只刷一小塊部分,速度反而會比IO方式慢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章