2.3 (完结)分析内核启动——分析内核的第一个文件head.S和第一个c文件
我们在 本栏目第一节介绍到的start.S分析到了linux进入了theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
这里我们猜测一下内核启动要做什么事情:
1.处理u-boot传入的参数
2.最终目的:运行应用程序(应用程序在根文件系统,所以后期我们要挂载根文件系统)
我们从分析第一个文件head.S入手
linux-2.6.22.6\arch\arm\kernel\head.S
1.我们一开始看到__lookup_processor_type(得到机器Id)和__lookup_machine_type(得到单板类型)
_lookup_processor_type:先确认我这个内核能不能支持你这个处理器,不能就error
__lookup_machine_type:由上面u-boot传进的参数bi_arch_number(单板类型)确认我的内核是否支持你这个单板
3.进去__lookup_machine_type
如果看汇编的话,会知道r1就是u-boot传进来的机器ID=362
因为这个时候还没有启动MMU(助于构建虚拟地址和物理地址的架构),所以现在的r3=真实的地址
__arch_info_begin和__arch_info_end可以在链接脚本中找到(vmlinux.lds)
*(.arch.info.init)表示架构相关的初始化信息,开始地址和结束地址是__arch_info_begin和__arch_info_end
现在我们要知道arch.info.init是在哪里被定义的(/include/asm-arm/arch.h)
这里有个宏MACHINE_START,用sci全局搜索,会发现有很多arch/arm/mach-单板都有使用到这个宏
我们打开我们开发板对应的文件linux-2.6.22.6\arch\arm\mach-s3c2440\Mach-smdk2440.c
这里我们看到这个结构体和上面的相识,猜测这个结构体被强制被设置一个属性,把它的段设置为.arch.info.init,然后被整合进vmlinux.lds中的*(.arch.info.init)
这个结构体被定义在
这样,这个有关于2440的结构体被编译进vmlinux.lds,内核就支持这个单板
4.跳出__lookup_machine_type回到head.S继续读
建立页表
为什么要建立页表,看到vmlinux.lds中
我们在lds中的是虚拟地址,但是我们的真实地址是0x30000000开始的,所以我们要建立一个页表,启动MMU
下面就有说到enable MMU,使能MMU后,就会跳到__switch_data
进去_switch_data 往下看,发现
start_kernel是内核的第一个c函数,所以head.S工作到这里交给了start_kernel
我们回忆head.S做了什么:
1.判断是否支持这个CPU(机器ID)
2.判断是否支持这个单板
3.建立页表,启动MMU
4.跳到start_kernel
但是我们想到u-boot传进的参数中机器ID被使用了,但是启动参数还没有分析,于是我们还要进去
start_kernel分析
这里我们叫head.S的工作为内核引导阶段
5.跳到start_kernel(内核的第一个c函数)
这里有各种初始化,中断,打印(打印内核版本信息等)等
注:c语言常用标准输出printf,但是内核是用系统调用printk
而就是内核来处理u-boot传进的启动参数)
图为u-boot传进的启动参数
我们进去setup_arch(&command_line);看到
boot_params不就是theKernel (0, bd->bi_arch_number, bd->bi_boot_params);中的bi_boot_params,这里传进去的0x30000100
再下面是一些tag的处理(u-boot传进的tag)
这里有一个
parse_cmdline(cmdline_p, from); ——(from在开头=default_command_line默认命令参数)
这函数的功能是解析命令行参数,对应的是u-boot参数中的
在我自己的开发板是(挂载根文件系统,这里可以让root=/dev/mtdblockX,也可以root=/dev/nfs用于NFS挂载共享文件根文件系统)——这里是用于让内核知道根文件系统的存放的路径,后面再分析
这样u-boot的启动参数就处理完了
现在要到我们的终极目的——应用程序(首先要挂载根文件系统)
6.寻找挂载根文件系统的函数
我们初始化了一系列东西,继续看下去,整理出如下关系,找到了mout_root()——挂载根文件系统
在linux-2.6.22.6\init\Do_mounts.c中
假设我们挂载好了根文件系统,按道理可以执行应用程序了
退回上一个文件,看到init_post();
init_post()做了什么事:
(1)sys_open((const char __user *) "/dev/console", O_RDWR, 0)——打开控制台
(2)——执行应用程序
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
但是如果我们没有挂载根文件系统,怎么办
我们想到上面我们说的参数bootargs,是用来让内核知道根文件系统存放的位置
那么内核源码中怎么找到这个bootargs
我们在图片看到有一个ROOT_DEV,那这个ROOT_DEV等于谁
看到saved_root_name,我们查找它的定义
我们内核在分析bootargs,发现了root=/dev/xxx,然后调用root_dev_setup把这个root=/dev/xxx保存到saved_root_name
在继续分析找到__serup的结构体,这个结构体把里面的属性定义为.init.setup(在vmlinux有定义)
现在我们知道了这个mout_root()是由bootargs决定的
但是我们想linux的Flash是没有分区的,显然是代码中写死的
在linux-2.6.22.6\arch\arm\plat-s3c24xx\Common-smdk.c有定义,我们可以在里面修改
于是,内核启动的第二阶段:
总结内核启动:
1.head.s
1.判断是否支持这个CPU(机器ID)
2.判断是否支持这个单板
3.建立页表,启动MMU
4.跳到start_kernel
2.解析u-boot传进的参数
3.挂载根文件系统、执行第一个应用程序
4.分区