内核编译(六):内核启动流程(一)

    上节课,我们就彻底分析了内核源码中的Makefile,并且知道它运行的第一个代码文件被编译成head.o。本节课,我们就来探究内核的启动流程。我们首先先在内核源码目录上建立一个SI工程,并把文件加入进去,同时打开arch\arm\kernel目录下的head.S代码:



我们先看到它的开始部分:


首先,它自定义了一个.text.head的段,这个段的属性的是可分配和可执行的。然后它把开始段的stext设置为函数的类型。这里是一些段的定义的操作,我们继续往下看。第一条指令是一条协处理指令,用来确保当前为管理模式。并irq是不可用的。下一条指令也是一条协处理指令,它把p15协处理器的c0寄存器(这里面保存有处理器的ID(0操作))的值赋值给r9。然后就跳到了函数__lookup_processor_type来检查一下是否支持这个cpuid,我们可以进去看一下这个函数:


可以看到,它在比较过程中,如果匹配了,就会直接返回,否则就会把0赋值给r5,然后再返回。接着它就会把r5赋值给r10(r10不重要,只是来判断结果是否会为零),并影响标记位,最终如果结果为零的话,就会跳转到__error_p,也就是错误显示:


最终它会进入到一个死循环,不会去执行了。我们接着看下去,接下来他就跳到__lookup_machine_type这个函数去,意思就是检查你的机器ID,看看是否支持你这款机器。接下来我们就来看一下这个函数:

再此之前,我们要弄懂一个问题,既然是检查机器ID,那机器ID肯定是作为参数传进来的,那么他在哪里呢?我们在U-Boot解析的时候,曾经解析到启动内核的函数:


我们看到,启动内核的函数theKernel传入了三个参数:0,bd->bi_arch_number,bd->bi_boot_params。第二个参数,就是我们的机器ID,我们可以看一下它的值是多少,但其实它只是个宏定义,值我们不是很关心,按照arm c语言的规则,第二个参数应该是传给寄存器,r1,所以r1就是我们的机器ID的值。我们回到刚才的那个函数,首先,第一句是:adr r3,3b,3后面的b表示backward,意思是要汇编器往后去搜索,我们可以搜到它的定义:


这样r3的地址就等于这个标号的地址,下一句指令是:ldmia r3, {r4, r5, r6},这样,r4就等于.(这个地址是一个虚拟地址,在链接脚本中可以看得到),r5就等于__arch_info_begin这个地址的值,这个地址也是一个虚拟地址,在链接脚本中可以看得到:


具体的值是多少,其实我们不必关心。r6的值就是__arch_info_end。我们继续往下看,接下来指令是sub r3, r3, r4,后面也有注释了,算出虚拟地址r3和r4之间的一个偏移,并把结果存到r3去,这样,我们就可以从虚拟地址,定位到真正的物理地址了,包括后面的r5,r6也可以做到。后面的两条指令就是把r5,r6的地址转换成真实的物理地址。接下来,我们就来探究一下__arch_info_begin与__arch_info_end之间的内容。从语义我们不难发现,它应该是一些和架构有关的信息。我们在链接脚本中可以看到包含段:.arch.info.init,接下来我们就来在si中进行搜索一下,看看谁定义了这个段:


我们看到这个宏定义,它是一个结构体,并且强制把它的属性设置为一个叫做.arch.info.init的段(这个我们在分析u-boot的时候也会遇到这种宏定义)。下面我们来搜索一下,谁使用了这个宏定义,我们找到这条结果:


然后,我们进去看一下:

我们不妨把它展开一下,看看它的结果:

static const struct machine_desc __mach_desc_S3C2440
 __used
 __attribute__((__section__(".arch.info.init"))) = {
	.nr		= MACH_TYPE_S3C2440,
	.name		= "SMDK2440",
	/* Maintainer: Ben Dooks <[email protected]> */
	.phys_io	= S3C2410_PA_UART,
	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S3C2410_SDRAM_PA + 0x100,

	.init_irq	= s3c24xx_init_irq,
	.map_io		= smdk2440_map_io,
	.init_machine	= smdk2440_machine_init,
	.timer		= &s3c24xx_timer,
};

可以看到,这个段的内容其实就是一个结构体。弄懂这个后,我们就可以继续看下去了。我们看回原来的那个检查机器ID的函数,下一句是:1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type,也就是把机器ID保存到r3去(r5是那个结构体的地址,#MACHINFO_TYPE则表示偏移,要取结构体哪个的信息,就加相应的偏移)。下面一句:

teq r3, r1 @ matches loader number?

则是测试r3和r1是否相等,如果相等则设置相应的标志位。下一句beq 2f,表示相等的话就直接跳到返回语句了。否则让它遍历到下一个MACHINFO,并判断是否已经遍历完了所有MACHINFO,如果遍历完还不匹配的话,就把0赋值给r5再返回。返回后,再进行检查机器ID是否为0,如果为0的话,就进入到__error_a这个函数中,最终也是一个死循环。如果支持这个机器ID的话,就跳到__create_page_tables这个函数里,也就是创建页表,我们就不分析这个函数了,我们继续看:


我们看到,它首先把__switch_data的地址赋值给r13,然后就__enable_mmu这个地址赋值给lr,然后给r10的值加上#PROCINFO_INITFUNC,然后复制给pc。r10的值我们在上面分析过了,是我们的procinfo的地址(忘了的话可以看前面的代码,不过这里我没有带大家分析检查CPUID这一步骤,大家可以自行分析,和分析机器ID是一模一样的),所以它是procinfo加上某个偏移量,然后复制给pc,pc就跳到那里。这里,我也不带大家继续分析那个过程了,我这里就直接说了,pc跳到那边之后,最终会mov pc ,lr,lr的值也就是我们之前那个__enable__mmu的地址,这个函数我也不带大家分析了,大家可以自行分析,最终它会执行这条指令mov pc ,r13,r13也就是我们__switch_data的地址。下面我们就探究一下__switch_data的内容:

我们看到,执行完mov pc,r13后,它就会跳到__mmap_switched这个标号处去执行,我们看下那部分的代码:

 
 
从注释可以看到,这里分别进行了复制数据段,清除BSS段,保存processor ID和machineID等初始化操作,这里我们就不详细分析了,最终它跳到了 start_kernel里去不复返了。到这里,我们可以把head.S做的这些工作称作第一阶段的初始化或者内核引导阶段。我们把它所做的工作总结一下:

    1.确定内核是否支持该架构

    2.确定内核是否支持该单板

    3.建立一级页表

    4.设置arm920,CPU核,禁止I,D caches等(这里并没有进行分析)

    5.使能MMU

    6.复制数据段,清除BSS段,设置栈指针,保存CPU ID到processor id 变量,保存机器ID到__machine_arch_type变量

    7.调用start_kernel函数

下节课,我们再来分析start_kernel这个函数。

猜你喜欢

转载自blog.csdn.net/xiaokangdream/article/details/80202930