mini2440 linux 内存布局

mini2440 linux 内存布局

        在学习linux内存寻址的过程中,注意到在x86架构上,分段与分页机制共存。而在RSIC体系结构下一般只支持分页。《深入理解linux内核》是在x86架构上介绍的linux物理内存布局。在x86架构上,linux被安装在ram从物理地址的0x00100000也就是第二个1M的地方。内核态的线性地址:0xc0000000~0xffffffff,在内核态可以寻址0x00000000~0xbfffffff的地址,用户态的线性地址范围为:0x00000000~0xbfffffff,用户态的程序不能访问内核态的线性地址。这几个是线性地址只是CPU寻址的时候用,最终都是要映射到实际的物理地址。在内核镜像包括代码段,数据段。在数据段的后面保存了全局页表描述了线性地址怎样转化成物理地址的。在内核态的线性地址空间里,内核要映射全部的物理RAM,前8M的RAM有两个映射分别对应于线性地址0x00000000~0x0x007fffff与0xc0000000~0xc07fffff,这个是为了在内核初始化的时候,MMU开启前后的操作方便,这是临时映射。最终的内核态映射是线性地址与物理地址线性映射,就是每个线性地址都是物理地址加上一个偏移量,在x86上这个偏移量就是0xc0000000。以上就是x86架构上linux的物理内存布局。而mini2440的物理内存布局会有很大的不同,以64M的SDRAM来说,RAM的物理地址是从0x30000000开始的,结束与0x34000000。要了解linux在mini2440上的内存布局首先要看System.map文件,这个链接器生成的文件。描述了linux镜像在内存中的布局,地址全部是线性地址。

[plain]  view plain copy
  1. c0004000 A swapper_pg_dir  
  2. c0008000 T __init_begin  
  3. c0008000 T _sinittext  
  4. c0008000 T _stext  
  5. c0008000 T stext  
  6. c0008034 t __enable_mmu  
  7. ......  
  8. ......  
  9. c04b08d8 B proc_net_rpc  
  10. c04b08dc b sunrpc_table_header  
  11. c04b08e0 B rpc_debug  
  12. c04b08e4 B nfs_debug  
  13. c04b08e8 B nfsd_debug  
  14. c04b08ec B nlm_debug  
  15. c04b08f0 b nullstats.25712  
  16. c04b0910 B __bss_stop  
  17. c04b0910 B _end  
         可以看出,内核镜像起始地址为0xc0004000,终止地址为0xc04b0910,但是起始地址处到0xc0008000之间32K的地址似乎没有内容,内核镜像大小大约4M。那么这个内核镜像在物理内存是如何布局的呢。在/arch/arm/kernel/head.S中有描述:

[plain]  view plain copy
  1. #define KERNEL_RAM_VADDR    (PAGE_OFFSET + TEXT_OFFSET)   
  2. //这个是内核线性地址的开始,PAGE_OFFSET = 0xc0000000 而TEXT_OFFSET = 0x00008000,所以KERNEL_RAM_VADDR = 0xc0008000   
  3. #define KERNEL_RAM_PADDR    (PHYS_OFFSET + TEXT_OFFSET)  
  4. //这个是内核物理地址的开始处,PHYS_OFFSET = 0x30000000 而TEXT_OFFSET = 0x00008000,所以KERNEL_RAM_PADDR = 0x30008000,所以bootloader将内核装载到这个地址处,装载到其他地址是不行的  
  5.   
  6. #if (KERNEL_RAM_VADDR & 0xffff) != 0x8000  
  7. #error KERNEL_RAM_VADDR must start at 0xXXXX8000  
  8. #endif  
  9. //检查定义的是否合法,内核开始物理地址必须是0xXXXX8000  
  10.   
  11.     .globl  swapper_pg_dir  
  12.     .equ    swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000  
  13. //swapper_pg_dir这个变量是内核全局页表的起始地址 可以看出这里是0xc0004000,与内核链接符号表相同  
  14.     .macro  pgtbl, rd  
  15.     ldr \rd, =(KERNEL_RAM_PADDR - 0x4000)  
  16.     .endm  
  17. //声明一个宏,作用就是将0x30004000赋值给rd  
  18. #ifdef CONFIG_XIP_KERNEL ///没定义  
  19. #define KERNEL_START    XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)  
  20. #define KERNEL_END  _edata_loc  
  21. #else  
  22. #define KERNEL_START    KERNEL_RAM_VADDR  
  23. #define KERNEL_END  _end  //_end是内核链接符号表中的变量,代表内核结束线性地址  
  24. #endif  
  25. ENTRY(stext)  
  26.     setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode  
  27.                         @ and irqs disabled  
  28.     mrc p15, 0, r9, c0, c0      @ get processor id  
  29.     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid  
  30.     movs    r10, r5             @ invalid processor (r5=0)?  
  31.     beq __error_p           @ yes, error 'p'  
  32.     bl  __lookup_machine_type       @ r5=machinfo  
  33.     movs    r8, r5              @ invalid machine (r5=0)?  
  34.     beq __error_a           @ yes, error 'a'  
  35.     bl  __vet_atags  
  36.     bl  __create_page_tables  
  37.   
  38.     /*  
  39.      * The following calls CPU specific code in a position independent  
  40.      * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of  
  41.      * xxx_proc_info structure selected by __lookup_machine_type  
  42.      * above.  On return, the CPU will be ready for the MMU to be  
  43.      * turned on, and r0 will hold the CPU control register value.  
  44.      */  
  45.     ldr r13, __switch_data      @ address to jump to after  
  46.                         @ mmu has been enabled  
  47.     adr lr, BSYM(__enable_mmu)      @ return (PIC) address  
  48.  ARM(   add pc, r10, #PROCINFO_INITFUNC )  
  49.  THUMB( add r12, r10, #PROCINFO_INITFUNC    )  
  50.  THUMB( mov pc, r12             )  
  51. ENDPROC(stext)  
        由内核链接符号表可以看出,这段代码是内核最早运行的代码,其地址在0xc0008000。这段代码是bootloader将内核解压后执行的代码,执行的环境是:没有开启MMU,r0 = 0, r1 = machine nr, r2 = atags pointer atags pointer是标记列表的指针,这个是UBOOT或者其他bootloader传递给内核的参数。前面的汇编代码主要是检查机器吗,与提取内核参数。最后调用 __create_page_tables来创建内核临时页表。
[cpp]  view plain copy
  1. __create_page_tables:  
  2.     pgtbl   r4              @ page table address  
  3. //r4中保存了内核临时页表的地址0x30004000  
  4.     /* 
  5.      * Clear the 16K level 1 swapper page table 
  6.      */  
  7.     mov r0, r4  
  8.     mov r3, #0  
  9.     add r6, r0, #0x4000  
  10. 1:  str r3, [r0], #4  
  11.     str r3, [r0], #4  
  12.     str r3, [r0], #4  
  13.     str r3, [r0], #4  
  14.     teq r0, r6  
  15.     bne 1b  
  16. //将从0x30004000~0x30008000的内存清零  
  17.     ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags  
  18.   
  19.     /* 
  20.      * Create identity mapping for first MB of kernel to 
  21.      * cater for the MMU enable.  This identity mapping 
  22.      * will be removed by paging_init().  We use our current program 
  23.      * counter to determine corresponding section base address. 
  24.      */  
  25.     mov r6, pc  
  26.     mov r6, r6, lsr #20         @ start of kernel section  
  27.     orr r3, r7, r6, lsl #20     @ flags + kernel base  
  28.     str r3, [r4, r6, lsl #2]        @ identity mapping  
  29. //一级页表使用段,每个段描述符都能映射1M的物理地址,这里只是映射前1M的物理地址  
  30. //内核物理地址从0xc0008000开始,所以一级页表表述符要存放在页表首地址的偏移0x0000c000这个位置上  
  31. //这里就是将一级页表表述符存放到此处,可以看出段基地址为0x30000000  
  32.     /* 
  33.      * Now setup the pagetables for our kernel direct 
  34.      * mapped region. 
  35.      */  
  36.     add r0, r4,  #(KERNEL_START & 0xff000000) >> 18  
  37.     str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!  
  38. //这段代码将虚拟地址0x30008000开始的1M内存也映射到了0x30008000处了  
  39.     ldr r6, =(KERNEL_END - 1)  
  40.     add r0, r0, #4  
  41.     add r6, r4, r6, lsr #18  
  42. 1:  cmp r0, r6  
  43.     add r3, r3, #1 << 20  
  44.     strls   r3, [r0], #4  
  45.     bls 1b  
  46. //将内核镜像全部映射到物理地址  
  47. //经过以上代码,我们访问从0xc0000000的前1M的地址起始就是访问物理地址从0x30000000开始的1M,我们访问从0x30000000到内核大小的线性地址,就是访问的真实的物理地址(前提是后面开启MMU)  
  48.   
  49.     /* 
  50.      * Then map first 1MB of ram in case it contains our boot params. 
  51.      */  
  52.     add r0, r4, #PAGE_OFFSET >> 18  //0x00003000  
  53.     orr r6, r7, #(PHYS_OFFSET & 0xff000000)  
  54.     .if (PHYS_OFFSET & 0x00f00000)  //不成立  
  55.     orr r6, r6, #(PHYS_OFFSET & 0x00f00000)  
  56.     .endif  
  57.     str r6, [r0]  
  58. //这段代码和上边做的事一样,就是将一级页表描述符写到正确的位置  
  59.   
  60.     mov pc, lr  
  61. ENDPROC(__create_page_tables)  
        经过页表的初级初始化,0xc0000000~0xc0100000的线性地址被映射到了0x30000000~0x30100000的物理地址 0x30000000~0x30000000+KERNELSIZE的线性地址被映射到了0x30000000~0x30000000+KERNELSIZE物理地址。之所以这样的初始化,《深入理解linux内核》上是这样说的:分页第一阶段的目标就是允许在实模式下与保护模式下很容易对前8M的空间进行寻址,在arm上是1M。这个页表只是初级的初始化,后面会有更具体的页表初始化的,内核要映射全部的物理内存。在初始化完页表后,内核开启MMU,从而从实模式进入虚拟地址模式。

你可能感兴趣的:(mini2440 linux 内存布局)