[置顶] linux内核启动1-启动参数(启动参数的获取和处理,分析setup_arch)

时间:2022-02-21 16:17:01

最近公司要求调试一个内核,启动时有问题,所以就花了一点时间看看内核启动。

看的过程中总结了一点东西,希望可以帮助大家调试内核。

当我开始看的时候,第一件事是从网上搜集资料,不看不知道,一看吓一跳!牛人太多了,像这种内核启动的上古代码早就被人分析的彻彻底底。这注定我写的只能是烂微博了。

为了此微博有存在的必要,我会显示内核启动打印的代码位置(用绿色表示)及出现错误打印的原因(用红色表示),同时我会尽力用添加打印(用蓝色字,同时给出对应于本人平台的打印结果)或实例来说明一些细节

注意我的是linux-3.2.36,有的老版本machine的判断位置不一样。

首先看启动参数

http://blog.chinaunix.net/uid-20543672-id-3151113.html

有两种启动参数

标签列表(taggedlist)或设备树(devicetree)。

引导程序和内核约定r2寄存器中存放的数据所指向的内存地址。

说设备树的微博,可以看看下面这个

http://blog.csdn.net/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有,有兴趣可以看看。

init_mm.start_code = (unsigned long) _text;

init_mm.end_code   = (unsignedlong) _etext;

init_mm.end_data   = (unsignedlong) _edata;

init_mm.brk        = (unsignedlong) _end;

上面的在平台连接脚本arch/arm/kernel/vmlinux.lds还有个vmlinux.lds.S看到大小

. = 0xC0000000 + 0x00008000;

.head.text : {

_text = .;

*(.head.text)

}

如果你编译内核会在源码目录下生产

System.map文件

我贴一点

000000c A cpu_arm920_suspend_size

c0004000 A swapper_pg_dir

c0008000 T _text

c0008000 T stext

c02f9b90 A _etext

c03367c0 D _edata

c0367db8 A _end

能看到一些上面的东西,不过我们还是要打印一下

//wxl 增加

printk(KERN_NOTICE“_text =%lx\n_etext=%lx\n_edata=%lx\n_end=%lx\n”, \

(unsigned long)_text, (unsigned long) _etext, (unsigned long) _edata, (unsigned long) _end);

打印结果

_text = c0008000

_etext=c02f9b90

_edata=c03367c0

_end=c0367db8

和上面一样吧

/* populate cmd_line too for later use, preserving boot_command_line */

strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);

*cmdline_p = cmd_line; 将boot_command_line复制到cmd_line中

这个函数是对cmdline的early_param处理,后面还有处理,在这个微博不会看到

parse_early_param();

这里需要注意的是内核的cmdline中的参数按照其被需要的先后,分为early和非early的。

parse_args("early options", cmdline, NULL, 0,do_early_param);

这个代码在kernerl/params.c下

while (*args) {

int ret;

intirq_was_disabled;

//wxl 增加

printk(KERN_NOTICE “parse_args args: %s\n”, args);

打印结果

parse_args args: mem=64M console=ttySAC0,115200 no_console_suspendroot=/dev/mtdblock3 rootfstype=jffs2mtdparts=384K(u-boot),128K(u-boot-env),5M(kernel),20M(root)

和我uboot的bootargs一样

args =next_arg(args, &param, &val);

irq_was_disabled = irqs_disabled();

//wxl增加

printk(KERN_NOTICE “param:%s val =%s\n”, param, val);

打印结果。循环打印

param: mem val =64M

param: console val =ttySAC0,115200

param: no_console_suspend val =(null)

param: root val =/dev/mtdblock3

param: rootfstype val =jffs2

param: mtdparts val=384K(u-boot),128K(u-boot-env),5M(kernel),20M(root)

就是一个取

,unknown是do_early_param函数

ret =parse_one(param, val, params, num, unknown);

,所以parse_one就是执行do_early_param

System.map下有c0316740 T__setup_start

c0316aa0 T __setup_end

在链接文件里,__setup_start = .;*(.init.setup) __setup_end = .;

static int __init do_early_param(char *param, char *val)

{

const struct obs_kernel_param *p;

struct obs_kernel_param {

const char *str;           //在cmdline中相应参数名。

int(*setup_func)(char *);  //对于此参数的专用处理函数

int early;                 //是否为早期需要处理的参数

};

for (p = __setup_start; p <__setup_end; p++) {

if ((p->early &&parameq(param, p->str)) ||

(strcmp(param,"console") == 0 &&

strcmp(p->str, "earlycon") ==0)

的在后面处理,在此微博不会看到了,以后我在说。不过console可以用earlycon指定的,就不要early,例如:

8250_early.c:early_param("earlycon",setup_early_serial8250_console);

if(p->setup_func(val) != 0)参数对应就可以执行对应函数

printk(KERN_WARNING

"Malformed early option '%s'\n", param);你看到这个打印就知道是你的early处理函数出错

}

}

/* We accept everything at this stage.*/

return 0;

}

这就是执行.init.setup的东西,也就是执行

#define early_param(str, fn) \

__setup_param(str,fn, fn, 1)

early_param("mem", early_mem); mem就是

我们要看一下early_param->arm_add_memory(start, size);这个start = PHTS_OFFSET就是0x30000000 size就是我们传入的64M即0x4000000

arm_add_memory会把mem信息存入meminfo(下一篇文章看到这个定义)

下面四个和内存有关,下一篇微博进行分析

        sanity_check_meminfo();

        arm_memblock_init(&meminfo, mdesc);

        paging_init(mdesc);

        request_standard_resources(mdesc);

下面两个我的平台不涉及,不说了

unflatten_device_tree();

#ifdef CONFIG_SMP

if (is_smp())

smp_init_cpus();

#endif

reserve_crashkernel();

用于内核崩溃时的保留内核此功能通过内核command line参数中的"crashkernel="保留下内存用于主内核崩溃时获取内核信息的导出。格式crashkernel=size[KMG][@offset[KMG]]

Kdump会用到,这位同志的linux启动有这个,你们看看

http://hi.baidu.com/sunboy_2050/item/6404d9ff3a3003e61a111fcb

tcm_init();

参考

http://blog.csdn.net/sergeycao/article/details/6030226

#ifdef CONFIG_MULTI_IRQ_HANDLER

Kconfig解释

config MULTI_IRQ_HANDLER

bool

help

Allow eachmachine to specify it's own IRQ handler at run time.

handle_arch_irq = mdesc->handle_irq; 我的平台没有

#endif

选择Console类型

#ifdef CONFIG_VT

#if defined(CONFIG_VGA_CONSOLE)

conswitchp = &vga_con;

#elif defined(CONFIG_DUMMY_CONSOLE)

conswitchp = &dummy_con;

#endif

#endif

early_trap_init();

对中断向量表进行早期初始化

void __init early_trap_init(void)

{

#if defined(CONFIG_CPU_USE_DOMAINS)

unsigned long vectors = CONFIG_VECTORS_BASE;

#define CONFIG_VECTORS_BASE 0xffff0000   arm中断向量表位置

#else

unsigned long vectors = (unsigned long)vectors_page;

#endif

先看看我的平台

System.map

c02fbea0 T __kuser_helper_start

c02fbee0 t __kuser_memory_barrier

c02fbf00 t __kuser_cmpxchg

c02fbf20 t __kuser_get_tls

c02fbf3c t __kuser_helper_version

c02fbf40 T __kuser_helper_end

c02fbf40 T __stubs_start

c02fbf40 t vector_irq

c02fbfc0 t vector_dabt

c02fc040 t vector_pabt

c02fc0c0 t vector_und

c02fc140 t vector_fiq

c02fc144 t vector_addrexcptn

c02fc164 T __stubs_end

c02fc164 T __vectors_start

c02fc184 T __vectors_end

extern char__stubs_start[], __stubs_end[];

extern char__vectors_start[], __vectors_end[];

extern char__kuser_helper_start[], __kuser_helper_end[];

上面定义在arch/arm/kernel/entry-armv.S

int kuser_sz =__kuser_helper_end - __kuser_helper_start;

/*

* Copy thevectors, stubs and kuser helpers (in entry-armv.S)

* into the vectorpage, mapped at 0xffff0000, and ensure these

* are visible tothe instruction stream.

*/

下面就是把向量表和kuser重映射到0xffff0000地址

memcpy((void*)vectors, __vectors_start, __vectors_end - __vectors_start);

memcpy((void*)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);

memcpy((void*)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

/*

* Do processorspecific fixups for the kuser helpers

*/

映射处理特殊的kuser帮助

地址在vectors + 0xfe0,如果有tls_emu或has_tls_reg

kuser_get_tls_init(vectors);

/*

* Copy signalreturn handlers into the vector page, and

* set sigreturnto be a pointer to these.

*/

sigreturn_codes是返回值数组

memcpy((void *)(vectors + KERN_SIGRETURN_CODE- CONFIG_VECTORS_BASE),

sigreturn_codes, sizeof(sigreturn_codes));

const unsigned long syscall_restart_code[2] = {

SWI_SYS_RESTART,        /*swi  __NR_restart_syscall */

0xe49df004,             /* ldr  pc, [sp], #4 */

};

进制代码填入,这个数组的都是命令。SWI,即software interrupt软件中断,__NR_restart_syscall是软件中断号。ldr pc, [sp], #4 可能是lr装入pc返回,我没有具体看

memcpy((void*)(vectors + KERN_RESTART_CODE - CONFIG_VECTORS_BASE),

syscall_restart_code,sizeof(syscall_restart_code));

flush_icache_range(vectors, vectors + PAGE_SIZE);

最终就是__glue(_CACHE, vectors + PAGE_SIZE)

#ifdef __STDC__

#define ____glue(name,fn)      name##fn

#else

#define ____glue(name,fn)      name/**/fn

#endif

#define __glue(name,fn)        ____glue(name,fn)

就是组成一个标记

modify_domain(DOMAIN_USER, DOMAIN_CLIENT);这个最终会用cp15协处理器去设置domain

}

if (mdesc->init_early)需要早期的初始化,我的没有,你只要知道这个在处理early参数之后。

mdesc->init_early();

}

static struct machine_desc * __initsetup_machine_tags(unsigned int nr)

{

struct tag *tags = (struct tag *)&init_tags;同一目录下有定义init_tags

struct machine_desc *mdesc = NULL, *p;

char *from = default_command_line;这个就是内核配置的cmdline,在内核配置选项

通过bootloader传递过来的设备ID来匹配一个 struct machine_desc结构体,搞过移植应该知道,uboot和kernel的设备ID是要一样的,还记得uboot如何传的吧,r1 =machid,

有个这个结构体表示structmachine_desc {

在大家的平台是arch/arm/mach-*/mach-*.c下,我的

MACHINE_START(MINI2440, "MINI2440") //id, name

        /* Maintainer:Michel Pollet <buserror@gmail.com> */

        .atag_offset    = 0x100,//标签列表相对地址,还记得我的是0x30000100吧,所以是0x100

        .map_io         = mini2440_map_io,//io映射函数

        .init_machine   = mini2440_init,//板子初始化函数

        .init_irq       = s3c24xx_init_irq,//中断初始化函数

        .timer          = &s3c24xx_timer,//系统tick定时器

MACHINE_END

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);

我的打印:

Machine: MINI2440

mdesc = p;

break;

}

if (!mdesc) {

early_print("\nError:unrecognized/unsupported machine ID"

"(r1 = 0x%08x).\n\n", nr);

如果你看到这个打印应该知道是设备id不匹配

dump_machine_table(); /* does not return */死循环

}

if (__atags_pointer)

tags =phys_to_virt(__atags_pointer);

// #define __virt_to_phys(x)        ((x) -PAGE_OFFSET + PHYS_OFFSET)
 //#define __phys_to_virt(x)        ((x) - PHYS_OFFSET+ PAGE_OFFSET)

else if (mdesc->atag_offset)上面看到是0x100,如果bootloader传的对就不用他了

tags = (void *)(PAGE_OFFSET +mdesc->atag_offset);

//wxl添加

printk(KERN_NOTICE “tags virt addr = %lxPAGE_OFFSET = %lx PHYS_OFFSET = %lx \n”, (unsigned long)tags, (unsigned long)PAGE_OFFSET,(unsigned long)PHYS_OFFSET);

打印为

tags virt addr = c0000100 PAGE_OFFSET = c0000000 PHYS_OFFSET =30000000

#if defined(CONFIG_DEPRECATED_PARAM_STRUCT)//参数检测

/*

* If we have the old style parameters, convert them to

* a tag list.

*/

if (tags->hdr.tag != ATAG_CORE)还记得第一个要是ATAG_CORE吧

convert_to_tag_list(tags);不用看代码,就知道是转换为现在的格式

#endif

if (tags->hdr.tag != ATAG_CORE) {如何不可用参数,看处理

#if defined(CONFIG_OF)

/*

* If CONFIG_OF is set, thenassume this is a reasonably

* modern system that shouldpass boot parameters

*/

early_print("Warning:Neither atags nor dtb found\n");

#endif

tags = (struct tag*)&init_tags;用内核默认的,所以从这可以看到,不可用启动参数不会引起无法启动

}

if (mdesc->fixup)

mdesc->fixup(tags,&from, &meminfo);对应你平台提供的函数,你可以为你的平台处理tagged list、cmdline和meminfo数据,我的没有。

if (tags->hdr.tag == ATAG_CORE) {

//wxl 添加

printk(KERN_NOTICE “meminfo.nr_banks = %d\n”, meminfo.nr_banks)

打印结果

meminfo.nr_banks = 0

if (meminfo.nr_banks != 0)/如果mem已经初始化,就不要tags里的mem了

squash_mem_tags(tags);

save_atags(tags);保存到atags_copy

parse_tags(tags);解析

static void __init parse_tags(const struct tag *t)

{

for (;t->hdr.size; t = tag_next(t))

if(!parse_tag(t))

printk(KERN_WARNING

"Ignoring unrecognised tag 0x%08x\n",

t->hdr.tag);

}

static int __init parse_tag(const struct tag *tag)

{

extern struct tagtable __tagtable_begin, __tagtable_end;

struct tagtable*t;

这个处理和对command_line处理很像,

System.map里面

c0312780 T __tagtable_begin

c0312780 t __tagtable_parse_tag_cmdline

c0312788 t __tagtable_parse_tag_revision

c0312790 t __tagtable_parse_tag_serialnr

c0312798 t __tagtable_parse_tag_ramdisk

c03127a0 t __tagtable_parse_tag_videotext

c03127a8 t __tagtable_parse_tag_mem32

c03127b0 t __tagtable_parse_tag_core

c03127b8 t __tagtable_parse_tag_initrd2

c03127c0 t __tagtable_parse_tag_initrd

c03127c8 T __pv_table_begin

c03127c8 T __tagtable_end

例如__tagtable_parse_tag_mem32,你在内核只能找到parse_tag_mem32函数

__tagtable(ATAG_MEM, parse_tag_mem32);

#define __tag __used __attribute__((__section__(".taglist.init")))

#define __tagtable(tag, fn) \

static struct tagtable __tagtable_##fn__tag = { tag, fn }

__tagtable_parse_tag_mem32就是用##连接得到的。

for (t =&__tagtable_begin; t < &__tagtable_end; t++)

{

//wxl add

printk(KERN_NOTICE “tag->hdr.tag = %lxt->tag = %lx\n”, (unsigned ling)tag->hdr.tag, (unsigned ling)t->tag);

tag->hdr.tag = 54410001 t->tag = 54410009

tag->hdr.tag = 54410001 t->tag = 54410007

tag->hdr.tag = 54410001 t->tag = 54410006

tag->hdr.tag = 54410001 t->tag = 54410004

tag->hdr.tag = 54410001 t->tag = 54410003

tag->hdr.tag = 54410001 t->tag = 54410002

tag->hdr.tag = 54410001 t->tag = 54410001   ATAG_CORE

tag->hdr.tag = 54410002 t->tag = 54410009

tag->hdr.tag = 54410002 t->tag = 54410007

tag->hdr.tag = 54410002 t->tag = 54410006

tag->hdr.tag = 54410002 t->tag = 54410004

tag->hdr.tag = 54410002 t->tag = 54410003

tag->hdr.tag = 54410002 t->tag = 54410002   ATAG_MEM

tag->hdr.tag = 54410009 t->tag = 54410009   ATAG_CMDLINE

从这个打印看会先执行mem在执行cmdline

__tagtable(ATAG_CMDLINE, parse_tag_cmdline);

执行cmdline就是把cmdline保存在default_command_line,处理command_line会用到

if(tag->hdr.tag == t->tag) {当tag==MEM时,执行parse_tag_mem32

t->parse(tag);

我们只看parse_tag_mem32

static int __init parse_tag_mem32(const struct tag *tag)

{

//wxl add

printk(KERN_NOTICE"tag->u.mem.start = %lx tag->u.mem.size = %lx\n", (unsignedlong)tag->u.mem.start, (unsigned long)tag->u.mem.size);

打印如下

tag->u.mem.start =30000000 tag->u.mem.size = 4000000

我的bootargs mem=64M和这个一样,但是如果我mem=32M,那内存会设为32M,因为在下面cammand_line处理还要重新调用arm_add_memory,还会把meminfo.nr_banks = 0

return arm_add_memory(tag->u.mem.start, tag->u.mem.size);

}

和command_line中的”mem=”处理一样

break;

}

}

return t <&__tagtable_end;

}

}

/* parse_early_param needs a boot_command_line */

strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

return mdesc;

}