最近公司要求调试一个内核,启动时有问题,所以就花了一点时间看看内核启动。
看的过程中总结了一点东西,希望可以帮助大家调试内核。
当我开始看的时候,第一件事是从网上搜集资料,不看不知道,一看吓一跳!牛人太多了,像这种内核启动的上古代码早就被人分析的彻彻底底。这注定我写的只能是烂微博了。
为了此微博有存在的必要,我会显示内核启动打印的代码位置(用绿色表示)及出现错误打印的原因(用红色表示),同时我会尽力用添加打印(用蓝色字,同时给出对应于本人平台的打印结果)或实例来说明一些细节。
注意我的是linux-3.2.36,有的老版本machine的判断位置不一样。
首先看启动参数
http://blog.chinaunix.net/uid-20543672-id-3151113.html
有两种启动参数
标签列表(taggedlist)或设备树(devicetree)。
引导程序和内核约定r2寄存器中存放的数据所指向的内存地址。
说设备树的微博,可以看看下面这个
http://blog.youkuaiyun.com/21cnbao/article/details/8457546
这两个注意:
标签列表必须置于内核自解压和initrd'bootp'程序都不会覆盖的内存区。建议放在RAM的头16KiB中。
设备树必须置于内核自解压不会覆盖的内存区。建议将其放置于RAM的头16KiB中
我简单介绍标签列表格式
· 基地址 -> +-----------+
· | ATAG_CORE | |
· +-----------+ |
· | ATAG_MEM | | 地址增长方向
· +-----------+ |
· | ATAG_NONE | |
· +-----------+ v
viarch/arm/include/asm/setup.h
struct tag_header {
__u32size; //标签总大小(包括tag_header)
__u32tag; //标签标识
};
上面的图就是要linux获取的第一个tag的头的__u32 tag要是ATAG_CORE
最后一个是ATAG_NONE。
struct tag {
structtag_header hdr;
union {
struct tag_core core;// 标签列表开始 struct tag_mem32 mem;// 内存信息标签(可以有多个标签,以标识多个内存区块)
struct tag_videotext videotext;// VGA文本显示参数标签
struct tag_ramdisk ramdisk;// ramdisk参数标签(位置、大小等)
struct tag_initrd initrd;// 压缩的ramdisk参数标签
struct tag_serialnr serialnr;// 板子串号标签
struct tag_revision revision;// 板子版本号标签
struct tag_videolfb videolfb;// 帧缓冲初始化参数标签
struct tag_cmdline cmdline;//就是uboot的bootargs
/*
*Acorn specific
*/
struct tag_acorn acorn;
/*
*DC21285 specific
*/
struct tag_memclk memclk;
} u;
};
先简单的解释一下cmdline,你可以在用户态下cat /proc/cmdline
看到,就是uboot的bootargs。
上面的微博有有对bootm分析,我只说一点
setup_start_tag (bd); //设置ATAG_CORE,这里面有params = (struct tag *) bd->bi_boot_params;
参数地址
kernel_entry(0, machid, bd->bi_boot_params);
r0 = 0 r1 = machid r2 = bd->bi_boot_params)
bd->bi_boot_params在board下对应的板子目录下
我的
bd->bi_boot_params = 0x30000100 我的ram开始是0x30000000
内核的建议
建议放在RAM的头16KiB中,还有一句是:但是不可将其放置于“0”物理地址处,因为内核认为:r2中为0,意味着没有标签列表和dtb传递过来。这在启动是的汇编代码可以看到。
Bootloader到内核的start_kernel之间还有自解压和一些汇编代码,这个我会在下一篇博客分析,现在主要是从setup_arch开始。这也是自解压之后,我们能看到内核打印开始的地方。
下面我们进入内核,看看内核如何获取这些启动参数
init/main.c
asmlinkage void __init start_kernel(void)
{
……
printk(KERN_NOTICE"%s", linux_banner);//这个就是我们linux启动时打印的版本信息,我的:Linux version 3.2.0(root@localhost.localdomain) (gcc version 4.4.3 (ctng-1.6.1) ) #70 Thu Jun 2010:50:51 CST 2013
setup_arch(&command_line);
……
下面是我们的主机
蓝色的字为我添加的调试打印,将在后面调试时开出来
arch/arm/kernel/setup.c
void __init setup_arch(char **cmdline_p)
{
struct machine_desc *mdesc;
setup_processor();
根据读出的cpuid,进行匹配,在汇编代码中有也有判断,我简单说一下,一个是从cpu读的id(用cp15协处理器),一个是从arch/arm/mm/ proc-armXXX.c读的,例如我的arm920t,
__arm920_proc_info:
.long 0x41009200
就是这个id,如果不匹配,while(1)死循环。
cpu信息就在这里打印,我的:
CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177
CPU: VIVT data cache, VIVT instruction cache
如果你没有那么是你的cpu类型选的不对,但是cpu类型一般是和machine一起选的,所以还是看下面的
//wxl 添加
printk(KERN_NOTICE “__atags_pointer: %lx\n”, (unsigned long)__atags_pointer);
打印为__atags_pointer : 30000100
还记得uboot的
bd->bi_boot_params= 0x30000100
mdesc = setup_machine_fdt(__atags_pointer);这个函数时获取设备树的信息,我的没有,会返回NULL
if (!mdesc)
mdesc =setup_machine_tags(machine_arch_type);
这个是处理tagged_list的主要函数,在下面,我没有放这里,你先看看它在向下看。
machine_desc = mdesc;
machine_name = mdesc->name;这是MINI2440
#ifdef CONFIG_ZONE_DMA
if (mdesc->dma_zone_size) {
extern unsigned longarm_dma_zone_size;
arm_dma_zone_size =mdesc->dma_zone_size;DMA区域大小
}
#endif
if (mdesc->soft_reboot)
reboot_setup("s");
这个是重启方式,”s”为软件,”h”为硬件,最终重启调用arch_reset(mode,cmd);这个函数成功就重启不会返回,如果没有成功,你会看到Reboot failed --System halted打印;这个函数由平台提供,samsung没有什么软件重启模式,mach-ebsa110</