Wednesday, August 19, 2015

Memory Management Unit

學習筆記 - Memory Management Unit

MMU特性:


  1. 虛擬地址與物理地址轉換
  2. MMU內存訪問機制
電腦在處理資料時,當應用程式很大,應用程式所要求的內存空間,超過內存的總容量。電腦所採取的作法是分批處理,當前運行程式部分先裝入內存處理,其餘部分程式在用到時,再從硬碟或FLASH等儲存裝置調入,當內存滿時,則將暫時不運行程式部分調入硬碟或FLASH等儲存裝置。如此使得大程式可以在小的內存空間中運行。
另一個情況是,在多執行緒系統中,有多支程式要同時執行,它們要求的內存空間也大於內存的物理空間。
MMU就是藉由特性1.虛擬地址與物理地址轉換機制來實現這樣的功能。

MMU地址轉換工作原理:
細節可參考: MMU工作过程
http://blog.csdn.net/shareCode/article/details/9023029

一般虛擬地址轉換有兩種方式: 1. 固定的數學式轉換,2. 用表格儲存虛擬地址與對應的物理地址(稱作Page table),S3C2440採用Page table,而頁表(Page table)由一個個條目(Entry)所組成,條目亦稱作描述符(Descript)。
S3C2440有四種內存虛擬地址轉換方式,
  1. Fault (無映射)
  2. Coarse Page (粗表),256個Entry,每個Entry 4000Byte大小
  3. Section (),4096個Entry,每個Entry 1MB大小
  4. Fine Page (細表),1024個Entry,每個Entry 4Byte大小
屬於一級頁表。
  1. Coarse Page (粗表)
  2. Fine Page (細表)
可在細分為大頁(64KB)、小頁(4KB)、極小頁(1KB)等轉換方式屬於二級頁表,S3C2440最多用到二級頁表。



虛擬地址轉換示意:

一級頁表
虛擬地址  < == > TTB Base + p1(一級頁表索引,找到對應的段(表)內基地址) +  p2(加上虛擬地址偏移量,找到段(表)內地址)

二級頁表
虛擬地址  < == > TTB Base + p1(一級頁表索引) +  p2(二級頁表索引,找到對應的頁內基地址) + p3(加上虛擬地址偏移量,找到地址)

其中  TTB Base( 寫入協處理器CP15的C2暫存器 

段轉換實例:
ARM920T是一個32bitCPU,它的虛擬位址空間為2^32=4G。而在Section模式,這4G的虛擬空間被分成一個一個稱為段(Section)的單位,每個段的長度是1M 4G的虛擬記憶體總共可以被分成4096個段(1M*4096=4G,因此我們必須用4096個描述符來對這組段進行描述,每個描述符佔用4Byte,故這組描述符的大小為16KB (4byte*4096),4096個描述符構為一個表格,我們稱其為Translation Table.

上圖是段描述符的結構
    Section base address:段基底位址(MVA[31:20] 此描述符低20填0,就是一個1MB物理地       址空間的起始地址,也就是描述符 section 區段的起始地址,MVA[19:0]用來在這1MB空間     中尋址)
    AP: 存取控制位Access Permission
    Domain: 存取控制寄存器的索引。DomainAP配合使用,對存取權限進行檢查
    C:C被置1時為write-through (WT)模式
    B: B被置1時為write-back (WB)模式(C,B兩個位在同一時刻只能有一個被置1

TLB(Translation Lookaside Buffers)作用:
上述MMU地址轉換需要進行查表,大大降低CPU性能,藉由TLB,使用一個高速、容量相對小的儲存器來儲存近期用到的頁表條目(段/大頁/小頁/極小頁描述符),避免每次地址轉換都到主存去查表,可大幅提高性能。

Cache作用:
在CPU與主存間設置一個高速、容量相對小的儲存器,將當前執行的指令、數據調入儲存器中,使得CPU不用再到主存去拿取資料,直接從Cache中拿取,以提高性能。
Cache啟動後有兩種方式,
  1. write through:cpu資料寫入cache也寫入主存,以保證主存數據能同步更新。
  2. write back:數據只寫入cache,不寫入主存,可在cache中設置旗標,標示地址與數據新舊,只有當cache中有新數據或 "clean" 操作時,才將cache中資料寫入主存中。
Cache 兩個操作,
  1. clean:將cache或write buffer中,新數據(修改過,尚未寫入主存),寫入主存。
  2. Invalidate: 使cache不再使用,並不將新數據寫入主存。


MMU內存訪問權限檢查:
MMU可以用來決定一塊內存是否可以進行讀/寫操作。




=============實作===================

MMU實驗:

開啟MMU功能,使用虛擬地址在SDRAM上跑LED實驗,4個LED跑得比SDRAM實驗快,這是由於開啟MMU的CACHE功能。

===知識插曲===
====Makefile 規則======
$@ 擴展成當前規則的目的檔案名, $< 擴展成依靠列表中的第 一個依靠檔,而 $^ 擴展成整個依靠的列表(除掉了裡面所有重 複的檔案名)。利用這些變數,我們可以把上面的 makefile 寫成:

OBJS = foo.o bar.o
CC = gcc
CFLAGS = -Wall -O -g

myprog : $(OBJS)
  $(CC) $^ -o $@

foo.o : foo.c foo.h bar.h
  $(CC) $(CFLAGS) -c $< -o $@

bar.o : bar.c bar.h
  $(CC) $(CFLAGS) -c $< -o $@

======================


====ARM指令:BIC====
Rd,  Rn, Oprand2
BIC(位清除)指令对 Rn 中的值 和 Operand2 值的反码按位进行逻辑“与”运算。 (注意:ARM官方网站有误, 写的是补码)
BIC 是 逻辑”与非” 指令, 实现的 Bit Clear的功能
举例:
BIC     R0,   R0  , #0xF0000000
#将 R0  高4位清零
BIC    R1,  R1,   #0x0F
#将R1   低4位清0
===========================
==================



關鍵代碼:

/*
 * 设置页表
 */
void create_page_table(void)
{

/* 
 * 用于段描述符的一些宏定义
 */ 
#define MMU_FULL_ACCESS     (3 << 10)   /* 访问权限 */
#define MMU_DOMAIN          (0 << 5)    /* 属于哪个域 */
#define MMU_SPECIAL         (1 << 4)    /* 必须是1 */
#define MMU_CACHEABLE       (1 << 3)    /* cacheable */
#define MMU_BUFFERABLE      (1 << 2)    /* bufferable */
#define MMU_SECTION         (2)         /* 表示这是段描述符 */
#define MMU_SECDESC         (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | \
                             MMU_SECTION)
#define MMU_SECDESC_WB      (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | \
                             MMU_CACHEABLE | MMU_BUFFERABLE | MMU_SECTION)
#define MMU_SECTION_SIZE    0x00100000

    unsigned long virtuladdr, physicaladdr;
    unsigned long *mmu_tlb_base = (unsigned long *)0x30000000;
    
    /*
     * Steppingstone的起始物理地址为0,第一部分程序的起始运行地址也是0,
     * 为了在开启MMU后仍能运行第一部分的程序,
     * 将0~1M的虚拟地址映射到同样的物理地址
     */
    virtuladdr = 0;
    physicaladdr = 0;
    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \
                                            MMU_SECDESC_WB;

    /*符合 段Descriptor */

    /*
     * 0x56000000是GPIO寄存器的起始物理地址,
     * GPFCON和GPFDAT这两个寄存器的物理地址0x56000050、0x56000054,
     * 为了在第二部分程序中能以地址0xA0000050、0xA0000054来操作GPFCON、GPFDAT,
     * 把从0xA0000000开始的1M虚拟地址空间映射到从0x56000000开始的1M物理地址空间
     */
    virtuladdr = 0xA0000000;
    physicaladdr = 0x56000000;
    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \
                                            MMU_SECDESC;
    /*符合 段Descriptor */

    /*
     * SDRAM的物理地址范围是0x30000000~0x33FFFFFF,
     * 将虚拟地址0xB0000000~0xB3FFFFFF映射到物理地址0x30000000~0x33FFFFFF上,
     * 总共64M,涉及64个段描述符
     */
    virtuladdr = 0xB0000000;
    physicaladdr = 0x30000000;
    while (virtuladdr < 0xB4000000)
    {
        *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \
                                                MMU_SECDESC_WB;
        /*符合 段Descriptor */
        virtuladdr += 0x100000;
        physicaladdr += 0x100000;

    }
}

/*
 * 启动MMU
 */
void mmu_init(void)
{
    unsigned long ttb = 0x30000000;     /*頁表基址*/

// ARM休系架构与编程
// 嵌入汇编:LINUX内核完全注释
__asm__(
    "mov    r0, #0\n"
    "mcr    p15, 0, r0, c7, c7, 0\n"    /* 使无效ICaches和DCaches */
    
    "mcr    p15, 0, r0, c7, c10, 4\n"   /* drain write buffer on v4 */
    "mcr    p15, 0, r0, c8, c7, 0\n"    /* 使无效指令、数据TLB */
    
    "mov    r4, %0\n"                   /* r4 = 页表基址 */
    "mcr    p15, 0, r4, c2, c0, 0\n"    /* 设置页表基址寄存器 */
                                        /*寫入CP15協處理器的C2暫存器*/
    
    "mvn    r0, #0\n"                   
    "mcr    p15, 0, r0, c3, c0, 0\n"    /* 域访问控制寄存器设为0xFFFFFFFF,
                                         * 不进行权限检查 
                                         */
                                        /*寫入CP15協處理器的C3暫存器*/
    
    /* 
     * 对于控制寄存器,先读出其值,在这基础上修改感兴趣的位,
     * 然后再写入
     */
    "mrc    p15, 0, r0, c1, c0, 0\n"    /* 读出控制寄存器的值 */
    
    /* 控制寄存器的低16位含义为:.RVI ..RS B... .CAM
     * R : 表示换出Cache中的条目时使用的算法,
     *     0 = Random replacement;1 = Round robin replacement
     * V : 表示异常向量表所在的位置,
     *     0 = Low addresses = 0x00000000;1 = High addresses = 0xFFFF0000
     * I : 0 = 关闭ICaches;1 = 开启ICaches
     * R、S : 用来与页表中的描述符一起确定内存的访问权限
     * B : 0 = CPU为小字节序;1 = CPU为大字节序
     * C : 0 = 关闭DCaches;1 = 开启DCaches
     * A : 0 = 数据访问时不进行地址对齐检查;1 = 数据访问时进行地址对齐检查
     * M : 0 = 关闭MMU;1 = 开启MMU
     */
    
    /*  
     * 先清除不需要的位,往下若需要则重新设置它们    
     */
                                        /* .RVI ..RS B... .CAM */ 
    "bic    r0, r0, #0x3000\n"          /* ..11 .... .... .... 清除V、I位 */
    "bic    r0, r0, #0x0300\n"          /* .... ..11 .... .... 清除R、S位 */
    "bic    r0, r0, #0x0087\n"          /* .... .... 1... .111 清除B/C/A/M */

    /*
     * 设置需要的位
     */
    "orr    r0, r0, #0x0002\n"          /* .... .... .... ..1. 开启对齐检查 */
    "orr    r0, r0, #0x0004\n"          /* .... .... .... .1.. 开启DCaches */
    "orr    r0, r0, #0x1000\n"          /* ...1 .... .... .... 开启ICaches */
    "orr    r0, r0, #0x0001\n"          /* .... .... .... ...1 使能MMU */
    
    "mcr    p15, 0, r0, c1, c0, 0\n"    /* 将修改的值写入控制寄存器 */
    : /* 无输出 */
    : "r" (ttb) );                      

/*"r"(ttb) 表示变量 ttb 的值赋给一个寄存器作为输入参数,这个寄存器由编译器自动分配。
第 42 行的 "%0" 被用来表示这个寄存器。 
    这是gcc嵌入汇编的写法
    
: "r" (ttb) );表示即系统会分配某个寄存器rx,保存 ttb 这个值,
    mov    r4, %0\n 相当于 mov r4,[rx]  ,相当于 mov r4,ttb ,
    相当于 mov r4,#0x30000000*/

}


  • create_page_table (頁表的創建),實作上就是創建一個指標陣列,指標指到實際物理段的基地址,這個指標陣列須符合Descriptor。
  • mmu_init,透過arm asm指令做初始化設定,最後將頁表基地址(TTB)傳入CP15協處理器的C2寄存器,如此就完成MMU之設定,啟用MMU後,即可透過MMU操作虛擬地址。



No comments:

Post a Comment