1、 内核启动过程分析(以ARM为例)
(1)arch/arm/kernel/head.S
① 确定内核是否支持该架构和单板。
② 连接内核时使用的虚拟地址,所以要设置页表,使能MMU。
③ 调用C函数start_kernel之前的常规工作,包括复制数据段,清除BSS段,设置栈指针,保存CPU ID到processor_id变量,保存机器码ID,调用start_kernel。
④ 理解代码:
/*
* Kernel startup entry point.
* ---------------------------
*
* This is normally called from thedecompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache =dont care, r0 = 0,
* r1 = machine nr, r2 = atags pointer.
*
* This code is mostly position independent, soif you link the kernel at
* 0xc0008000, you call this at__pa(0xc0008000).
*
* See linux/arch/arm/tools/mach-types for thecomplete list of machine
* numbers for r1.
*
* We're trying to keep crap to a minimum; DONOT add any machine specific
* crap here - that's what the boot loader (orin extreme, well justified
* circumstances, zImage) is for.
*/
__HEAD
ENTRY(stext)
setmode PSR_F_BIT| PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
@and irqs disabled
mrc p15,0, r9, c0, c0 @ getprocessor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10,r5 @invalid processor (r5=0)?
THUMB( it eq) @ force fixup-able longbranch encoding
beq __error_p @yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8,r5 @invalid machine (r5=0)?
THUMB( it eq) @ force fixup-able longbranch encoding
beq __error_a @ yes, error 'a'
………………………………………………………………
l bl __lookup_processor_type
调用__lookup_processor_type子程序。
在内核映像中定义了若干proc_info_list结构,表示支持的cpu。如果定义了CONFIG_CPU_ARM920t则编译proc-arm920.S的文件生成对应的proc_info_list结构。
__lookup_processor_type的作用就是从实际硬件获得cpu Id 号和内核中proc_info_list是否有相对应的cpu类型,如果没有返回0.
l bl __lookup_machine_type
调用__lookup_machine_type子程序。
和匹配CPUID的过程相似,在匹配机器类型过程中,没一个开发板都对应与一个machine_desc结构体,它定义了开发板相对应的属性和函数,比如机器的Id,IO起始物理地址,Bootloader传入的参数地址,中断初始化函数,Io映射函数等。每一个machine_desc结构体是由一个BSP文件内的MACHINE_START和MACHINE_END定义。Uboot在调用内核时,r1存储的是机器ID。匹配不成功则返回0,匹配成功则返回machine_desc结构体地址,在start_kernel()中被多次使用。
如果上述匹配CPUID和MACHINEID都成功则继续执行。
l 下面是对SMDK2440的BSP文件的machine_desc结构分析
MACHINE_START(S3C2440,"SMDK2440")
/* Maintainer: Ben Dooks<ben-linux@fluff.org> */
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq =s3c24xx_init_irq,
.map_io =smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer =&s3c24xx_timer,
MACHINE_END
…………………………………………………………
#define MACHINE_START(_type,_name) \
staticconst struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init")))= { \
.nr =MACH_TYPE_##_type, \
.name =_name,
#defineMACHINE_END \
};
在arch/arm/tools/mach-types.h中定义了机器类型。
machine_is_xxx CONFIG_xxxx MACH_TYPE_xxx number
s3c2440 ARCH_S3C2440 S3C2440 362
(2)init/main.cstart_kernel()函数。
① 调用setup_arch()函数设置与体系结构相关的环境。
setup_arch()函数内容如下:
setup_processor();//处理器设置
mdesc =setup_machine(machine_arch_type);//获取单板的machine_desc结构体。
parse_tags(tags);//解析machine_desc中初始化的boot参数
paging_init(mdesc);//重新初始化页表
paging_init(mdesc);分析:我们知道默认外设I/O资源是不在Linux内核空间中的(如sram或硬件接口寄存器等),若需要访问该外设I/O资源,必须先将其地址映射到内核空间中来,然后才能在内核空间中访问它。这里的map_io成员即内核提供给用户的创建外设I/O资源到内核虚拟地址静态映射表的接口函数。Map_io成员函数会在系统初始化过程中被调用,流程如下:
Start_kernel -> setup_arch() -->paging_init() --> devicemaps_init()—>Map_io中被调用。
② 初始化控制台。
console_init();调用s3c24xx_serial_console初始化。
③ Reset_init启动init进程。
Reset_initàkernel_thread创建一个线程àinit_post()àrun_init_process();如果命令行定义了“init=xxx”则调用xxx作为第一个进程否则默认调用根文件系统的/sbin/init程序。至此内核启动了第一个进程并根据init进程的配置文件/etc/inittab执行进程。