Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1432749
  • 博文数量: 1334
  • 博客积分: 645
  • 博客等级: 上士
  • 技术积分: 5762
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-25 16:56
文章分类

全部博文(1334)

文章存档

2014年(108)

2013年(1059)

2012年(169)

分类: LINUX

2013-06-05 15:10:49

     在分析Linux-3.0内核启动的时,当分析到自解压后的汇编部分,发现head.S (arch\arm\kernel)中并没有对machine_type作任何的检查,只是检查了处理器ID(__lookup_processor_type)。
在2.6.38及以前的代码:
  1. __HEAD
  2. ENTRY(stext)
  3. setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
  4. @ and irqs disabled
  5. mrc p15, 0, r9, c0, c0 @ get processor id
  6. bl __lookup_processor_type @ r5=procinfo r9=cpuid
  7. movs r10, r5 @ invalid processor (r5=0)?
  8. THUMB( it eq ) @ force fixup-able long branch encoding
  9. beq __error_p @ yes, error 'p'
  10. bl __lookup_machine_type @ r5=machinfo
  11. movs r8, r5 @ invalid machine (r5=0)?
  12. THUMB( it eq ) @ force fixup-able long branch encoding
  13. beq __error_a @ yes, error 'a'
  bl __vet_atags
2.6.39-rc1及其之后的代码:
  1. __HEAD
  2. ENTRY(stext)
  3. setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ CPU模式设置宏
  4. @ (进入svc模式并且关闭中断)
  5. mrc p15, 0, r9, c0, c0 @ 获取处理器id-->r9
  6. bl __lookup_processor_type @ 返回r5=procinfo r9=cpuid
  7. movs r10, r5 @ r10=r5,并可以检测r5=0?注意当前r10的值
  8. THUMB( it eq ) @ force fixup-able long branch encoding
  9. beq __error_p @ yes, error 'p'如果r5=0,则内核处理器不匹配,出错~死循环

  10. /*
  11. * 获取RAM的起始物理地址,并保存于 r8 = phys_offset
  12. * XIP内核与普通在RAM中运行的内核不同
  13. * (1)CONFIG_XIP_KERNEL
  14. * 通过运行时计算????
  15. * (2)正常RAM中运行的内核
  16. * 通过编译时确定(PLAT_PHYS_OFFSET 一般在arch/arm/mach-xxx/include/mach/memory.h定义)
  17. *
  18. */
  19. #ifndef CONFIG_XIP_KERNEL
  20. adr r3, 2f
  21. ldmia r3, {r4, r8}
  22. sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)
  23. add r8, r8, r4 @ PHYS_OFFSET
  24. #else
  25. ldr r8, =PLAT_PHYS_OFFSET
  26. #endif

  27. /*
  28. * r1 = machine no, r2 = atags or dtb,
  29. * r8 = phys_offset, r9 = cpuid, r10 = procinfo
  30. */
  31. 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:

  1. commit 6fc31d54443bdc25a8166be15e3920a7e39d195d
  2. Author: Russell King
  3. Date: Wed Jan 12 17:50:42 2011 +0000
  4. ARM: Defer lookup of machine_type to setup.c
  5. ARM:推迟machine_type的检查
  6. Since the debug macros no longer depend on the machine type information,
  7. the machine type lookup can be deferred to setup_arch() in setup.c which
  8. simplifies the code somewhat.
  9. 由于调试宏不再依赖机器类型(machine type)信息,
  10. 机器类型(machine type)的查找可以被推迟到 setup.c 中的 setup_arch()
  11. 这样从一定程度上简化了代码。
  12. (译者注:汇编变成了C代码,必然简化了编程,提高了移植性和通用性)
  13. We also move the __error_a functionality into setup.c for displaying a
  14. message when a bad machine ID is passed to the kernel via the LL debug
  15. code. We also log this into the kernel ring buffer which makes it
  16. possible to retrieve the message via a debugger.
  17. 我们同时也将__error_a函数移动到了setup.c中,
  18. 当一个错误的机器ID被传递给内核,它可以通过LL(底层)调试代码显示信息。
  19. 我们也将这个信息放进内核环型缓冲,使它可能通过调试器被检索到。
  20. Original idea from Grant Likely.
  21. 原始的想法来源于Grant Likely(格兰特 莱克里)
  22. Acked-by: Grant Likely
  23. Tested-by: Tony Lindgren
  24. Signed-off-by: Russell King
  25. :100644 100644 8f57515... c84b57d... M arch/arm/kernel/head-common.S
  26. :100644 100644 814ce1a... 6b1e0ad... M arch/arm/kernel/head-nommu.S
  27. :100644 100644 c0225da... 8a154b9... M arch/arm/kernel/head.S
  28. :100644 100644 420b8d6... 78678b0... M arch/arm/kernel/setup.c
  从这里我们可以知道,其实这个__lookup_machine_type消失不是被删除了,而是这个功能被推迟到了C代码中:

init/main.c

  1. asmlinkage void __init start_kernel(void)
  2. {
  3.     char * command_line;
  4.     extern const struct kernel_param __start___param[], __stop___param[];

  5.     smp_setup_processor_id();

  6.     /*
  7.      * Need to run as early as possible, to initialize the
  8.      * lockdep hash:
  9.      */
  10.     lockdep_init();
  11.     debug_objects_early_init();

  12.     /*
  13.      * Set up the the initial canary ASAP:
  14.      */
  15.     boot_init_stack_canary();

  16.     cgroup_init_early();

  17.     local_irq_disable();
  18.     early_boot_irqs_disabled = true;

  19. /*
  20.  * Interrupts are still disabled. Do necessary setups, then
  21.  * enable them
  22.  */
  23.     tick_init();
  24.     boot_cpu_init();
  25.     page_address_init();
  26.     printk(KERN_NOTICE "%s", linux_banner);
  27.     setup_arch(&command_line);

arch/arm/kernel/setup.c
  1. void __init setup_arch(char **cmdline_p)
  2. {
  3.     struct machine_desc *mdesc;

  4.     unwind_init();

  5.     setup_processor();
  6.     mdesc = setup_machine_fdt(__atags_pointer);
  7.     if (!mdesc)
  8.         mdesc = setup_machine_tags(machine_arch_type);
  9. ......
     这个函数首先检测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数据拷贝得来的:
  1. /*
  2. * 以下的代码段是在MMU开启的状态下执行的,
  3. * 而且使用的是绝对地址; 这不是位置无关代码.
  4. *
  5. * r0 = cp#15 控制寄存器值
  6. * r1 = machine ID
  7. * r2 = atags/dtb pointer
  8. * r9 = processor ID
  9. */
  10. __INIT
  11. __mmap_switched:
  12. adr r3, __mmap_switched_data
  13. ldmia r3!, {r4, r5, r6, r7}
  14. cmp r4, r5 @ 如果有必要,拷贝数据段。
  15. @ 对比__data_loc和_sdata
  16. @ __data_loc是数据段在内核代码映像中的存储位置
  17. @ _sdata是数据段的链接位置(在内存中的位置)
  18. @ 如果是XIP技术的内核,这两个数据肯定不同
  19. 1: cmpne r5, r6 @ 检测数据是否拷贝完成
  20. ldrne fp, [r4], #4
  21. strne fp, [r5], #4
  22. bne 1b
  23. mov fp, #0 @ 清零 BSS 段(and zero fp)
  24. 1: cmp r6, r7 @ 检测是否完成
  25. strcc fp, [r6],#4
  26. bcc 1b
  27. /* 这里将需要的数据从寄存器中转移到全局变量中,
  28. * 因为最后会跳入C代码,寄存器会被使用。
  29. */
  30. ARM( ldmia r3, {r4, r5, r6, r7, sp})
  31. THUMB( ldmia r3, {r4, r5, r6, r7} )
  32. THUMB( ldr sp, [r3, #16] )
  33. str r9, [r4] @ 保存 processor ID到全局变量processor_id
  34. str r1, [r5] @ 保存 machine type到全局变量__machine_arch_type
  35. str r2, [r6] @ 保存 atags指针到全局变量__atags_pointer
  36. bic r4, r0, #CR_A @ 清除cp15 控制寄存器值的 'A' bit(禁用对齐错误检查)
  37. stmia r7, {r0, r4} @ 保存控制寄存器值到全局变量cr_alignment(在arch/arm/kernel/entry-armv.S)
  38. b start_kernel @ 跳入C代码(init/main.c)
  39. ENDPROC(__mmap_switched)
    所以我们可以说:machine_arch_type == r1。

    下面我们接着分析setup_machine_tag:

  1. static struct machine_desc * __init setup_machine_tags(unsigned int nr)
  2. {
  3.     struct tag *tags = (struct tag *)&init_tags;
  4.     struct machine_desc *mdesc = NULL, *p;
  5.     char *from = default_command_line;

  6.     init_tags.mem.start = PHYS_OFFSET;

  7.     /*
  8.      * locate machine in the list of supported machines.
  9.      */
  10.     for_each_machine_desc(p)
  11.         if (nr == p->nr) {
  12.             printk("Machine: %s\n", p->name);
  13.             mdesc = p;
  14.             break;
  15.         }

  16.     if (!mdesc) {
  17.         early_print("\nError: unrecognized/unsupported machine ID"
  18.             " (r1 = 0x%08x).\n\n", nr);
  19.         dump_machine_table(); /* does not return */
  20.     }
    其中的(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
  1. /*
  2.  * Set of macros to define architecture features. This is built into
  3.  * a table by the linker.
  4.  */
  5. #define MACHINE_START(_type,_name)            \
  6. static const struct machine_desc __mach_desc_##_type    \
  7.  __used                            \
  8.  __attribute__((__section__(".arch.info.init"))) = {    \
  9.     
  10. .nr        = MACH_TYPE_##_type,        \
  11.     
  12. .name        = _name,

  13. #define MACHINE_END                \
  14. };
例如arch/arm/mach-s3c64xx/mach-mini6410.c 查看下面这个结构体:
  1. MACHINE_START(MINI6410, "MINI6410")
  2.     /* Maintainer: Darius Augulis */
  3.     .boot_params    = S3C64XX_PA_SDRAM + 0x100,
  4.     .init_irq    = s3c6410_init_irq,
  5.     .map_io        = mini6410_map_io,
  6.     .init_machine    = mini6410_machine_init,
  7.     .timer        = &s3c24xx_timer,
  8. MACHINE_END
所以我们可以这么说:如果r1和Linux内核代码定义的MACHINE_START(_type,_name)不匹配,那么就会打印出:
  1. Error: unrecognized/unsupported machine ID
并且运行 dump_machine_table():
  1. void __init dump_machine_table(void)
  2. {
  3.     struct machine_desc *p;

  4.     early_print("Available machine support:\n\nID (hex)\tNAME\n");
  5.     for_each_machine_desc(p)
  6.         early_print("%08x\t%s\n", p->nr, p->name);

  7.     early_print("\nPlease check your kernel config and/or bootloader.\n");

  8.     while (true)
  9.         /* can't use cpu_relax() here as it may require MMU setup */;
  10. }
同样死循环,挂了~~~!!!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
所以我们可以得出结论:虽然 __lookup_machine_type消失了,并不代表内核不检查bootloader通过r1传递过来的machine_type参数,只是把检查机制推迟到了C代码中(除非你实现了FDT)。


阅读(300) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~