源码配置编译:
本文主要是针对linux-2.6.22.6来进行配置编译和源码的分析,这里韦老师已经做好补丁文件和配置文件,直接在此基础上进行配置编译
解压缩
tar xjf 压缩文件
打补丁
patch -p1 < 补丁文件 (这里韦老师已经为我们做好了补丁文件)
配置
linux有两种配置方式
1. 通过make xxx_defconfig命令在默认的配置上进行修改,然后再输入make menuconfig配置菜单
make s3c2410_defconfig (根据自己的板子选择)
配置完后可以看到打印信息中最后出现“configuration written to .config”,表示将所有config配置都写入到.config文件中
make menuconfig (启动X-windows图形配置界面,选择一些内核的配置选项)
2.使用厂家提供的配置config_ok文件 (我们这里使用的就是韦老师配置好的config_ok文件)
在linux-2.6.22.6目录下,使用cp config_ok .config命令将config_ok复制成新的.config文件(通过 ls -la 命令可以查看隐藏文件.config),
然后执行make menuconfig 回去读取.config文件
问题: make menuconfig时出现段错误
Makefile:14: recipe for target 'menuconfig' failed make[1]: *** [menuconfig] Segmentation fault (core dumped)
Makefile:417: recipe for target 'menuconfig' failed make: *** [menuconfig] Error 2
解决办法:由于ubuntu的版本过新不能编译老的版本所以需要安装一些库
sudo apt-get install libncurses*
make menuconfig时提示makefile错误
linux-2.6.22.6/Makefile:416: *** mixed implicit and normal rules: deprecated syntax
linux-2.6.22.6/Makefile:1449: *** mixed implicit and normal rules: deprecated syntax
解决办法:系统的make工具太新,make的旧版规则已经无法兼容新版
config %config: scripts_basic outputmakefile FORCE 改为%config: scripts_basic outputmakefile FORCE
/ %/: prepare scripts FORCE 改为%/: prepare scripts FORCE
编译
make uImage
Makefile分析:
要想快速的分析源码,一般通过分析配置编译的过程快速切入以了解实际的启动流程,我们之前通过make s3c2410_defconfig命令对内核源码进行配置过后会生成一个.config文件,首先需要分析.config文件的内容。
CONFIG_DM9000=y 配置项 =y表示选中,会被编译进内核
以dm9000为例,在源码中搜索可以知道,在makefile中使用过CONFIG_DM9000,但是这个是由autoconf.h文件中定义的,然后我们平常的配置CONFIG_DM9000=y 就是生成了在autoconf.h中将CONFIG_DM9000定义了
drivers/net/dm9ks.c:445:#define CONFIG_DM9000_BASE 0x20000000
drivers/net/dm9ks.c:446: iobase = ioremap(CONFIG_DM9000_BASE, 0x100000) + 0x300;
drivers/net/Makefile:197:obj-$(CONFIG_DM9000) += dm9dev9000c.o
drivers/net/Makefile:198:#obj-$(CONFIG_DM9000) += dm9000.o
drivers/net/Makefile:199:#obj-$(CONFIG_DM9000) += dm9ks.o
include/linux/autoconf.h:145:#define CONFIG_DM9000 1
include/config/auto.conf:144:CONFIG_DM9000=y
CONFIG_DM9000=m就会被编辑成模块,这些.o文件就会被连接成.ko文件
#obj-$(CONFIG_DM9000) += dm9000.o
#obj-$(CONFIG_DM9000) += dm9ks.o
所以当我们执行make uImage时就执行了.config文件中的内容,然后就生成了include/linux/autoconf.h 和 include/config/auto.conf 这两个文件
对于所有的makefile,内核中有一个文件专门进行了讲解 linux-2.6.22.6\Documentation\kbuild\makefiles.txt 有时间可以仔细细看
然后我们方便分析,直接从编译命令 make uImage入手,我们发现在顶层的makefile中并没有发现uImage的踪影,相反在arch/arm/makefile文件中有
zImage Image xipImage bootpImage uImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
所以推测在顶层的makefile中肯定包含了arm目录下的makefile,然后查找可得
ARCH ?= arm
include $(srctree)/arch/$(ARCH)/Makefile
然后我们从arm下可知uImage依赖于vmlinux,在顶层中,第一个目标就是vmlinux
# The all: target is the default when no target is given on the
# command line.
# This allow a user to issue only 'make' to build a kernel including modules
# Defaults vmlinux but it is usually overridden in the arch makefile
all: vmlinux
我们再找到vmlinux的依赖
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) FORCE
再继续找到这些依赖指向哪里
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-all := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds := arch/$(ARCH)/kernel/vmlinux.lds
head-y在顶层里没有定义,在arm下有个定义
head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o MMUEXT没有定义就变成了空的 arch/arm/kernel/head.o
init-y := init/
init-y := $(patsubst %/, %/built-in.o, $(init-y)) ---> init-y = init//built-in.o
core-y := usr/
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
core-y := $(patsubst %/, %/built-in.o, $(core-y)) ---> core-y = usr/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
libs-y := lib/
libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y := $(libs-y1) $(libs-y2) ---> libs-y = lib/lib.a lib/built-in.o
drivers-y := drivers/ sound/
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) ---> drivers-y := drivers/ sound/built-in.o
net-y := net/
net-y := $(patsubst %/, %/built-in.o, $(net-y)) ---> net-y := net/built-in.o
想知道上面的这些文件如何编译到内核,直接编译内核看它的编译过程
make uImage v=1 (v=1 表示更详细的打印编译信息)然后查看编译流程,找到有用的信息:
arm-linux-ld -EL -p --no-undefined -X -o vmlinux -T arch/arm/kernel/vmlinux.lds -> $(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 -> head-y
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/nwfpe/built-in.o
arch/arm/plat-s3c24xx/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 net/built-in.o --end-group .tmp_kallsyms2.o -> libs-y
然后通过上面的对比,就能直接对应起来,不需要再去找每个依赖的下一级依赖,从这里我们知道了我们的链接脚本位于arch/arm/kernel/vmlinux.lds ,第一个链接的文件arch/arm/kernel/head.s
启动流程分析:
从head.s入手,
.section ".text.head", "ax"
.type stext, %function
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode//确保进入管理模式(设置 CPSR寄存器)
@ and irqs disabled //禁止中断
mrc p15, 0, r9, c0, c0 @ get processor id //读取CPU ID(CP15寄存器),存入r9寄存器
bl __lookup_processor_type @ r5=procinfo r9=cpuid// 调用函数,输入参数r9=cpuid,返回值r5=procinfo 判断是否支持cpu
movs r10, r5 @ invalid processor (r5=0)?//如果不支持当前cpu,则返回值r5=0
beq __error_p @ yes, error 'p' //如果r5=0,则打印错误
bl __lookup_machine_type @ r5=machinfo // 调用函数,返回值r5=machinfo,判断是否支持单板
movs r8, r5 @ invalid machine (r5=0)?//如果不支持当前机器,则返回值r5 =0
beq __error_a @ yes, error 'a' // 如果r5=0,则打印错误
bl __create_page_tables//创建页表 建立虚拟地址和物理地址的映射关系为了启动mmu
上面主要是实现几个流程
1. 判断是否支持处理器 -> bl __lookup_processor_type
2. 判断是否支持单板(根据uboot传入的机器id) -> bl __lookup_machine_type
__lookup_machine_type的实现:
= (0xc0000000) + 0x00008000;
text.head : {
_stext = .;
_sinittext = .;
*(.text.head)
init : { /* Init code and data */
*(.init.text)
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .; /*链接脚本中__arch_info_begin的地址(虚拟地址)*/
*(.arch.info.init)
__arch_info_end = .;
3: .long . @表示当前这行代码编译链接后的虚拟地址
.long __arch_info_begin
.long __arch_info_end
__lookup_machine_type:
adr r3, 3b //r3等于3b的地址 物理地址 __arch_info_begin
ldmia r3, {r4, r5, r6} //从r3里读取内容到r4,r5,r6,r4 = "." r5 ="__arch_info_begin" r6 ="__arch_info_end"
sub r3, r3, r4 @ get offset between virt&phys//物理地址和虚拟地址的偏移量
add r5, r5, r3 @ convert virt addresses to//计算r5,r6对应的物理地址
add r6, r6, r3 @ physical address space
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type //这下面就是开始不断比较传入的机器id是否能够被改内核支持
teq r3, r1 @ matches loader number? //r1存放的就uboot存进来的机器id
beq 2f @ found 从*(.arch.info.init)段中对比id,找到就退出
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc 没找到继续往下找直到地址到末尾__arch_info_end
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr //比较完成就会返回直接调用处
这种将机器id如何存放到链接脚本中指定的段中,我们可以从他的实现来分析,搜索.arch.info.init可以知道
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
然后找到调用MACHINE_START宏的函数,这里就是2440,可以看出来每种单板都会调用该函数
arch.h (E:\print\linux-2.6-100ask\linux-2.6.22.6\include\asm-arm\mach) line 50 : #define MACHINE_START(_type,_name) \
mach-amlm5900.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 247 : MACHINE_START(AML_M5900, "AML_M5900")
mach-anubis.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2440) line 317 : MACHINE_START(ANUBIS, "Simtec-Anubis")
mach-bast.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 500 : MACHINE_START(BAST, "Simtec-BAST")
mach-h1940.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 239 : MACHINE_START(H1940, "IPAQ-H1940")
mach-n30.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 121 : MACHINE_START(N30, "Acer-N30")
mach-nexcoder.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2440) line 148 : MACHINE_START(NEXCODER_2440, "NexVision - Nexcoder 2440")
mach-osiris.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2440) line 343 : MACHINE_START(OSIRIS, "Simtec-OSIRIS")
mach-otom.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 115 : MACHINE_START(OTOM, "Nex Vision - Otom 1.1")
mach-qt2410.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 433 : MACHINE_START(QT2410, "QT2410")
mach-rx3715.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2440) line 231 : MACHINE_START(RX3715, "IPAQ-RX3715")
mach-smdk2410.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 198 : MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
mach-smdk2440.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2440) line 339 : MACHINE_START(S3C2440, "SMDK2440")
mach-vr1000.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 422 : MACHINE_START(VR1000, "Thorcom-VR1000")
我们将2440的宏展开来分析
#define MACHINE_START(_type,_name) \ MACHINE_START(S3C2440, "SMDK2440")
static const struct machine_desc __mach_desc_##_type \ /* Maintainer: Ben Dooks <ben@fluff.org> */
__used \ .phys_io = S3C2410_PA_UART,
__attribute__((__section__(".arch.info.init"))) = { \ .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.nr = MACH_TYPE_##_type, \ .boot_params = S3C2410_SDRAM_PA + 0x100,
.name = _name,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
#define MACHINE_END \ .init_machine = smdk2440_machine_init,
}; .timer = &s3c24xx_timer,
MACHINE_END
static const struct machine_desc __mach_desc_##S3C2440 \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##S3C2440, \
.name = "SMDK2440",
.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,
};
这样之后S3C2410所有的信息都存在了machine_desc结构体中,然后这个结构体被定义了一个属性,段强制设成了".arch.info.init",存放在链接脚本中的指定地址中,所以对于不同的开发板传入各自的machine_desc结构体就好了,然后将各自结构体的文件编进内核就能支持该单板
3. 建立页表 -> bl __create_page_table
4. 使能MMU -> adr lr, __enable_mmu
5. 复制数据段,清bss等 -> __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) //清除bss段
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, sp} //设置栈指针
str r9, [r4] @ Save processor ID //保存CPU_ID到processor_id变量
str r1, [r5] @ Save machine type //保存机器id
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values //跳转到kernel
b start_kernel
6. 跳转到start_kernel,进入内核启动的第二阶段 -> b start_kernel
第二阶段函数解析:
start_kernel里主要是一系列的初始化函数,里面跟单板挂钩比较重要的是
setup_arch(&command_line)函数分析:
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;
setup_processor(); // 进行处理器的相关一些设置 获得proc_info_list结构 就是cpu相关的信息
mdesc = setup_machine(machine_arch_type); // 获得开发板的machine_desc结构 就是单板相关的信息
machine_name = mdesc->name;
if (mdesc->soft_reboot) // 定义了bootloader传入参数的地址
reboot_setup("s");
if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params); // 这个地址就是tag列表的首地址 0x30000100
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0) //如果在内核中已经定义了meminfo结构
squash_mem_tags(tags); // 则忽略内存tag
parse_tags(tags); // 解释每个tag 调用tagtable里面的函数,对各种tag进行操作
}
init_mm.start_code = (unsigned long) &_text;
init_mm.end_code = (unsigned long) &_etext;
init_mm.end_data = (unsigned long) &_edata;
init_mm.brk = (unsigned long) &_end;
memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
parse_cmdline(cmdline_p, from);//解析uboot传进来的命令行,如果没有传进来就使用默认的default_command_line
paging_init(&meminfo, mdesc); // 重新初始化页表
大致流程如下所示:
parse_tags(tags)函数的实现,主要是依赖于“.taglist.init”段预先已经存放了各个tag的函数,然后通过for循环逐步调用各个tag函数的实现,以parse_tag_mem32为例:
#define __tag __used __attribute__((__section__(".taglist.init")))
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn }
static int __init parse_tag_mem32(const struct tag *tag)
{
if (meminfo.nr_banks >= NR_BANKS) {
printk(KERN_WARNING
"Ignoring memory bank 0x%08x size %dKB\n",
tag->u.mem.start, tag->u.mem.size / 1024);
return -EINVAL;
}
arm_add_memory(tag->u.mem.start, tag->u.mem.size); //增加内存的描述信息
return 0;
}
__tagtable(ATAG_MEM, parse_tag_mem32);
__attribute__是一个特殊的GNU关键字,在这里的用法是:告诉编译器需要将其作用的函数或者数据放入”.taglist.init”这一段区域,也就是说由__tagtable定义的函数将会被放在section“.taglist.init” 这个区域,所以在parse_tag()做for循环调用的时候,必然会调用到parse_tag_mem32()。其中一点要注意的是,parse_tag_mem32()的TAG为ATAG_MEM, 所以在boot传过来的TAG参数如果是要定义为memory参数的话TAG一定要定义为ATAG_MEM,否则parse_tag_mem32()是无法解析到的!
console_init()函数分析:
call = __con_initcall_start;
while (call < __con_initcall_end) {
(*call)();
call++;
}
这里只要是调用__con_initcall_start和__con_initcall_end之间定义的每个函数,这些函数都存在连接脚本中的 *(.con_initcall.init)中,搜索脚本文件
__con_initcall_start = .;
*(.con_initcall.init)
__con_initcall_end = .;
rest_init()函数分析:
rest_init()主要功能是挂接根文件系统然后执行应用程序,具体流程如下
rest_init()
kernel_init();
prepare_namespace();
mount_root(); //挂接根文件系统
init_post(); //打开dev/console,执行应用程序
启动参数的分析:
以挂接根文件系统为例,分析内核是怎么处理这些命令行参数的。例如在命令行中有个参数为"root=/dev/mtdblock3",那么内核是如何将该参数记录并执行的呢?
在rest_init()函数中有个prepare_namespace()主要的任务就是挂接根文件系统的,代码如下
void __init prepare_namespace(void)
{
int is_floppy;
if (root_delay) {
printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
root_delay);
ssleep(root_delay);
}
/* wait for the known devices to complete their probing */
while (driver_probe_done() != 0)
msleep(100);
md_run_setup();
if (saved_root_name[0]) { //查看是否存在root这个参数(其实这个参数就是我们读取的命令行)
root_device_name = saved_root_name;
if (!strncmp(root_device_name, "mtd", 3)) {
mount_block_root(root_device_name, root_mountflags); //去该地址的地方去挂接
goto out;
}
ROOT_DEV = name_to_dev_t(root_device_name);
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}
is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
if (initrd_load())
goto out;
if (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;
mount_root(); //挂接根文件系统
这里最终实现的是 mount_root()函数,实现挂接根文件系统,在这之前我们可以看到还有一些参数需要设置,例如从哪个地址挂接,这个就跟我们要分析的传入的命令行参数有关了,在mount_root()之前我们可以看到saved_root_name[0]就是我们之前保存的参数,然后对该参数判断再执行该参数,所以对别的参数来说肯定也是类型,先对保存的参数做判断,然后再执行,那么我们是如何实现对参数的保存的呢,我们可以搜索saved_root_name,然后可以看到如下代码:
static int __init root_dev_setup(char *line)
{
strlcpy(saved_root_name, line, sizeof(saved_root_name));
return 1;
}
__setup("root=", root_dev_setup);
这里实现的就是当遇到root参数时,__setup("root=", root_dev_setup)函数就会去执行root_dev_setup函数,然后这个root_dev_setup(char *line)函数就会保存这个参数到saved_root_name,__setup("root=", root_dev_setup)的实现跟之前proc_info_list结构体和machine_desc结构体一样,__setup("root=", root_dev_setup)肯定也是将该参数放进一个结构体中,来看下__setup的具体实现
#define __setup(str, fn) \ //存在include/linux/init.h
__setup_param(str, fn, fn, 0)
#define __setup_param(str, unique_id, fn, early) \
static char __setup_str_##unique_id[] __initdata = str; \
static struct obs_kernel_param __setup_##unique_id \
__attribute_used__ \
__attribute__((__section__(".init.setup"))) \ //强制段属性
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_##unique_id, fn, early }
——————> __setup_##unique_id = {str,fn,0}
通过*(.init.setup),可以知道这个肯定又是链接脚本中定义的一个段,在链接脚本中搜索有
__setup_start = .;
*(.init.setup)
__setup_end = .;
好了这些就是我们root参数的保存,在*(.init.setup)段中定义了一个obs_kernel_param类型的结构体,里面的参数就是{"root=",root_dev_setup,0},但是这里我们只是定义了,还没有执行root_dev_setup函数去保存我们的参数至saved_root_name中,所以搜索__setup_start我们可以来看到是如何调用*(.init.setup)段内的函数,我们可以搜到在start_kernel()中有:
parse_early_param()
do_early_param
从setup_start到setup_end 调用early(1)函数
static int __init do_early_param(char *param, char *val)
{
struct obs_kernel_param *p;
for (p = __setup_start; p < __setup_end; p++) {
if (p->early && strcmp(param, p->str) == 0) {
if (p->setup_func(val) != 0)
printk(KERN_WARNING
"Malformed early option '%s'\n", param);
}
}
/* We accept everything at this stage. */
return 0;
}
当early为1的时候才会去调用p->setup_func(val)函数,这里就是root_dev_setup函数,保存root参数
unknown_bootoption
obsolete_checksetup
从setup_start到setup_end 调用非early(0)函数
static int __init obsolete_checksetup(char *line)
{
struct obs_kernel_param *p;
int had_early_param = 0;
p = __setup_start;
do {
int n = strlen(p->str);
if (!strncmp(line, p->str, n)) {
if (p->early) {
/* Already done in parse_early_param?
* (Needs exact match on param part).
* Keep iterating, as we can have early
* params and __setups of same names 8( */
if (line[n] == '\0' || line[n] == '=')
had_early_param = 1;
} else if (!p->setup_func) {
printk(KERN_WARNING "Parameter %s is obsolete,"
" ignored\n", p->str);
return 1;
} else if (p->setup_func(line + n))
return 1;
}
当early为0的时候才会去调用p->setup_func函数,这里就是root_dev_setup函数,保存root参数
调用完成后后面再去执行rest_init()函数去挂接根文件系统,此时root参数已经完全保存完毕,在执行prepare_namespace()函数时就可以直接调用saved_root_names数组里面的值了,其他环境参数的实现过程也是大致的过程
mtdblock3分区在代码中已经写死,就跟uboot传给内核的环境参数一样