http://blog.chinaunix.net/uid-20543672-id-3019566.html
在分析Linux-3.0内核启动的时,当分析到自解压后的汇编部分,发现head.S (arch\arm\kernel)中并没有对machine_type作任何的检查,只是检查了处理器ID(__lookup_processor_type)。
在2.6.38及以前的代码:
- __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 @ get processor id
- bl __lookup_processor_type @ r5=procinfo r9=cpuid
- movs r10, r5 @ invalid processor (r5=0)?
- THUMB( it eq ) @ force fixup-able long branch 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 long branch encoding
- beq __error_a @ yes, error 'a'
bl__vet_atags
2.6.39-rc1及其之后的代码:
- __HEAD
- ENTRY(stext)
- setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ CPU模式设置宏
- @ (进入svc模式并且关闭中断)
- mrc p15, 0, r9, c0, c0 @ 获取处理器id-->r9
- bl __lookup_processor_type @ 返回r5=procinfo r9=cpuid
- movs r10, r5 @ r10=r5,并可以检测r5=0?注意当前r10的值
- THUMB( it eq ) @ force fixup-able long branch encoding
- beq __error_p @ yes, error 'p'如果r5=0,则内核处理器不匹配,出错~死循环
-
- /*
- * 获取RAM的起始物理地址,并保存于 r8 = phys_offset
- * XIP内核与普通在RAM中运行的内核不同
- * (1)CONFIG_XIP_KERNEL
- * 通过运行时计算????
- * (2)正常RAM中运行的内核
- * 通过编译时确定(PLAT_PHYS_OFFSET 一般在arch/arm/mach-xxx/include/mach/memory.h定义)
- *
- */
- #ifndef CONFIG_XIP_KERNEL
- adrr3, 2f
- ldmiar3, {r4, r8}
- subr4, r3, r4@ (PHYS_OFFSET - PAGE_OFFSET)
- addr8, r8, r4@ PHYS_OFFSET
- #else
- ldrr8, =PLAT_PHYS_OFFSET
- #endif
-
- /*
- * r1 = machine no, r2 = atags or dtb,
- * r8 = phys_offset, r9 = cpuid, r10 = procinfo
- */
- bl__vet_atags@ 判断r2(内核启动参数)指针的有效性
这引起了我的注意,难道内核不检查bootloader通过r1传递过来的machine_type参数了吗?
我的第一反应就是在git中找到相应的commit,看看其中有什么关于删除 __lookup_machine_type消失的解释。
首先确定了消失的版本是在2.6.38到2.6.39-rc1之间,让后通过 git whatchanged找到有修改arch/arm/kernel/head.S的commit,从中找到了删除 __lookup_machine_type的commit:
- commit 6fc31d54443bdc25a8166be15e3920a7e39d195d
- Author: Russell King
- Date: Wed Jan 12 17:50:42 2011 +0000
- ARM: Defer lookup of machine_type to setup.c
- ARM:推迟machine_type的检查
- Since the debug macros no longer depend on the machine type information,
- the machine type lookup can be deferred to setup_arch() in setup.c which
- simplifies the code somewhat.
- 由于调试宏不再依赖机器类型(machine type)信息,
- 机器类型(machine type)的查找可以被推迟到 setup.c 中的 setup_arch(),
- 这样从一定程度上简化了代码。
- (译者注:汇编变成了C代码,必然简化了编程,提高了移植性和通用性)
- We also move the __error_a functionality into setup.c for displaying a
- message when a bad machine ID is passed to the kernel via the LL debug
- code. We also log this into the kernel ring buffer which makes it
- possible to retrieve the message via a debugger.
- 我们同时也将__error_a函数移动到了setup.c中,
- 当一个错误的机器ID被传递给内核,它可以通过LL(底层)调试代码显示信息。
- 我们也将这个信息放进内核环型缓冲,使它可能通过调试器被检索到。
- Original idea from Grant Likely.
- 原始的想法来源于Grant Likely(格兰特 莱克里)
- Acked-by: Grant Likely
- Tested-by: Tony Lindgren
- Signed-off-by: Russell King
- :100644 100644 8f57515... c84b57d... M arch/arm/kernel/head-common.S
- :100644 100644 814ce1a... 6b1e0ad... M arch/arm/kernel/head-nommu.S
- :100644 100644 c0225da... 8a154b9... M arch/arm/kernel/head.S
- :100644 100644 420b8d6... 78678b0... M arch/arm/kernel/setup.c
从这里我们可以知道,其实这个__lookup_machine_type消失不是被删除了,而是这个功能被推迟到了C代码中:
init/main.c
- asmlinkage void __init start_kernel(void)
- {
- char * command_line;
- extern const struct kernel_param __start___param[], __stop___param[];
-
- smp_setup_processor_id();
-
- /*
- * Need to run as early as possible, to initialize the
- * lockdep hash:
- */
- lockdep_init();
- debug_objects_early_init();
-
- /*
- * Set up the the initial canary ASAP:
- */
- boot_init_stack_canary();
-
- cgroup_init_early();
-
- local_irq_disable();
- early_boot_irqs_disabled = true;
-
- /*
- * Interrupts are still disabled. Do necessary setups, then
- * enable them
- */
- tick_init();
- boot_cpu_init();
- page_address_init();
- printk(KERN_NOTICE "%s", linux_banner);
- setup_arch(&command_line);
arch/arm/kernel/setup.c
- void __init setup_arch(char **cmdline_p)
- {
- struct machine_desc *mdesc;
-
- unwind_init();
-
- setup_processor();
- mdesc = setup_machine_fdt(__atags_pointer);
- if (!mdesc)
- mdesc = setup_machine_tags(machine_arch_type);
- ......
这个函数首先检测bootloader导入的启动参数是否包含了fdt(flattened device tree扁平设备树)。这个函数一开始会核对启动参数区中是否有匹配的FDT魔数(OF_DT_HEADER),如果没有,则mdesc==NULL。那么下面就执行setup_machine_tags(machine_arch_type);。
而在我们正常的情况下,ARM的构架中我没有见过实现扁平设备树的,这个似乎是原先PPC的东西,具体的资料请看:《全面解析PowerPC架构下的扁平设备树FDT》,所以可以说正常情况下都是去执行setup_machine_tag。
这里还要解释一下此处调用setup_machine_tag的参数:machine_arch_type,这个其实就是全局变量__machine_arch_type(这是用了#define,参加arch/arm/tools/gen-mach-types),而这个__machine_arch_type中的数据是由内核启动代码的汇编部分(arch/arm/kernel/head.S)中将bootloader传递进来的r1数据拷贝得来的:
- /*
- * 以下的代码段是在MMU开启的状态下执行的,
- * 而且使用的是绝对地址; 这不是位置无关代码.
- *
- * r0 = cp#15 控制寄存器值
- * r1 = machine ID
- * r2 = atags/dtb pointer
- * r9 = processor ID
- */
- __INIT
- __mmap_switched:
- adr r3, __mmap_switched_data
- ldmia r3!, {r4, r5, r6, r7}
- cmp r4, r5 @ 如果有必要,拷贝数据段。
- @ 对比__data_loc和_sdata
- @ __data_loc是数据段在内核代码映像中的存储位置
- @ _sdata是数据段的链接位置(在内存中的位置)
- @ 如果是XIP技术的内核,这两个数据肯定不同
- 1: cmpne r5, r6 @ 检测数据是否拷贝完成
- ldrne fp, [r4], #4
- strne fp, [r5], #4
- bne 1b
- mov fp, #0 @ 清零 BSS 段(and zero fp)
- 1: cmp r6, r7 @ 检测是否完成
- strcc fp, [r6],#4
- bcc 1b
- /* 这里将需要的数据从寄存器中转移到全局变量中,
- * 因为最后会跳入C代码,寄存器会被使用。
- */
- ARM( ldmia r3, {r4, r5, r6, r7, sp})
- THUMB( ldmia r3, {r4, r5, r6, r7} )
- THUMB( ldr sp, [r3, #16] )
- str r9, [r4] @ 保存 processor ID到全局变量processor_id
- str r1, [r5] @ 保存 machine type到全局变量__machine_arch_type
- str r2, [r6] @ 保存 atags指针到全局变量__atags_pointer
- bic r4, r0, #CR_A @ 清除cp15 控制寄存器值的 'A' bit(禁用对齐错误检查)
- stmia r7, {r0, r4} @ 保存控制寄存器值到全局变量cr_alignment(在arch/arm/kernel/entry-armv.S)
- b start_kernel @ 跳入C代码(init/main.c)
- ENDPROC(__mmap_switched)
所以我们可以说:machine_arch_type == r1。
下面我们接着分析setup_machine_tag:
- static struct machine_desc * __init setup_machine_tags(unsigned int nr)
- {
- struct tag *tags = (struct tag *)&init_tags;
- struct machine_desc *mdesc = NULL, *p;
- char *from = default_command_line;
-
- init_tags.mem.start = PHYS_OFFSET;
-
- /*
- * locate machine in the list of supported machines.
- */
- for_each_machine_desc(p)
- if (nr == p->nr) {
- printk("Machine: %s\n", p->name);
- mdesc = p;
- break;
- }
-
- if (!mdesc) {
- early_print("\nError: unrecognized/unsupported machine ID"
- " (r1 = 0x%08x).\n\n", nr);
- dump_machine_table(); /* does not return */
- }
其中的(nr == p->nr)就是核对machine_arch_type:nr就是上面谈到的参数,p->nr其实就是在arch/arm/mach-*/mach-*.c中定义的结构体:MACHINE_START~MACHINE_END
这个宏的定义在arch/arm/include/asm/mach/arch.h
- /*
- * Set of macros to define architecture features. This is built into
- * a table by the linker.
- */
- #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 \
- };
例如arch/arm/mach-s3c64xx/mach-mini6410.c 查看下面这个结构体:
- MACHINE_START(MINI6410, "MINI6410")
- /* Maintainer: Darius Augulis */
- .boot_params = S3C64XX_PA_SDRAM + 0x100,
- .init_irq = s3c6410_init_irq,
- .map_io = mini6410_map_io,
- .init_machine = mini6410_machine_init,
- .timer = &s3c24xx_timer,
- MACHINE_END
所以我们可以这么说:如果r1和Linux内核代码定义的MACHINE_START(_type,_name)不匹配,那么就会打印出:
- Error: unrecognized/unsupported machine ID
并且运行 dump_machine_table():
- void __init dump_machine_table(void)
- {
- struct machine_desc *p;
-
- early_print("Available machine support:\n\nID (hex)\tNAME\n");
- for_each_machine_desc(p)
- early_print("%08x\t%s\n", p->nr, p->name);
-
- early_print("\nPlease check your kernel config and/or bootloader.\n");
-
- while (true)
- /* can't use cpu_relax() here as it may require MMU setup */;
- }
同样死循环,挂了~~~!!!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
所以我们可以得出结论:虽然 __lookup_machine_type消失了,并不代表内核不检查bootloader通过r1传递过来的machine_type参数,只是把检查机制推迟到了C代码中(除非你实现了FDT)。