在学习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镜像在内存中的布局,地址全部是线性地址。
- c0004000 A swapper_pg_dir
- c0008000 T __init_begin
- c0008000 T _sinittext
- c0008000 T _stext
- c0008000 T stext
- c0008034 t __enable_mmu
- ......
- ......
- c04b08d8 B proc_net_rpc
- c04b08dc b sunrpc_table_header
- c04b08e0 B rpc_debug
- c04b08e4 B nfs_debug
- c04b08e8 B nfsd_debug
- c04b08ec B nlm_debug
- c04b08f0 b nullstats.25712
- c04b0910 B __bss_stop
- c04b0910 B _end
- #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
- //这个是内核线性地址的开始,PAGE_OFFSET = 0xc0000000 而TEXT_OFFSET = 0x00008000,所以KERNEL_RAM_VADDR = 0xc0008000
- #define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
- //这个是内核物理地址的开始处,PHYS_OFFSET = 0x30000000 而TEXT_OFFSET = 0x00008000,所以KERNEL_RAM_PADDR = 0x30008000,所以bootloader将内核装载到这个地址处,装载到其他地址是不行的
- #if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
- #error KERNEL_RAM_VADDR must start at 0xXXXX8000
- #endif
- //检查定义的是否合法,内核开始物理地址必须是0xXXXX8000
- .globl swapper_pg_dir
- .equ swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000
- //swapper_pg_dir这个变量是内核全局页表的起始地址 可以看出这里是0xc0004000,与内核链接符号表相同
- .macro pgtbl, rd
- ldr \rd, =(KERNEL_RAM_PADDR - 0x4000)
- .endm
- //声明一个宏,作用就是将0x30004000赋值给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 //_end是内核链接符号表中的变量,代表内核结束线性地址
- #endif
- ENTRY(stext)
- setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
- @ and irqs disabled
- mrc p15, 0, r9, c0, c0 @ get processor id
- bl __lookup_processor_type @ r5=procinfo r9=cpuid
- movs r10, r5 @ invalid processor (r5=0)?
- beq __error_p @ yes, error 'p'
- bl __lookup_machine_type @ r5=machinfo
- movs r8, r5 @ invalid machine (r5=0)?
- beq __error_a @ yes, error 'a'
- bl __vet_atags
- bl __create_page_tables
- /*
- * The following calls CPU specific code in a position independent
- * manner. See arch/arm/mm/proc-*.S for details. r10 = base of
- * xxx_proc_info structure selected by __lookup_machine_type
- * above. On return, the CPU will be ready for the MMU to be
- * turned on, and r0 will hold the CPU control register value.
- */
- ldr r13, __switch_data @ address to jump to after
- @ mmu has been enabled
- adr lr, BSYM(__enable_mmu) @ return (PIC) address
- ARM( add pc, r10, #PROCINFO_INITFUNC )
- THUMB( add r12, r10, #PROCINFO_INITFUNC )
- THUMB( mov pc, r12 )
- ENDPROC(stext)
- __create_page_tables:
- pgtbl r4 @ page table address
- //r4中保存了内核临时页表的地址0x30004000
- /*
- * Clear the 16K level 1 swapper page table
- */
- mov r0, r4
- mov r3, #0
- add r6, r0, #0x4000
- 1: str r3, [r0], #4
- str r3, [r0], #4
- str r3, [r0], #4
- str r3, [r0], #4
- teq r0, r6
- bne 1b
- //将从0x30004000~0x30008000的内存清零
- ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
- /*
- * Create identity mapping for first MB of kernel to
- * cater for the MMU enable. This identity mapping
- * will be removed by paging_init(). We use our current program
- * counter to determine corresponding section base address.
- */
- mov r6, pc
- mov r6, r6, lsr #20 @ start of kernel section
- orr r3, r7, r6, lsl #20 @ flags + kernel base
- str r3, [r4, r6, lsl #2] @ identity mapping
- //一级页表使用段,每个段描述符都能映射1M的物理地址,这里只是映射前1M的物理地址
- //内核物理地址从0xc0008000开始,所以一级页表表述符要存放在页表首地址的偏移0x0000c000这个位置上
- //这里就是将一级页表表述符存放到此处,可以看出段基地址为0x30000000
- /*
- * Now setup the pagetables for our kernel direct
- * mapped region.
- */
- add r0, r4, #(KERNEL_START & 0xff000000) >> 18
- str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
- //这段代码将虚拟地址0x30008000开始的1M内存也映射到了0x30008000处了
- ldr r6, =(KERNEL_END - 1)
- add r0, r0, #4
- add r6, r4, r6, lsr #18
- 1: cmp r0, r6
- add r3, r3, #1 << 20
- strls r3, [r0], #4
- bls 1b
- //将内核镜像全部映射到物理地址
- //经过以上代码,我们访问从0xc0000000的前1M的地址起始就是访问物理地址从0x30000000开始的1M,我们访问从0x30000000到内核大小的线性地址,就是访问的真实的物理地址(前提是后面开启MMU)
- /*
- * Then map first 1MB of ram in case it contains our boot params.
- */
- add r0, r4, #PAGE_OFFSET >> 18 //0x00003000
- orr r6, r7, #(PHYS_OFFSET & 0xff000000)
- .if (PHYS_OFFSET & 0x00f00000) //不成立
- orr r6, r6, #(PHYS_OFFSET & 0x00f00000)
- .endif
- str r6, [r0]
- //这段代码和上边做的事一样,就是将一级页表描述符写到正确的位置
- mov pc, lr
- ENDPROC(__create_page_tables)