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

转自:http://www.myexception.cn/linux-unix/1438068.html

 

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

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

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

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

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


注意我的是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

 

       这个函数是对cmdlineearly_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)

和我ubootbootargs一样

                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)

就是一个取

paramsNULLnum0unknowndo_early_param函数

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

               

                由于parse_onenum0,所以parse_one就是执行do_early_param

System.map下有c0316740 T__setup_start

               c0316aa0 T __setup_end

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

 

static int __initdo_early_param(char *param, char *val)

{

        const struct obs_kernel_param *p;

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

                ) {这里看到判断了early1,是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 */

};

直接把2进制代码填入,这个数组的都是命令。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 结构体,搞过移植应该知道,ubootkernel的设备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 listcmdlinememinfo数据,我的没有。

 

       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;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值