內存管理單元MMU負責虛擬地址到物理地址的映射,並提供硬件機制的內存訪問權限檢查。
4種映射長度:段(1MB)、大頁(64KB)、小頁(4KB)、極小頁(1KB)。
對每個段都可以設置訪問權限。
大頁、小頁的每個子頁(sub-page,即被映射頁的1/4)都可以單獨設置訪問權限。
沒有啓動MMU時,CPU核、cache、MMU、外設等所有部件使用的都是物理地址。
理論知識我就不多寫了,畢竟這裏是做實驗而不是講原理的,但是代碼的註釋會很清楚
主要的c代碼:
- #include"memmap.h"
- void creat_page_table()
- {
- //我們建立段描述符 相對簡單些 便於理解
- //權限設置 它們佔描述符的低12bit【11-0】
- #define MMU_AP (3<<10) //ap 位111 訪問權限
- #define MMU_DOMAIN (0<<5) //選擇0域
- #define MMU_SPECIAL (1<<4) // 必須1 why?
- #define MMU_CACHEEN (1<<3) //cache使能
- #define MMU_BUFFEN (1<<2) //buffer 使能
- #define MMU_SECTION 2 //0b10 表示這個是段描述符
- //段描述符低12bit 沒有開啓cache和buff,因爲它 將用於前1M的映射,不能開啓cache
- #define MMU_SEC_DESC MMU_AP|MMU_DOMAIN|MMU_SPECIAL|MMU_SECTION
- //段描述符低12bit 開啓cache和buff,它將用於 後面sdram的映射,開啓cache buffer
- #define MMU_SEC_DESC_WB MMU_AP|MMU_DOMAIN|MMU_SPECIAL|MMU_SECTION|MMU_CACHEEN|MMU_BUFFEN
- unsigned long vir_address,phy_address;
- unsigned long *mmu_table_base=(unsigned long *)0x30000000;//地址轉化爲指針
- //映射前1M的物理地址到虛擬地址的前1M,爲了能夠在開啓mmu 之後0地址的前4k內的程序依然可以執行,所以它們是一一對應的關係
- /*
- (vir_address>>20可以理解取虛擬地址高 12bit 也可以理解爲虛擬地址除於1MB,就得到了段地址,所以要映射的虛擬地址必須是1M的倍數。學過彙編的話你應該很容易知道段地址是怎麼一回 事,比如這裏我們的是按照1M分段的,那麼段1就是1M=0x100000。段大頁小頁極小頁地址,也就是單位不一樣。
- phy_address&0xfff00000也是保存高 12bit 這樣做取來的結果是1M的倍數,也是段對應的物理地址,低12bit保存權限,中間的8bit保留
- */
- vir_address=0;
- phy_address=0;
- *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC);
- /*
- mmu_table_base+(vir_address>>20)取得段描述符的地址(指針),這4個字 節保存着一個32bit的段描述符(((phy_address&0xfff00000) | MMU_SEC_DESC)),段描述符的 【31-20】bit保存着段的物理地址(帶上單位就是實實在在的地址了,比如12M,12M=0x1200000)
- MMU_SEC_DESC即是上面定義的權限了
- */
- /* 將0x56000000 之後1M映射到0xA0000000
- 外部 IO存儲設備也是不能開啓cache和buff的
- */
- vir_address=0xa0000000;
- phy_address=0x56000000;
- *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC);
- /*將0x30000000 之後64M映射到0xB0000000*/
- vir_address=0xb0000000;
- phy_address=0x30000000;
- while(phy_address<0x34000000)
- {
- *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC_WB);
- vir_address+=0x100000; //累加1m,到下一個段描述符
- phy_address+=0x100000;
- }
- }
- /*以上就是我們建的描述符表了,但是cpu還不知道這是怎麼回事, 下面我們就需要告訴cpu這樣一個虛擬地址映射物理地址的規則了,由於處理開啓mmu以後的所有的虛擬地址轉換*/
- void mmu_init()
- {
- unsigned long ttb=0x30000000; //描述符表table的表頭地址
- __asm__(
- "mov r0,#0/n" //操作協處理器得仔細看其結構了
- "mcr p15,0,r0,c7,c7,0/n" //mcr是寫 將r0 寫入c7
- "mcr p15,0,r0,c7,c10,4/n"
- "mcr p15,0,r0,c8,c7,0/n"
- "mov r4,%0/n"
- "mcr p15,0,r4,c2,c0,0/n"
- "mvn r0,#0/n"
- "mcr p15,0,r0,c3,c0,0/n"
- /*對於控制寄存器 先讀出控制寄存器的值 修改之 再保存進去*/
- "mrc p15,0,r0,c1,c0,0/n"
- /*先清除不需要的位 如果需要用到 之後再設置*/
- "bic r0,r0,#0x3000/n"
- "bic r0,r0,#0x0300/n"
- "bic r0,r0,#0x0087/n" //清除1 2 3 7bit
- /* 設置需要的位*/
- "orr r0,r0,#0x2/n" //開啓對齊檢查
- "orr r0,r0,#0x4/n" //開啓dcache
- "orr r0,r0,#0x1000/n" //開啓icache
- "orr r0,r0,#0x1/n" //使能mmu
- "mcr p15,0,r0,c1,c0,0/n"
- :
- :"r" (ttb)
- );
- }
對於協處理器的結構,我也很模糊,以後專門研究下
其次就是定義那些寄存器的物理地址需要修改一下,因爲mmu開啓之後 就只認虛擬地址了
- //led fanction
- #define GPBCON (* (volatile unsigned long *)0xa0000010) //先將地址值其轉化爲指針,GPBCON其實就變成了這個地址儲存的值 鳥 哈哈
- #define GPBDAT (*(volatile unsigned long *)0xa0000014)
- #define GPBUP (*(volatile unsigned long *)0xa0000018)
- //key addr
- #define GPFCON (*(volatile unsigned long *)0xa0000050)
- #define GPFDAT (*(volatile unsigned long *)0xa0000054)
- #define GPFUP (*(volatile unsigned long *)0xa0000058)
然後就是start.s加的一點內容了
- @led start
- @2010-01-12
- @jay
- .globl _start
- _start:
- b reset
- @預留着以後擴展中斷向量表
- reset:
- /*因爲下面有c函數 要先設置個sp*/
- ldr sp,=4096
- @disable watchdog
- ldr r0,=0x53000000
- mov r1,#0
- str r1,[r0]
- @init SDRAM
- bl memsetup
- @cope the code to sdram
- bl cp_to_SDRAM
- @setup pagetable
- bl creat_page_table
- @init and enable mmu
- bl mmu_init
- @reset stack 開啓了mmu,我們要重設下堆棧指針,指向虛擬地址映射的頂部
- ldr sp,=0xb4000000
- @跳到虛擬地址
- /*這2句指令是從uboot學來的 注意這一點的理解,_led_on是個標籤,標籤的實質就是個地址,這個標籤存儲 的內容就是另外一個標籤led_test(是不是很熟悉的感覺?對了,就是c語言的指針的指針),其實就是將_led_on的內容led_text賦給了 pc*/
- ldr pc,_led_on
- _led_on:.word led_test
- load_sd:
- ldr sp,=0x34000000
- bl led_test
- @copy to sdram
- cp_to_SDRAM:
- @ adr r0,_start
- mov r0,#0
- add r1,r0,#4096
- ldr r2,=0x30004000 @注意這裏不要指定到0x30000000了
- lp:
- ldmia r0!,{r3-r11}
- stmia r2!,{r3-r11}
- cmp r0,r1
- ble lp
- mov pc,lr
最後就是連接腳本了
- OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
- OUTPUT_ARCH(arm)
- ENTRY(_start)
- SECTIONS
- {
- . =0x30004000;
- . =ALIGN(4);
- .text :
- {
- start.o (.text)
- low_init.o (.text)
- *(.text)
- }
- . = ALIGN(4);
- .data :{ *(.data) }
- . =ALIGN(4);
- .rodata : { *(.rodata) }
- . =ALIGN(4);
- __bss_start = .;
- .bss :{*(.bss)}
- _end = . ;
- }
這個也是從uboot學來的
ALIGN(4)是4字節對齊,32bitcpu一次處理32bit的嘛
.是指當前地址 這裏指定的知識編譯地址,跟運行地址會不一樣的。
兩個.指令要用;隔開的,注意. =0x30004000 這裏有空格的啊 否則會出錯
makefile
- CC=arm-linux-gcc
- LD=arm-linux-ld
- CP=arm-linux-objcopy
- DP=arm-linux-objdump
- objs:=start.o low_init.o memmap.o led.o
- mmu.bin:$(objs)
- $(LD) -Tboot.lds -g -o mmu_elf $^
- $(CP) -O binary -S mmu_elf $@
- $(DP) -D -m arm mmu_elf > mmu.asm
- #makefile 注意 .s不可以大寫 -T不可以少-
- %.o:%.c
- $(CC) -g -Wall -c -o $@ $<
- %.o:%.s
- $(CC) -g -Wall -c -o $@ S<
- clean:
- rm -f *.asm *.bin *_elf *.o
內容雖然不多,卻花費了我很多時間去理解,其他的代碼與之前的一樣 沒有改動
終於是可以成功開啓mmu了