kernel之内核启动分析
总览
本文使用 linux-2.6.22.6 内核, 使用jz2440开发板.
内核引导阶段 head.S
由 uboot之源码分析 可知, uboot最后调用的函数是
theKernel (0, bd->bi_arch_number, bd->bi_boot_params)
, 会把一些板级参数传递给linux内核使用.
由 kernel之Makefile分析 可知, 两个重要的文件如下:
- 第1个文件:
arch/arm/kernel/head.S
- 链接脚本:
arch/arm/kernel/vmlinux.lds
head.S
主要完成了如下工作.
__lookup_machine_type
会将 theKernel 传入的单板类型与内核支持的单板类型逐一比较..arch.info.init
这一个段存放的就是内核支持的单板类型.- 定义在
./arch/arm/kernel/vmlinux.lds
__lookup_machine_type
会对该段进行轮询- 该段的数据结构为
machine_desc
, 定义在./include/asm-arm/mach/arch.h
arch.h
文件还宏定义了MACHINE_START
MACHINE_END
,
调用该宏定义, 就会将相关数据放在.arch.info.init
段内
- 定义在
./arch/arm/mach-s3c2440/mach-smdk2440.c:
内调用了MACHINE_START(S3C2440, "SMDK2440")
- kernel下
grep -nr MACH_TYPE_S3C2440
可知 MACH_TYPE_S3C2440 的值为 362 - uboot下
grep -nr bi_arch_number
可知其值为 MACH_TYPE_S3C2440, 即 362 - 这样, kernel就能确定是支持当前的开发板的. (当前的开发板信息由uboot提供给kernel)
- kernel下
内核启动第二阶段 init/main.c
head-common.S
调用 start_kernel
后, 代码会转到 init/main.c
, 使用的是C语言.
从 start_kernel
开始, 函数的基本调用关系如下:
setup_arch(&command_line);
读取uboot传过来的TAG参数- 位于
./arch/arm/kernel/setup.c
770 行 - uboot 的
setup_memory_tags
存放内存大小 - uboot 的
setup_commandline_tag
告知linux挂载文件系统的命令行 - uboot 的
/board/100ask24x0/100ask24x0.c
中,gd->bd->bi_boot_params = 0x30000100
- 使用了
machine_desc
结构体,.boot_params = S3C2410_SDRAM_PA + 0x100
, 其值就是0x30000100
- 至此, uboot存放在内存中的TAG参数, 就能被内核读取出来了.
- 此函数还会把挂载文件系统的命令赋值给
boot_command_line
, 后续会去处理. parse_cmdline
会去处理放在early_param.init
段的指令.
- 位于
parse_early_param
, 此函数会把boot_command_line
传递给do_early_param
让其执行挂载文件系统的指令.do_early_param
- 搜索
.init.setup
段, 调用有early标记的函数 (大多只是设置一下参数)
- 搜索
unknown_bootoption
(使用 parse_args 先检查参数后再执行该函数)obsolete_checksetup
- 搜索
.init.setup
段, 调用非early标记的函数 (大多只是设置一下参数)
- 搜索
rest_init
, 去做剩余的初始化工作kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
首先就是创建一个线程来做初始化工作kernel_init
会调用prepare_namespace
- 在
do_mounts.c
文件下prepare_namespace
调用mount_root
- 目的就是挂载文件系统.
- 在
- 挂载完文件系统后, 调用
init_post
, 尝试执行第一个应用程序(下述应用程序不会再返回)run_init_process(execute_command);
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
- 以默认的启动指令为例
"root=/dev/hda1 ro init=/bin/bash console=ttySAC0"
- `__setup("root=", root_dev_setup);` 位于 `./init/do_mounts.c` - `__setup("init=", init_setup);` 位于 `./init/main.c` - `__setup("console=", console_setup);` 位于 `./kernel/printk.c` - 以上命令对应的函数都会由 `obsolete_checksetup` 函数来调用. 主要是设置好相关参数.
// ./init/main.c |
# 我们擦除开发板文件系统后, 来看一下打印出来的启动信息 |
如果正常启动, 会打印如下信息, 可知第一个应用程序名字是 BusyBox
, 然后启动了 /bin/sh
!!!init started: BusyBox v1.7.0 (2008-01-22 10:04:09 EST)
starting pid 764, tty '': '/etc/init.d/rcS'
Please press Enter to activate this console.
starting pid 770, tty '/dev/s3c2410_serial0': '/bin/sh'
初始化中用到的段 vmlinux.lds
.init.setup
段分析
在 ./arch/arm/kernel/vmlinux.lds
中有这么一个段*(.init.setup)
, 其头尾为 __setup_start
__setup_end
这一个段存放着需要通过读取命令行来执行的初始化工作, 内核称之为 unknow_xxx
定义在此段的变量通过两个宏定义实现 __setup
以及 early_param
简单理解的话, early_param
是需要先执行的命令行, __setup
则稍后执行.
# ./include/linux/init.h |
先来分析 __setup(str, fn)
这么一个宏定义, 它设置的就是命令行队列, 将字符串关联到函数.
unique_id
就是fn的函数地址- 存放在
.init.setup
段内. - 与数据结构
obs_kernel_param
相关. 保存的就是 str, fn, 以及early三个变量. - 通过这样一种结构, 可以很方便的使用字符串来调用指定的函数.
grep -nwr __setup ./arch/arm
可找到__setup("tft=", qt2410_tft_setup);
, 很明显就是用来初始化屏幕的.- 在
./init/do_mounts.c
里, 还能找到__setup("root=", root_dev_setup)
, 是用来挂载文件系统的.
然后看 early_param(str, fn)
宏定义.
__setup
是一样的.- 只是将标记
early
赋值为1. 表示需要较早执行的初始化工作, 一般为更底层的驱动 - arm 架构几乎没有用到
early_param
的地方, 搜索的话, 可以看到 pci 的初始化就用到了early_param
early_param.init
段
和 .init.setup
段类似, 其定义在./include/asm-arm/setup.h
, 220行.
关联的宏定义为 __early_param
, 初始化如 initrd=
mem=
之类的命令grep -nwr __early_param
进行查询, 结果不多的.
搜索 grep -nwr __early_begin
, 可知, 其处理函数为 parse_cmdline
__param
段
该段主要用于给驱动模块的初始化. 设置参数使用.
定义在 ./include/linux/moduleparam.h
, 72 行
其调用位于 ./init/main.c
的 parse_args
(grep -nwr __start___param
)
关联的宏定义为 module_param_call
module_param_named
module_param
以及 module_param_string
查阅后, 可以发现全部是和驱动模块有关的.
linux下的分区
jz2440的启动参数为 root= /dev/mtdblock3
,
linux下没有分区表的概念, 这里分区是在内核源码里写死的.
可见 ./arch/arm/plat-s3c24xx/common-smdk.c
的118行, smdk_default_nand_part
它分配了4个的分区:
分区 | 对应mtdblock | size |
---|---|---|
bootloader |
mtdblock0 | 0x00040000 |
params |
mtdblock1 | 0x00020000 |
kernel |
mtdblock2 | 0x00200000 |
root |
mtdblock3 | MTDPART_SIZ_FULL |
更多参考
原创于 DRA&PHO