您的位置:首页 > 运维架构 > Linux

linux非解压代码的启动过程分析 unicore head.S vmlinux解压后的代码运行 临时MMU的建立

2011-07-08 16:46 603 查看
我们首先看看内核的生成过程:

非压缩内核映象由 make Image 命令产生。其生成过程是:
(1) 内核的各个模块经过编译,链接,在内核源代码的顶层目录下生成 vmlinux 文件,这是
一个 ELF 格式的映象
(2) 用 arm-linux-objcopy 命令把 vmlinux 转换为二进制格式映象 arch/arm/boot/Image
压缩内核映象由 make zImage 命令产生,其生成过程是:
(1) 用 gzip 对 非 压 缩 内 核 二 进 制 映 象 arch/arm/boot/ Image 进 行 压 缩 , 生 成
arch/arm/boot/compressed/piggy.gz 文件
(2) arch/arm/boot/compressed/目录下有三个文件:piggy.s,定义了一个包含./piggy.gz 文件的
数据段;head.S 包含了对 gzip 压缩过的内核进行解压的代码;vmlinux-lds 是链接脚本。
这几个文件经过编译链接,在 arch/arm/boot/compressed/目录下产生 vmlinux 文件,这是
一个 ELF 格式的映象
(3) 用 arm-linux-objcopy 命令把 arch/arm/boot/compressed/vmlinux 转换为二进制格式映象:
arch/arm/boot/compressed/zImage

/*
* linux/arch/unicore32/kernel/head.S
*/

#if (PHYS_OFFSET & 0x003fffff)
#error "PHYS_OFFSET must be at an even 4MiB boundary!"
#endif

#define KERNEL_RAM_VADDR(PAGE_OFFSET + KERNEL_IMAGE_START) @0xc0000000+0x8000
#define KERNEL_RAM_PADDR(PHYS_OFFSET + KERNEL_IMAGE_START) @0x40000000+0x8000

#define KERNEL_PGD_PADDR(KERNEL_RAM_PADDR - 0x1000) @0x40008000-0x1000 = 0x40007000
#define KERNEL_PGD_VADDR(KERNEL_RAM_VADDR - 0x1000) @0xc0008000-0x1000 = 0x40007000

#define KERNEL_STARTKERNEL_RAM_VADDR @0xc0008000
#define KERNEL_END_end @in the vmlinux-linux.lds

/*
* swapper_pg_dir is the virtual address of the initial page table.
* We place the page tables 4K below KERNEL_RAM_VADDR. Therefore, we must
* make sure that KERNEL_RAM_VADDR is correctly set. Currently, we expect
* the least significant 16 bits to be 0x8000, but we could probably
* relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x1000.
*/
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000 @the least 16 bite must be 0x8000 as the error said below
#error KERNEL_RAM_VADDR must start at 0xXXXX8000 @预留了32k的空间
#endif

.globlswapper_pg_dir
.equswapper_pg_dir, KERNEL_RAM_VADDR - 0x1000 @swapper_pg_dir = KERNEL_PGD_VDIR
=0xc0007000

/*
* Kernel startup entry point.
* ---------------------------
*
* This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care
*
* This code is mostly position independent, so if you link the kernel at
* 0xc0008000, you call this at __pa(0xc0008000).
注意前面这段代码肯定是地址无关的(也就是如上所述position independent
*/
__HEAD
ENTRY(stext)
@ set asr
movr0, #PRIV_MODE@ ensure priv
mode PRIV_MODE = 0x13 =0b10011
orr0, #PSR_R_BIT | PSR_I_BIT@
disable irqs 关闭实时模式和屏蔽中断
mov.aasr, r0

@ process identify
movcr0, p0.c0, #0@ cpuid
movlr1, 0xff00ffff@ mask
movlr2, 0x4d000863@ value
andr0, r1, r0
cxor.ar0, r2
bne__error_p@ invalid processor
id

/*
* Clear the 4K level 1 swapper page table
*/
movlr0, #KERNEL_PGD_PADDR@
page table address 0x40007000
movr1, #0
addr2, r0, #0x1000 @so r2 =0x40008000
101:stw.wr1, [r0]+, #4 @clear 0x40007000-0x40008000
stw.wr1, [r0]+, #4
stw.wr1, [r0]+, #4
stw.wr1, [r0]+, #4
cxor.ar0, r2
bne101b

movlr4, #KERNEL_PGD_PADDR@
page table address r4 = 0x40007000
movr7, #PMD_TYPE_SECT | PMD_PRESENT@
page size: section PMD_TYPE_SECT 3<<0 即0b11 PMD_PRESENT 1<<2
orr7, r7, #PMD_SECT_CACHEABLE@
cacheable 其他的对应unicore一级页表超页
orr7, r7, #PMD_SECT_READ | PMD_SECT_WRITE | PMD_SECT_EXEC @读写及可执行权 所有综合以上3条指令应该是准备超页(4M)的属性数据

/*
* Create identity mapping for first 4MB 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.
这是第一次映射 va = pa 的映射 pgd页表项地址为0x40007000+0x400
*/
movr6, pc @镜像解压后的链接地址是0xc0008000 而实际上是在0x40008000那里开始跑所以这里得到的pc应该是0x40008xxx
movr6, r6 >> #22@ start of
kernel section得到物理地址的高10位 即0b0100_0000_00
orr1, r7, r6 << #22@ flags
+ kernel phys base
stwr1, [r4+], r6 << #2@ identity
mapping 至此已为0x40000000后的4M建立了映射0x40400000 而r6右移动两位是一个页表项有4个字节,10有1024个表项,所以站4k,这里的意思是,由于这里的cpu发出的地址仍然为0x4xxxxxxx 当开MMU时,在前面的4M物理地址和虚拟地址完全相同,当pc发出的地址经过MMU后找到的物理地址数据其实还是一样的,此时页表项的内容为地址0x40007000+0x400

/*
* Now setup the pagetables for our kernel direct
* mapped region.
这是第二次的映射 建立内核虚拟地址的运用
*/
addr0, r4, #(KERNEL_START & 0xff000000) >> 20 @KERNEL_START = 0xc0008000 r0 = 0x40007000
+ 0xc00
stw.wr1, [r0+], #(KERNEL_START & 0x00c00000) >> 20
@页表项内容地址0x40007000+0xc00 至此内核后0xc0000000后面4M的内容已经建立了页表,以上两句话相当于KERNEL_START&0b_1111_1111_1100(高10)位。而r1为0b_0100_0000_00。所以还身下22位可以变动。所以为4M
movlr6, #(KERNEL_END - 1) @获得kernel的最后虚拟地址
addr0, r0, #4 @r0 = 0x40007000+0xc00+4 也就是下一个页表项,以免内核超过4M
addr6, r4, r6 >> #20 @得到内核最后的页表项地址
102:csub.ar0, r6
addr1, r1, #1 << 22 @下一个4M的物理地址高10位和相应的属性位
bua103f
stw.wr1, [r0]+, #4
b102b @至此内核所有的虚拟地址都建立好了超页的页表项 此时页表项有两类,至少两个,一个是平坦的即va=pa 而另外一个是内核虚拟地址开始即0xc0000000到内核结束地址的映射
103:
/*
* Then map first 4MB of ram in case it contains our boot params.
*/
addr0, r4, #PAGE_OFFSET >> 20
@这里有电特殊,如果我们的其实链接地址不是0xc0008000而是如0xc0048000这样超过4M的地址,那么这里就是必须的了
orr6, r7, #(PHYS_OFFSET & 0xffc00000)
@但是此时在这里其实和上面一段是完全一样的
stwr6, [r0]

ldw r15, __switch_data @
address to jump to after 这里是我们关键之中的关键,这里r15必定会变成0xc0008xxx,所以从这个函数开始cpu使用虚拟地址取数据

/*
* Initialise TLB, Caches, and MMU state ready to switch the MMU
* on.
*/
movr0, #0
movcp0.c5, r0, #28@ cache invalidate
all 这里对照手册很容易明白
nop8
movcp0.c6, r0, #6@ TLB invalidate
all
nop8

/*
* ..V. .... ..TB IDAM
* ..1. .... ..01 1111
*/
movlr0, #0x201f@ control register
setting 对协处理器控制器的数据准备

/*
* Setup common bits before finally enabling the MMU. Essentially
* this is just loading the page table pointer and domain access
* registers.
*/
#ifndef CONFIG_ALIGNMENT_TRAP @属性位如DCHCHE使能 CACHE策略等等
andnr0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
andnr0, r0, #CR_D
#endif
#ifdef CONFIG_CPU_DCACHE_WRITETHROUGH
andnr0, r0, #CR_B
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
andnr0, r0, #CR_I
#endif

movcp0.c2, r4, #0@ set pgd
pgd物理地址
b__turn_mmu_on
ENDPROC(stext)

/*
* Enable the MMU. This completely changes the structure of the visible
* memory space. You will not be able to trace execution through this.
*
* r0 = cp#0 control register
* r15 = *virtual* address to jump to upon completion
*/
.align5
__turn_mmu_on:
movr0, r0
movcp0.c1, r0, #0@ write control
reg
nop @ fetch inst by phys
addr 注意在这条指令之后有微妙而十分关键的变化cpu这这条指令之前(包括这条指令,都是从0x40008xxx地址取指令的,即使movcp0.c1, r0, #0 这条指令运行之后,MMU已经开了,但是由于在0x40007000+0x400已经建立了页表所以nop这条指令可以正常取到
movpc, r15 @当pc被赋值为r15后,里面的pc就开始从0xc0008xxx取数据了
nop8@ fetch inst by phys addr
ENDPROC(__turn_mmu_on)

/*
* Setup the initial page tables. We only setup the barest
* amount which are required to get the kernel running, which
* generally means mapping in the kernel code.
*
* r9 = cpuid
* r10 = procinfo
*
* Returns:
* r0, r3, r6, r7 corrupted
* r4 = physical page table address
*/
.ltorg

.align2
.type__switch_data, %object
__switch_data:
.long__mmap_switched
.long__bss_start@ r6
.long_end@ r7
.longcr_alignment@ r8
.longinit_thread_union + THREAD_START_SP @ sp

/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
* r0 = cp#0 control register
*/
__mmap_switched:
adrr3, __switch_data + 4

ldm.w(r6, r7, r8), [r3]+
ldwsp, [r3]

movfp, #0@ Clear BSS (and zero
fp)
203:csub.ar6, r7
bea204f
stw.wfp, [r6]+,#4
b203b
204:
andnr1, r0, #CR_A@ Clear 'A'
bit
stm(r0, r1), [r8]+@ Save control
register values
bstart_kernel @前面说过在0mmap_switched函数之后的已经变成了0xc0008xxx所以在这里肯定就已经是kernel的虚拟地址了
ENDPROC(__mmap_switched)

/*
* Exception handling. Something went wrong and we can't proceed. We
* ought to tell the user, but since we don't have any guarantee that
* we're even running on the right architecture, we do virtually nothing.
*
* If CONFIG_DEBUG_LL is set we try to print out something about the error
* and hope for the best (useful if bootloader fails to pass a proper
* machine ID for example).
*/
__error_p:
#ifdef CONFIG_DEBUG_LL
adrr0, str_p1
b.lprintascii
movr0, r9
b.lprinthex8
adrr0, str_p2
b.lprintascii
901:nop8
b901b
str_p1:.asciz"\nError: unrecognized processor variant (0x"
str_p2:.asciz").\n"
.align
#endif
ENDPROC(__error_p)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: