内核启动(一)

内核启动(一)

前面大致浏览了一下,uboot的流程。从感性上面理解了uboot的启动加载过程。接下来就是解惑内核的启动流程了。

同样的手上有mini2440开发板,因此,就对linux-2.6.32.2进行解析。

从编译开始

因为uboot使用的是uImage,所以,直接使用如下的命令,查看整个编译流程.

make uImage -n

得到部分输出如下:


...<省略部分>

echo '  LD      vmlinux'; arm-linux-ld -EL  -p --no-undefined -X --build-id  -o vmlinux -T arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o arch/arm/kernel/init_task.o  init/built-in.o --start-group  usr/built-in.o  arch/arm/kernel/built-in.o  arch/arm/mm/built-in.o  arch/arm/common/built-in.o  arch/arm/mach-s3c2410/built-in.o  arch/arm/mach-s3c2400/built-in.o  arch/arm/mach-s3c2412/built-in.o  arch/arm/mach-s3c2440/built-in.o  arch/arm/mach-s3c2442/built-in.o  arch/arm/mach-s3c2443/built-in.o  arch/arm/plat-s3c24xx/built-in.o  arch/arm/plat-s3c/built-in.o  kernel/built-in.o  mm/built-in.o  fs/built-in.o  ipc/built-in.o  security/built-in.o  crypto/built-in.o  block/built-in.o  arch/arm/lib/lib.a  lib/lib.a  arch/arm/lib/built-in.o  lib/built-in.o  drivers/built-in.o  sound/built-in.o  firmware/built-in.o  net/built-in.o --end-group .tmp_kallsyms2.o

...<省略部分>

set -e;  echo '  LD      arch/arm/boot/compressed/vmlinux'; arm-linux-ld -EL    --defsym zreladdr=0x30008000 --defsym params_phys=0x30000100 -p --no-undefined -X /home/wanbiao/workspace/bin/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/bin/../lib/gcc/arm-none-linux-gnueabi/4.3.2/armv4t/libgcc.a -T arch/arm/boot/compressed/vmlinux.lds arch/arm/boot/compressed/head.o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/misc.o -o arch/arm/boot/compressed/vmlinux ; echo 'cmd_arch/arm/boot/compressed/vmlinux := arm-linux-ld -EL    --defsym zreladdr=0x30008000 --defsym params_phys=0x30000100 -p --no-undefined -X /home/wanbiao/workspace/bin/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/bin/../lib/gcc/arm-none-linux-gnueabi/4.3.2/armv4t/libgcc.a -T arch/arm/boot/compressed/vmlinux.lds arch/arm/boot/compressed/head.o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/misc.o -o arch/arm/boot/compressed/vmlinux ' > arch/arm/boot/compressed/.vmlinux.cmd
:
echo '  Kernel:wanbiao arch/arm/boot/zImage is ready'
set -e;  echo '  UIMAGE  arch/arm/boot/uImage'; /bin/bash /home/wanbiao/workspace/linux-2.6.32.2-mini2440-20110413/linux-2.6.32.2/scripts/mkuboot.sh -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -n 'Linux-2.6.32.2' -d arch/arm/boot/zImage arch/arm/boot/uImage; echo 'cmd_arch/arm/boot/uImage := /bin/bash /home/wanbiao/workspace/linux-2.6.32.2-mini2440-20110413/linux-2.6.32.2/scripts/mkuboot.sh -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -n '\''Linux-2.6.32.2'\'' -d arch/arm/boot/zImage arch/arm/boot/uImage' > arch/arm/boot/.uImage.cmd
echo '  Image arch/arm/boot/uImage is ready'

从上面的输出可以看到。uImage依赖zImage,他们生成的命令如下:

/bin/bash /home/wanbiao/workspace/linux-2.6.32.2-mini2440-20110413/linux-2.6.32.2/scripts/mkuboot.sh 
    -A arm 
    -O linux 
    -T kernel 
    -C none 
    -a 0x30008000 
    -e 0x30008000 
    -n 'Linux-2.6.32.2' 
    -d arch/arm/boot/zImage 
    arch/arm/boot/uImage

而zImage依赖于compressed/vmlinux。他们的依赖和生成指令在./arch/arm/boot/Makefile中,如下:

$(obj)/zImage:	$(obj)/compressed/vmlinux FORCE
	$(call if_changed,objcopy)
	@echo '  Kernel: $@ is ready'

如果发生改变,则调用objcopy命令,使用依赖,生成目标,该命令使用的选项定义在OBJCOPYFLAGS中,文件位于:./arch/arm/Makefile,值如下:

OBJCOPYFLAGS	:=-O binary -R .note -R .note.gnu.build-id -R .comment -S

接下来就是compressed/vmlinux的生成,同样在./arch/arm/boot/Makefile中定义了compressed/vmlinux生成的依赖和指令,如下:

$(obj)/compressed/vmlinux: $(obj)/Image FORCE
	$(Q)$(MAKE) $(build)=$(obj)/compressed $@

原来,依赖Image,然后直接调用同名目录下的makefile文件,即可生成compressed/vmlinux

看看,compressed/vmlinux的生成命令是什么,可以直接在make uImage -n的输出中,直接找到,如下:

arm-linux-ld -EL    
    --defsym zreladdr=0x30008000 
    --defsym params_phys=0x30000100 
    -p 
    --no-undefined 
    -X /home/wanbiao/workspace/bin/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/
        bin/../lib/gcc/arm-none-linux-gnueabi/4.3.2/armv4t/libgcc.a 
    -T arch/arm/boot/compressed/vmlinux.lds 
    arch/arm/boot/compressed/head.o 
    arch/arm/boot/compressed/piggy.o 
    arch/arm/boot/compressed/misc.o 
    -o arch/arm/boot/compressed/vmlinux 

很显然,Image并没有直接作为输入文件,那么Image就有可能是作为间接输入文件了。查看上面的输入文件,发现了piggy.o的生成,如下:

arm-linux-gcc 
    -Wp,-MD,arch/arm/boot/compressed/.piggy.o.d  
    -nostdinc 
    -isystem /home/wanbiao/workspace/bin/arm-linux-gcc-4.3.2/usr/local/arm/
        4.3.2/bin/../lib/gcc/arm-none-linux-gnueabi/4.3.2/include 
    -Iinclude  
    -I/home/wanbiao/workspace/linux-2.6.32.2-mini2440-20110413/linux-2.6.32.
        2/arch/arm/include 
    -include include/linux/autoconf.h 
    -D__KERNEL__ 
    -mlittle-endian 
    -Iarch/arm/mach-s3c2410/include 
    -Iarch/arm/mach-s3c2400/include 
    -Iarch/arm/mach-s3c2412/include 
    -Iarch/arm/mach-s3c2440/include 
    -Iarch/arm/mach-s3c2442/include 
    -Iarch/arm/mach-s3c2443/include 
    -Iarch/arm/plat-s3c24xx/include 
    -Iarch/arm/plat-s3c/include 
    -D__ASSEMBLY__ 
    -mabi=aapcs-linux 
    -mno-thumb-interwork 
    -funwind-tables  
    -D__LINUX_ARM_ARCH__=4 
    -march=armv4t 
    -mtune=arm9tdmi 
    -include asm/unified.h 
    -msoft-float 
    -gdwarf-2     
    -Wa,-march=all   
    -c 
    -o arch/arm/boot/compressed/piggy.o 
    arch/arm/boot/compressed/piggy.S

由piggy.S直接生成,而该文件,包含了一个二进制文件,如下:

	.section .piggydata,#alloc
	.globl	input_data
input_data:
	.incbin	"arch/arm/boot/compressed/piggy.gz"
	.globl	input_data_end
input_data_end:

piggy.gz的生成在./arch/arm/boot/compressed/Makefile中

$(obj)/piggy.gz: $(obj)/../Image FORCE
	$(call if_changed,gzip)

哦,现在终于看到了,Image是如何一步步编程compressed/vmlinux了。大致经过如下

Image,先调用gzip生成piggy.gz,该文件又被piggy.S直接包含,最后piggy.S编译生成piggy.o

piggy.o最终编译生成compressed/vmlinux.

接下来,继续看,Image是如何生成的了。它生成的依赖和指令在./arch/arm/boot/Makefile中,如下:

$(obj)/Image: vmlinux FORCE
	$(call if_changed,objcopy)
	@echo '  Kernel: $@ is ready'

原来是依赖于vmlinux呀。终于到了我们熟悉的文件了。而vmlinux的生成,直接参考make uImage -n的输出即可得出,如下:

arm-linux-ld 
    -EL  
    -p 
    --no-undefined 
    -X 
    --build-id  
    -o vmlinux 
    -T arch/arm/kernel/vmlinux.lds 
    arch/arm/kernel/head.o arch/arm/kernel/init_task.o  
    init/built-in.o 
    --start-group  
        usr/built-in.o  
        arch/arm/kernel/built-in.o  
        arch/arm/mm/built-in.o  
        arch/arm/common/built-in.o  
        arch/arm/mach-s3c2410/built-in.o  
        arch/arm/mach-s3c2400/built-in.o  
        arch/arm/mach-s3c2412/built-in.o  
        arch/arm/mach-s3c2440/built-in.o  
        arch/arm/mach-s3c2442/built-in.o  
        arch/arm/mach-s3c2443/built-in.o  
        arch/arm/plat-s3c24xx/built-in.o  
        arch/arm/plat-s3c/built-in.o  
        kernel/built-in.o  
        mm/built-in.o 
        fs/built-in.o  
        ipc/built-in.o  
        security/built-in.o  
        crypto/built-in.o  
        block/built-in.o  
        arch/arm/lib/lib.a  
        lib/lib.a  
        arch/arm/lib/built-in.o  
        lib/built-in.o  
        drivers/built-in.o  
        sound/built-in.o  
        firmware/built-in.o  
        net/built-in.o 
    --end-group 
    .tmp_kallsyms2.o

从上面可以看到,vmlinux由各个模块连接而成,到此,基本可以告一段落了,到这里的所有其他依赖都是我们熟悉的编译环节了。

总结一下uImage的生成过程。

1.各个子模块,生成相应的中间文件
2.第一个步骤的中间文件,链接生成vmlinux
3.vmlinux通过objcopy生成Image
4.Image通过gzip生成piggy.gz
5.piggy.gz被包含到piggy.S中,并被编译成piggy.o
6.piggy.o和其他一些必要的文件,一起连接生成compressed/vmlinux
7.compressed/vmlinux通过objcopy生成zImage
8.zImag通过mkuboot.sh生成uImage

从入口点开始分析

上面通过make,大致了解了uImage的生成流程,那么接下来就是定位整个vmlinux的执行起始点了。

查看arch/arm/kernel/vmlinux.lds ,如下:

OUTPUT_ARCH(arm)
ENTRY(stext)

因此,入口点在stext中,查看整个源码程序,发现arch/arm/kernel/head.S文件中,如下:

	.section ".text.head", "ax"
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)

第一条指令,进入svc模式,并且关闭中断

第二条指令,用于获取处理器ID,详细的介绍可以参考arm的官方文档:https://developer.arm.com/documentation/ddi0344/k/debug/management-registers/processor-id-registers

第三条指令,跳转到__lookup_processor_type.

__lookup_processor_type,根据r9中的处理器id,找到对应的proc_info_list结构体,并将其地址保存在r5寄存中。整个代码如下:

__lookup_processor_type:
	adr	r3, 3f
	ldmia	r3, {r5 - r7}
	add	r3, r3, #8
	sub	r3, r3, r7			@ get offset between virt&phys
	add	r5, r5, r3			@ convert virt addresses to
	add	r6, r6, r3			@ physical address space
1:	ldmia	r5, {r3, r4}			@ value, mask
	and	r4, r4, r9			@ mask wanted bits
	teq	r3, r4
	beq	2f
	add	r5, r5, #PROC_INFO_SZ		@ sizeof(proc_info_list)
	cmp	r5, r6
	blo	1b
	mov	r5, #0				@ unknown processor
2:	mov	pc, lr
ENDPROC(__lookup_processor_type)

大致逻辑描述如下:

先列出,即将处理的数据结构如下:

	.align	2
3:	.long	__proc_info_begin
	.long	__proc_info_end
4:	.long	.
	.long	__arch_info_begin
	.long	__arch_info_end
  1. 将3的地址放入r3中。此时r3中的值为物理地址
  2. r5为r3处的内容,r6为r3+4处的内容,r7为r3+8处的内容。r5,r6,r7中的值为虚拟地址。
  3. 为了计算虚拟地址和物理地址之间的偏移。将r3加8之后,与r7相减。并将结果保存在r3中
  4. 此时,r3中保存的是虚拟地址和物理地址的偏移;r5中保存的是__proc_info_begin的虚拟地址,r6保存的是__proc_info_end的虚拟地址。
  5. 因为,当前还没有使能MMU,所以,我们应该操作的是物理地址,因此需要将r5,r6中的虚拟地址转换成对应的物理地址。即接下里的两句add指令
  6. 现在,将r3,r4分别赋值为r5处开始的内容。

接下来为了查看__proc_info_begin处的内容是什么,查找arch/arm/kernel/vmlinux.lds得如下内容:

  __proc_info_begin = .;
   *(.proc.info.init)
  __proc_info_end = .;

即.proc.info.init段的内容。查找整个源码,可得位于./arch/arm/mm/proc-arm920.S如下内容:

	.section ".proc.info.init", #alloc, #execinstr

	.type	__arm920_proc_info,#object
__arm920_proc_info:
	.long	0x41009200
	.long	0xff00fff0
	.long   PMD_TYPE_SECT | \
		PMD_SECT_BUFFERABLE | \
		PMD_SECT_CACHEABLE | \
		PMD_BIT4 | \
		PMD_SECT_AP_WRITE | \
		PMD_SECT_AP_READ
	.long   PMD_TYPE_SECT | \
		PMD_BIT4 | \
		PMD_SECT_AP_WRITE | \
		PMD_SECT_AP_READ
	b	__arm920_setup
	.long	cpu_arch_name
	.long	cpu_elf_name
	.long	HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
	.long	cpu_arm920_name
	.long	arm920_processor_functions
	.long	v4wbi_tlb_fns
	.long	v4wb_user_fns
#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
	.long	arm920_cache_fns
#else
	.long	v4wt_cache_fns
#endif
	.size	__arm920_proc_info, . - __arm920_proc_info

  1. 所以r3的值为0x41009200,r4的值为0xff00fff0
  2. 将r4和r9 按位与,将结果放置于r4中
  3. 然后再异或r3和r4.
  4. 如果修改了Zflag,就跳转到2处,即加载lr中的内容于pc中,相当于回到这个__lookup_processor_type的调用处
  5. 如果没有修改zflag,即没有找到对应的proc_info_list结构体,则将r5移动到下一个proc_info_list结构体处。
  6. 移动之后,如果r5和r6相等,即r5已经移动到了末尾处,则将r5赋值为0,表示没有找到,并退出
  7. 移动之后,如果r5和r6不相等,则跳到第6步,继续执行,直到找到或者r5为0

接下来回到arch/arm/kernel/head.S文件中,继续分析,再次粘贴原文如下:

	.section ".text.head", "ax"
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)

第四条指令,将r5的内容,保存在r10中。
第五条指令,如果r5是0,那么mov指令之后,状态寄存器的Z位会被置位。即该指令判断r5是否为0,如果是,则跳到__error_p
第六条指令,跳转到__lookup_machine_type中,进行machine_desc结构体的查找。查找工作和第三条指令的分析,一模一样,此处不再展开

第七条指令,第八条指令,也跟上面的分析一样,不在展开

第九条指令,跳转到__vet_atags,它的作用,是验证r2中的指针是否有效。该指针指向了tag数组。
验证的简略逻辑如下,先贴上源码:

__vet_atags:
	tst	r2, #0x3			@ aligned?
	bne	1f

	ldr	r5, [r2, #0]			@ is first tag ATAG_CORE?
	cmp	r5, #ATAG_CORE_SIZE
	cmpne	r5, #ATAG_CORE_SIZE_EMPTY
	bne	1f
	ldr	r5, [r2, #4]
	ldr	r6, =ATAG_CORE
	cmp	r5, r6
	bne	1f

	mov	pc, lr				@ atag pointer is ok

1:	mov	r2, #0
	mov	pc, lr
ENDPROC(__vet_atags)
  1. 首先判断,r2中的值是否有效,无效则r2为0,并跳到1处,然后退出并返回
  2. 再判断大小和标志,是否为预期值,如果无效,则将r2赋值为0,然后退出并返回
  3. 如果一切有效,则r2就是有效的值,并返回

第十条指令,跳转到__create_page_tables中,它的功能是为内核设置必要的page table。

依照惯例,先将原码贴上,它位于:/arch/arm/kernel/head.S中

__create_page_tables:
	pgtbl	r4				@ page table address

	/*
	 * 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

	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

	/*
	 * Now setup the pagetables for our kernel direct
	 * mapped region.
	 */
	add	r0, r4,  #(KERNEL_START & 0xff000000) >> 18
	str	r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
	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

#ifdef CONFIG_XIP_KERNEL
	/*
	 * Map some ram to cover our .data and .bss areas.
	 */
	orr	r3, r7, #(KERNEL_RAM_PADDR & 0xff000000)
	.if	(KERNEL_RAM_PADDR & 0x00f00000)
	orr	r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000)
	.endif
	add	r0, r4,  #(KERNEL_RAM_VADDR & 0xff000000) >> 18
	str	r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!
	ldr	r6, =(_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
#endif

	/*
	 * Then map first 1MB of ram in case it contains our boot params.
	 */
	add	r0, r4, #PAGE_OFFSET >> 18
	orr	r6, r7, #(PHYS_OFFSET & 0xff000000)
	.if	(PHYS_OFFSET & 0x00f00000)
	orr	r6, r6, #(PHYS_OFFSET & 0x00f00000)
	.endif
	str	r6, [r0]

#ifdef CONFIG_DEBUG_LL
	ldr	r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
	/*
	 * Map in IO space for serial debugging.
	 * This allows debug messages to be output
	 * via a serial console before paging_init.
	 */
	ldr	r3, [r8, #MACHINFO_PGOFFIO]
	add	r0, r4, r3
	rsb	r3, r3, #0x4000			@ PTRS_PER_PGD*sizeof(long)
	cmp	r3, #0x0800			@ limit to 512MB
	movhi	r3, #0x0800
	add	r6, r0, r3
	ldr	r3, [r8, #MACHINFO_PHYSIO]
	orr	r3, r3, r7
1:	str	r3, [r0], #4
	add	r3, r3, #1 << 20
	teq	r0, r6
	bne	1b
#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
	/*
	 * If we're using the NetWinder or CATS, we also need to map
	 * in the 16550-type serial port for the debug messages
	 */
	add	r0, r4, #0xff000000 >> 18
	orr	r3, r7, #0x7c000000
	str	r3, [r0]
#endif
#ifdef CONFIG_ARCH_RPC
	/*
	 * Map in screen at 0x02000000 & SCREEN2_BASE
	 * Similar reasons here - for debug.  This is
	 * only for Acorn RiscPC architectures.
	 */
	add	r0, r4, #0x02000000 >> 18
	orr	r3, r7, #0x02000000
	str	r3, [r0]
	add	r0, r4, #0xd8000000 >> 18
	str	r3, [r0]
#endif
#endif
	mov	pc, lr
ENDPROC(__create_page_tables)

大致逻辑如下:

  1. pgtbl宏,将r4指向KERNEL_RAM_PADDR - 0x4000的地址,即预留16k作为页表的空间。
  2. 接下来将用0初始化这16k空间
  3. 首先,将r0指向指向内存地址,将r3设置为0,将r6设置为16k空间的末尾地址
  4. 将r3的内容存储到r0所指向的内存中,然后r0再加4.
  5. 接着判断r0是否等于r6,即是否初始化完成。如果没有则继续第4步。
  6. 在上面分析的第三条指令中,r10将保存proc_info_list结构体。该结构体有一个字段用于存储mmu的标志。在我的开发板中(mini2440),它的定义位于./arch/arm/mm/proc-arm920.S。值为:
PMD_TYPE_SECT | \
		PMD_SECT_BUFFERABLE | \
		PMD_SECT_CACHEABLE | \
		PMD_BIT4 | \
		PMD_SECT_AP_WRITE | \
		PMD_SECT_AP_READ

即,接下来将proc_info_list中的mmu标志,存放在r7中

  1. 将当前的pc地址,从高位往低位数12位,作为内核的基址

  2. 将内核基址和mmu标志按位与,作为当前pc物理地址和虚拟地址的映射表的值,保存于r3中

  3. 将r3中的数据,放入mem[r4+r6*4]中。即将物理地址和虚拟地址一一对应。所以称为一致性映射

  4. 接下来就是进行内核的直接映射了。首先计算内核的末尾地址,然后从头到尾进行映射

  5. 首先,将r3中的数据,再次放入内核开始对应的页表,即r0所指的位置

  6. 然后,保存内核末尾地址在r6中

  7. 接着,r0+4,指向下一个页表项,同时将内核末尾表示的段,保存在r6中

  8. 比较r0对应的段和r6对应的段是否相同。

  9. 如果不同,将r3中的数据,修改一下,即段基址加一,放入r3中

  10. 然后继续放入r0对应的地址中。直到r0和r6相等。

  11. 接下来就是boot参数的一个映射。该映射实在没有什么可写的

  12. 剩下的宏的条件判断,一律跳过,不再展开

  13. 使用lr寄存器,回到最开始的地方。

照例,将原文再次粘贴一下:

	.section ".text.head", "ax"
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)

第十一条指令,加载__switch_data地址在r13中。在后面使能mmu之后,会跳转到该地址中

第十二条指令,将__enable_mmu地址保存在lr中,而不是pc当前地址。

第十三条指令,根据前面的分析,已经知道,该指令,是跳转到proc_info_list的PROCINFO_INITFUNC的偏移处,即,__cpu_flush。查找./arch/arm/mm/proc-arm920.S文件,即跳转到__arm920_setup处

代码如下:

	.type	__arm920_setup, #function
__arm920_setup:
	mov	r0, #0
	mcr	p15, 0, r0, c7, c7		@ invalidate I,D caches on v4
	mcr	p15, 0, r0, c7, c10, 4		@ drain write buffer on v4
#ifdef CONFIG_MMU
	mcr	p15, 0, r0, c8, c7		@ invalidate I,D TLBs on v4
#endif
	adr	r5, arm920_crval
	ldmia	r5, {r5, r6}
	mrc	p15, 0, r0, c1, c0		@ get control register v4
	bic	r0, r0, r5
	orr	r0, r0, r6
	mov	pc, lr
  1. 上面的代码,很简单,使指令和数据缓存无效,使指令和数据TLB无效。以及往协处理器中写入数据。

  2. 接下来,跳转到lr中。lr保存的是__enable_mmu的地址

代码如下:

__enable_mmu:
#ifdef CONFIG_ALIGNMENT_TRAP
	orr	r0, r0, #CR_A
#else
	bic	r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
	bic	r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
	bic	r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
	bic	r0, r0, #CR_I
#endif
	mov	r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
		      domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
		      domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
		      domain_val(DOMAIN_IO, DOMAIN_CLIENT))
	mcr	p15, 0, r5, c3, c0, 0		@ load domain access register
	mcr	p15, 0, r4, c2, c0, 0		@ load page table pointer
	b	__turn_mmu_on
ENDPROC(__enable_mmu)
  1. 上面__enable_mmu中功能,我没有完全掌握,先摘抄,网上的结论如下:
在使能MMU之前,最后的设置.他们包括页表指针,和domain访问寄存器。最后跳到__turn_mmu_on

__turn_mmu_on代码如下:

__turn_mmu_on:
	mov	r0, r0
	mcr	p15, 0, r0, c1, c0, 0		@ write control reg
	mrc	p15, 0, r3, c0, c0, 0		@ read id reg
	mov	r3, r3
	mov	r3, r13
	mov	pc, r3
ENDPROC(__turn_mmu_on)
  1. __turn_mmu_on的功能。就是真正的使能MMU功能,一旦完成,整个内存空间就发生了改变。

  2. 在最后,pc跳转到r13寄存器中的地址。在第十一条指令中,r13保存的是__switch_data的地址

接下来就是进入,__switch_data中,代码位于:./arch/arm/kernel/head-common.S中,如下:

	.align	2
	.type	__switch_data, %object
__switch_data:
	.long	__mmap_switched
	.long	__data_loc			@ r4
	.long	_data				@ r5
	.long	__bss_start			@ r6
	.long	_end				@ r7
	.long	processor_id			@ r4
	.long	__machine_arch_type		@ r5
	.long	__atags_pointer			@ r6
	.long	cr_alignment			@ r7
	.long	init_thread_union + THREAD_START_SP @ sp

即,跳转到__mmap_switched处执行,代码如下:

__mmap_switched:
	adr	r3, __switch_data + 4

	ldmia	r3!, {r4, r5, r6, r7}
	cmp	r4, r5				@ Copy data segment if needed
1:	cmpne	r5, r6
	ldrne	fp, [r4], #4
	strne	fp, [r5], #4
	bne	1b

	mov	fp, #0				@ Clear BSS (and zero fp)
1:	cmp	r6, r7
	strcc	fp, [r6],#4
	bcc	1b

 ARM(	ldmia	r3, {r4, r5, r6, r7, sp})
 THUMB(	ldmia	r3, {r4, r5, r6, r7}	)
 THUMB(	ldr	sp, [r3, #16]		)
	str	r9, [r4]			@ Save processor ID
	str	r1, [r5]			@ Save machine type
	str	r2, [r6]			@ Save atags pointer
	bic	r4, r0, #CR_A			@ Clear 'A' bit
	stmia	r7, {r0, r4}			@ Save control register values
	b	start_kernel
ENDPROC(__mmap_switched)
  1. r3指向存放__data_loc的地址
  2. r4,r5,r6,r7分别为__data_loc,_data, __bss_start ,_end
  3. 如果需要的话则复制数据段
  4. 清除BSS段为0
  5. 继续加载各个寄存器,r4中为processor_id,r5中为__machine_arch_type, r6中为__atags_pointer, r7中为cr_alignment ,sp中为init_thread_union + THREAD_START_SP
  6. 保存processor id,machine type,atags 指针。
  7. 接下来的两句,不知道其作用,暂时略过
  8. 接下来跳转到start_kernel处执行。

查找整个源码,可见start_kernel在./init/main.c中。

start_kernel即为用c语言写的,内核的入口了。下篇笔记见

总结一下,整个启动过程,虽然已经看完,但是还是有好多处细节,没办法吃透。不过好在不需要在这上面工作,没有吃透,并不影响,对整体流程的把控和理解。

如果后面有时间,再把不清楚的,挨个补上。

你可能感兴趣的:(内核,内核启动,head.S,内核编译过程,内核入口,processor_type)