1. 程式人生 > >ARM Linux 核心啟動總結 之 建立臨時頁表

ARM Linux 核心啟動總結 之 建立臨時頁表

硬體平臺:S5PV210   核心版本:Linux2.6.32   檔案:head.S(linux/arch/arm/kernel/)

#include <**********>


#define KERNEL_RAM_VADDR    (PAGE_OFFSET + TEXT_OFFSET) 
#define KERNEL_RAM_PADDR    (PHYS_OFFSET + TEXT_OFFSET)

//PAGE_OFFSET是核心虛擬地址空間的起始地址,一般為0xC000_0000

//PHYS_OFFSET 是硬體實體記憶體的起始地址,在檔案memory.h(linux/arch/arm/mach-s5pv210/include/mach/)中定義,為0x2000_0000

//TEXT_OFFSET是核心程式碼相對於起始地址的偏移量,一般為0x8000

//KERNEL_RAM_VADDR=0xC000_8000;     KERNEL_RAM_PADDR=0x2000_8000

//剛開始的這32k(0x8000)空間用來存放頁表,啟動引數等

 .globl swapper_pg_dir
 .equ swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000

//equ相當於C語言中的#define

 .macro pgtbl, rd
 ldr \rd, =(KERNEL_RAM_PADDR - 0x4000)
 .endm

//這裡定義了一個巨集,把實體地址0x2000_4000賦值給rd暫存器,一般這個地址用來存放臨時頁表

#ifdef CONFIG_XIP_KERNEL(未定義)
#define KERNEL_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
#define KERNEL_END _edata_loc
#else
#define KERNEL_START KERNEL_RAM_VADDR
#define KERNEL_END _end
#endif

//KERNEL_START=0xC000_8000, KERNEL_END=具體是多少沒搞懂


 .section ".text.head", "ax"
ENTRY(stext)
********************************* 

 bl __create_page_tables

//跳過其他的啟動過程,直接看建立頁表的過程__create_page_tables

__create_page_tables:
 pgtbl r4                                 //巨集展開,把實體地址0x2000_4000賦值給r4暫存器
 mov r0, r4                             //r0=r4=0x2000_4000
 mov r3, #0                            //r3=0
 add r6, r0, #0x4000              //r6=0x2000_8000; r0和r6一起限定了頁表的範圍
1: str r3, [r0], #4
 str r3, [r0], #4
 str r3, [r0], #4
 str r3, [r0], #4                      
 teq r0, r6                             
 bne 1b                                   //將0x2000_4000開始到0x2000_8000之間的實體記憶體初始化為0,為後面的填充頁表做準備

 ldr r7, [r10, #PROCINFO_MM_MMUFLAGS]         //r10存放的是procinfo結構的地址,裡面包含了記憶體管理相關的引數
 mov r6, pc                          //這時候PC值是物理值,因為還沒有開MMU。核心啟動階段,PC值應該處於實體記憶體開始的部分,是0x200*_****
 mov r6, r6, lsr #20              //右移20位,r6=0x200,用r6來指示核心所處的段。
 orr r3, r7, r6, lsl #20           //把記憶體管理的一些引數加進去賦值給r3。注意r6還是0x200,r3=0x2000_****(flags)
 str r3, [r4, r6, lsl #2]           

//從0x2000_4000開始存放頁表,這裡使用的是段頁表,4G=4K*1M,所以一共有4096個頁表項,每個頁表項代表1M空間。虛擬地址的高12位用來在這個頁表中找到具體的頁表項,2^12=4096,也就是0x000~0xfff。每個頁表項佔4個位元組,剛剛好是4*4096=16K,也就是0x2000_4000到(0x2000_8000-1)這個區間大小。虛擬地址的低20位用來在1M的空間裡定位某個具體的位元組2^20=1M。

//為什麼還要把實體地址進行對映呢?我在其他高手的部落格中看到過,好像是因為:CPU流水線取指令,在開MMU後,PC地址應該全部是虛擬地址,但是預取指令功能導致開MMU的時候既有虛擬地址,又有實體地址,如果我們只建立虛擬地址的對映,那麼PC實體地址送到MMU之後就會找不到相應的頁表,會出錯。大概是這樣,這部分理解還有待深入確認。
//注意一下這裡的 r6, lsl #2,就是r6*4=0x200*4=0x800,0x200相對於0x000的偏移量是0x200,每個頁表項佔4個位元組,所以這個1M的段對應的頁表項地址就是0x2000_4800,最後把r3的值存進這個頁表項中。

 add r0, r4,  #(KERNEL_START & 0xff000000) >> 18       //KERNEL_START=0xC000_8000,所以r0=0x2000_4000+0xC00*4=0x2000_7000

//提取核心虛擬地址空間的段地址,右移18位就是右移20位,然後左移2位,找到對應的頁表項


 str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!  //KERNEL_START & 0x00f00000=0,對0x2000_7000處的頁表項進行填充
 ldr r6, =(KERNEL_END - 1)               //r6存放要對映的虛擬地址的終點,假設r6=0xC03*_****
 add r0, r0, #4                                    //r0=0x2000_7000+4=0x2000_7004
 add r6, r4, r6, lsr #18                        //找到虛擬地址終點所在段對應的頁表項地址r6=0x2000_4000+0xC03*4=0x2000_700C
1: cmp r0, r6
 add r3, r3, #1 << 20                          //0xC00段對應0x200段,0xC01段對應0x201段,0xC02段對應0x202段。。。
 strls r3, [r0], #4
 bls 1b                                                //對整個核心程式碼段(表述不確切,這個範圍由KERNEL_END 決定)進行對映,填充相應的頁表項

 add r0, r4, #PAGE_OFFSET >> 18                     //PAGE_OFFSET=0xC000_0000,r0=0x2000_7000 
 orr r6, r7, #(PHYS_OFFSET & 0xff000000)         //r7裡面存放的是記憶體管理引數,r6=0x2000_****
 .if (PHYS_OFFSET & 0x00f00000)
 orr r6, r6, #(PHYS_OFFSET & 0x00f00000)
 .endif
 str r6, [r0]                                                            //對0x2000_7000處的頁表項再次進行填充。填充的數值是一樣的,都是0x2000_****(flags)

   通常kernel的啟動引數由bootloader放到了實體記憶體的第1個M上,所以需要為RAM上的第1個M建立對映。 
* 上面已為PHYS_OFFSET + TEXT_OFFSET建立了對映,如果TEXT_OFFSET小於0x00100000的話, 
* 上面程式碼應該也為SDRAM的第一個M建立了對映,但如果大於0x0010000則不會。 
* 所以這裡無論如何均為SDRAM的第一個M建立對映(不知分析對否,還請指正)。 


mov pc, lr                      //結束

轉自 :http://blog.163.com/q_n_b/blog/static/214798216201301495346679/