Linux文件之间传递参数,u-boot与linux内核间的参数传递过程分析

http://liuyue18301.blog.163.com/blog/static/27913282009930112920220/?fromdm&fromSearch&isFromSearchEngine=yes

U-boot会给Linux Kernel传递很多参数,如:串口,RAM,videofb、MAC地址等。而Linux kernel也会读取和处理这些参数。两者之间通过struct tag来传递参数。U-boot把要传递给kernel的东西保存在struct tag数据结构中,启动kernel时,把这个结构体的物理地址传给kernel;Linux kernel通过这个地址,用parse_tags分析出传递过来的参数。

本文主要以U-boot(1.1.6)传递RAM和Linux kernel读取RAM参数为例进行说明。

1、u-boot给kernel传RAM参数

在介绍该之前,我们需要看一看几个数据结构,这些是u-boot中几个重要的数据结构:

(1)gd_t结构体

U-Boot使用了一个结构体gd_t来存储全局数据区的数据,这个结构体在U-Boot的include/asm-arm/global_data.h中定义如下:

typedef    struct    global_data {

bd_t        *bd;   //与板子相关的结构,见下面

unsigned long    flags;

unsigned long    baudrate;

unsigned long    have_console;    /* serial_init() was called */

unsigned long    reloc_off;    /* Relocation Offset */

unsigned long    env_addr;    /* Address  of Environment struct */

unsigned long    env_valid;    /* Checksum of Environment valid? */

unsigned long    fb_base;    /* base address of frame buffer */

#ifdef CONFIG_VFD  //我们一般没有配置这个,这个是frame buffer的首地址

unsigned char    vfd_type;    /* display type */

#endif

#if 0

unsigned long    cpu_clk;    /* CPU clock in Hz!        */

unsigned long    bus_clk;

unsigned long    ram_size;    /* RAM size */

unsigned long    reset_status;    /* reset status register at boot */

#endif

void        **jt;        /* jump table */

} gd_t;

/*

* Global Data Flags

*/

#define    GD_FLG_RELOC    0x00001        /* Code was relocated to RAM        */

#define    GD_FLG_DEVINIT    0x00002        /* Devices have been initialized    */

#define    GD_FLG_SILENT    0x00004        /* Silent mode                */

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

在global_data.h中U-Boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址:

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据结构的指针,这个指针存放在指定的寄存器r8中。这个声明也避免编译器把r8分配给其它的变量。任何想要访问全局数据区的代码,只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了。

根据U-Boot内存使用图中可以计算gd的值:

gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)

c8a566e790567d20d5849b6a6aaa9053.png

2)bd_t 保存与板子相关的配置参数

bd_t在U-Boot的include/asm-arm/u-boot.h中定义如下:

typedef struct bd_info {

int            bi_baudrate;    /* 串口通讯波特率 */

unsigned long    bi_ip_addr;    /* IP地址 */

unsigned char    bi_enetaddr[6]; /* Ethernet adress */

struct environment_s           *bi_env; /*环境变量开始地址 */

ulong  bi_arch_number;    /* unique id for this board开发板的机器码 */

ulong  bi_boot_params;    /* where this board expects params 内核参数的开始地址*/

struct                /* RAM配置信息 */

{

ulong start;

ulong size;

}     bi_dram[CONFIG_NR_DRAM_BANKS]; //在我的板子上DRAM配置是1个

#ifdef CONFIG_HAS_ETH1

/* second onboard ethernet port */

unsigned char   bi_enet1addr[6];

#endif

} bd_t;

#define bi_env_data bi_env->data

#define bi_env_crc  bi_env->crc

U-Boot启动内核时要给内核传递参数,这时就要使用gd_t,bd_t结构体中的信息来设置标记列表。

3)启动参数的数据结构

向内核传递启动参数可保存在两种数据结构中,param_struct和tag,前者是2.4内核用的,后者是2.6以后的内核更期望用的但是,到目前为止,2.6的内核也可以兼容前一种结构,内核参数通过一个静态的param_struct或tag链表在启动的时候传递到内核。需要注意的是,这两个数据结构在uboot中和linux中分别有定义,这个定义必须一致才能正常传递参数如果实际使用中不一致的话就不能正常传递,可以自行修改 两种数据结构具体定义如下(这里说的是内核源码中的定义):

struct param_struct {

union {

struct {

unsigned long page_size;        /*  0 */

unsigned long nr_pages;        /*  4 */

unsigned long ramdisk_size;        /*  8 */

unsigned long flags;        /* 12 */

#define FLAG_READONLY    1

#define FLAG_RDLOAD    4

#define FLAG_RDPROMPT    8

unsigned long rootdev;        /* 16 */

unsigned long video_num_cols;    /* 20 */

unsigned long video_num_rows;    /* 24 */

unsigned long video_x;        /* 28 */

unsigned long video_y;        /* 32 */

unsigned long memc_control_reg;    /* 36 */

unsigned char sounddefault;        /* 40 */

unsigned char adfsdrives;        /* 41 */

unsigned char bytes_per_char_h;    /* 42 */

unsigned char bytes_per_char_v;    /* 43 */

unsigned long pages_in_bank[4];    /* 44 */

unsigned long pages_in_vram;    /* 60 */

unsigned long initrd_start;        /* 64 */

unsigned long initrd_size;        /* 68 */

unsigned long rd_start;        /* 72 */

unsigned long system_rev;        /* 76 */

unsigned long system_serial_low;    /* 80 */

unsigned long system_serial_high;    /* 84 */

unsigned long mem_fclk_21285;       /* 88 */

} s;

char unused[256];

} u1;

union {

char paths[8][128];

struct {

unsigned long magic;

char n[1024 - sizeof(unsigned long)];

} s;

} u2;

char commandline[COMMAND_LINE_SIZE];

};

param_struct只需要设置cmmandline,u1.s.page_size,u1.s.nr_pages三个域,下面是使用param_struct例子通过param_struct让uboot中的go命令可以传递参数

分析:go的代码在common/cmd_boot.c中,里面并没有拷贝启动参数的代码,转向内核的时候也没有传送

启动参数所在的地址,因此添加如下代码用于拷贝参数,可以看到,对于param_struct只需要设置cmmandline

u1.s.page_size,u1.s.nr_pages三个域

char *commandline = getenv("bootargs");

struct param_struct *lxy_params=(struct param_struct *)0x80000100;

printf("setup linux parameters at 0x80000100\n");

memset(lxy_params,0,sizeof(struct param_struct));

lxy_params->u1.s.page_size=(0x1<<12); //4K 这个是必须有的,否则无法启动

lxy_params->u1.s.nr_pages=(0x4000000)>>12; //64M 这个是必须有的,否则无法启动

memcpy(lxy_params->commandline,commandline,strlen(commandline)+1);

printf("linux command line is: \"%s\"\n",lxy_params->commandline);

然后还要向内核传递参数地址,将下面一行代码修改:

rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);  //需要被修改的代码

rc = ((ulong(*)(int,int,uint))addr) (0, gd->bd->bi_arch_number,gd->bd->bi_boot_params);//修改之后的代码

关于param_struct不是这里重点,下面主要分析tag

对于tag来说,在实际使用中是一个struct tag组成的列表,在tag->tag_header中,一项是u32 tag(重名,注意类型)其值用宏ATAG_CORE,ATAG_MEM,ATAG_CMDLINE,ATAG_NONE等等来表示,此时下面union就会使用与之相关的数据结构同时,规定tag列表中第一项必须是ATAG_CORE,最后一项必须是ATAG_NONE,比如在linux代码中,找到启动参数之后首先看tag列表中的第一项的tag->hdr.tag是否为ATAG_CORE,如果不是,就会认为启动参数不是tag结构而是param_struct结构,然后调用函数来转换.在tag->tag_header中,另一项是u32 size,表示tag的大小,tag组成列表的方式就是指针+size

tag数据结构在arch/arm/include/asm/setup.h(U-Boot的在include/asm-arm/setup.h定义,完全一样)中定义如下:

struct tag {

struct tag_header hdr;

union {

struct tag_core        core;

struct tag_mem32    mem;

struct tag_videotext    videotext;

struct tag_ramdisk    ramdisk;

struct tag_initrd    initrd;

struct tag_serialnr    serialnr;

struct tag_revision    revision;

struct tag_videolfb    videolfb;

struct tag_cmdline    cmdline;

/*

* Acorn specific

*/

struct tag_acorn    acorn;

/*

* DC21285 specific

*/

struct tag_memclk    memclk;

} u;

};

其中tag_header为tag头,表明tag_xxx的类型和大小,之所以要标识tag_xxx的类型是因为不同的tag需要不同的处理函数

内核tag_header的结构(arch/arm/include/asm/setup.h)为

struct tag_header {

__u32 size;

__u32 tag;

};

U-Boot的在include/asm-arm/setup.h定义

struct tag_header {

u32 size;

u32 tag;

};

size表示tag的结构大小,tag为表示tag类型的常量。这个静态的链表必须以tag_header.tag = ATAG_CORE开始,并以tag_header.tag = ATAG_NONE结束。由于不同的tag所使用的格式可能不尽相同,所以内核又定义了一个结构tagtable来把tag和相应的操作函数关联起来

(arch/arm/include/asm/setup.h)

struct tagtable {

__u32 tag;

int (*parse)(const struct tag *);

};

其中tag为标识入ATAG_NONE,ATAG_CORE等。parse为处理函数。Linux内核将tagtable也组成了一个静态的链表放入.taglist.init节中,这是通过__tagtable宏来实现的

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

#define __tagtable(tag, fn) \

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

这个tagtable 列表 是怎么形成的?

如arch/arm/kernel/setup.c

556 static int __init parse_tag_mem32(const struct tag *tag)

557 {

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

559 }

560

561 __tagtable(ATAG_MEM, parse_tag_mem32);

607 __tagtable(ATAG_SERIAL, parse_tag_serialnr);

608

609 static int __init parse_tag_revision(const struct tag *tag)

610 {

611         system_rev = tag->u.revision.rev;

612         return 0;

613 }

614

615 __tagtable(ATAG_REVISION, parse_tag_revision);

618 static int __init parse_tag_cmdline(const struct tag *tag)

619 {

620         strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);

621         return 0;

622 }

623

624 __tagtable(ATAG_CMDLINE, parse_tag_cmdline);

根据前面相关宏定义,__tagtable(ATAG_CMDLINE, parse_tag_cmdline)展开后为

static struct tagtable __tagtable_parse_tag_cmdline __used __attribute__((__section__(".taglist.init"))) = { ATAG_CMDLINE, parse_tag_cmdline }

__tagtable将ATAG_CMDLINE和parse_tag_cmdline挂钩,

再参看arch/arm/kernel/vmlinux.lds.S文件

34                 __proc_info_begin = .;

35                         *(.proc.info.init)

36                 __proc_info_end = .;

37                 __arch_info_begin = .;

38                         *(.arch.info.init)

39                 __arch_info_end = .;

40                 __tagtable_begin = .;

41                         *(.taglist.init)

42                 __tagtable_end = .;

tagtable 列表编译连接后被存放在.taglist.init中。

现在再来看一下U-boot给Linux Kernel传递启动参数的传递过程

启动参数是包装在struct tag数据结构里的,在linux kernel启动的时候,bootloader把这个数据结构拷贝到某个地址,在改动PC跳向内核接口的同时,通过通用寄存器R2来传递这个地址的值,在bootm执行的流程中,会调用do_bootm_linux()在执行Linux内核,内核的起始地址如下:

void (*theKernel)(int zero, int arch, uint params);

image_header_t *hdr = &header;

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

header是uImage的头部,通过头部,得到内核映像起始的执行地址,标识为theKernel。从中也可以看到,内核接受三个参数,第一个为0,第二个为系统的ID号,第三个是传入内核的参数。

在do_bootm_linux()的最后,会跳到内核去执行:

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

thekernel其实不是个函数,而是指向内核入口地址的指针,把它强行转化为带三个参数的函数指针,会把三个

参数保存到通用寄存器中,实现了向kernel传递信息的功能,在这个例子里,把R0赋值为0,R1赋值为机器号bd->bi_arch_number, R2赋值为启动参数数据结构的首地址bd->bi_boot_params。最后两个参数在board/smdk2410/smdk2410.c的board_init()中被初始化。

因此,要向内核传递参数很简单,只要把启动参数封装在linux预定好的数据结构里,拷贝到某个地址(一般

约定俗成是内存首地址+100dex,后面会见到)  p { margin-bottom: 0.21cm; }

U-boot向内核传递参数的具体实现过程

a、在include/asm-arm/global_data.h中声名一个gd全局指针变量宏定义,并指定存放在r8寄存器中,在后面要用到gd全局指针变量时,只须要在文件开头引用这个宏就可以了。

64 #define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

b、在start_armboot(lib_arm/board.c)主函数中计算全局数据结构的地址并赋值给指针gd,并对struct tag数据结构里参数赋值

下面是start_armboot函数部分代码

55 DECLARE_GLOBAL_DATA_PTR;   //gd指针引用声名

248         gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));

249         /* compiler optimization barrier needed for GCC >= 3.4 */

250         __asm__ __volatile__("": : :"memory");

251

252         memset ((void*)gd, 0, sizeof (gd_t));

253         gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

254         memset (gd->bd, 0, sizeof (bd_t));

255

256         monitor_flash_len = _bss_start - _armboot_start;

257

258         for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

259                 if ((*init_fnc_ptr)() != 0) {

260                         hang ();

261                 }

262         }

首先在55行对gd指针引用声名,在248行计算全局数据结构的地址并赋值给指针gd,具体计算请参看前面的说明,253行计算出结构体中bd指针的地址,然后在第258行逐个调用init_sequence初始化函数列表数组中的初始化函数对平台硬件进行初始化,这里只分析后面用到的硬件初始化函数board_init、dram_init。这两个函数都在board/smdk2410/smdk2410.c中实现

首先看board_init函数,以下是部分实现

31 DECLARE_GLOBAL_DATA_PTR;

105         /* arch number of SMDK2410-Board */

106         gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;

107

108         /* adress of boot parameters */

109         gd->bd->bi_boot_params = 0x30000100;//一般约定俗成是内存首地址+100dex

可以看到,theKernel最后两个参数在这里的第106和109行被初始化,uboot传给内核的参数表存被放在内存中起始偏移0x100的位置,这里只是指定了“指针”的位置,但还没初始化其中的值,后面传递到内核的参数列表的构建才初始化其中的值,这是在 do_bootm_linux()中跳到内核前去完成的。值得注意的是, 内核的默认运行地址的0x30008000,前面就是留给参数用的。所以一般不要将内核下载到该地址之前,以免冲掉了传给内核的参数。这里在55行同样要对gd指针引用声名,MACH_TYPE_SMDK2410在include/asm-arm/mach-types.h中定义,值为192

而dram_init函数是对struct tag数据结构里内存参数赋值,后面会用到。

117 int dram_init (void)

118 {

119         gd->bd->bi_dram[0].start = PHYS_SDRAM_1;

120         gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;

121

122         return 0;

123 }

PHYS_SDRAM_1与PHYS_SDRAM_1_SIZE宏都在include/configs/smdk2410.h中定义。

#define CONFIG_NR_DRAM_BANKS    1          /* we have 1 bank of DRAM */

#define PHYS_SDRAM_1            0x30000000 /* SDRAM Bank #1 */

#define PHYS_SDRAM_1_SIZE       0x04000000 /* 64 MB */

c、传递到内核的参数列表的构建

./common/cmd_bootm.c文件中,bootm命令对应的do_bootm函数,当分析uImage中信息发现OS是Linux时,调用./lib_arm/armlinux.c文件中的do_bootm_linux函数来启动Linux kernel。在do_bootm_linux函数中(lib_arm/armlinux.c) ,以下是部分相关源码:

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \

defined (CONFIG_CMDLINE_TAG) || \

defined (CONFIG_INITRD_TAG) || \

defined (CONFIG_SERIAL_TAG) || \

defined (CONFIG_REVISION_TAG) || \

defined (CONFIG_LCD) || \

defined (CONFIG_VFD)

setup_start_tag (bd);    /* 设置ATAG_CORE标志 */

#ifdef CONFIG_SERIAL_TAG

setup_serial_tag (&params);

#endif

#ifdef CONFIG_REVISION_TAG

setup_revision_tag (&params);

#endif

#ifdef CONFIG_SETUP_MEMORY_TAGS

setup_memory_tags (bd);  /* 设置内存标记 */

#endif

#ifdef CONFIG_CMDLINE_TAG

setup_commandline_tag (bd, commandline);  /* 设置命令行标记 */

#endif

#ifdef CONFIG_INITRD_TAG

if (initrd_start && initrd_end)

setup_initrd_tag (bd, initrd_start, initrd_end);

#endif

#if defined (CONFIG_VFD) || defined (CONFIG_LCD)

setup_videolfb_tag ((gd_t *) gd);

#endif

setup_end_tag (bd);  /* 设置ATAG_NONE标志 */

#endif

在uboot中,进行设置传递到内核的参数列表tag的函数都在lib_arm/armlinux.c中,在这些函数前面是有ifdef的因此,如果你的bootm命令不能传递内核参数,就应该是在你的board的config文件里没有对上述的宏进行设置,定义一下即可

这里对于setup_start_tag、setup_memory_tags和setup_end_tag函数说明如下。它们都在lib_arm/armlinux.c文件中定义,如下

static void setup_start_tag (bd_t *bd)

{

params = (struct tag *) bd->bi_boot_params; /* 内核的参数的开始地址 */

params->hdr.tag = ATAG_CORE;

params->hdr.size = tag_size (tag_core);

params->u.core.flags = 0;

params->u.core.pagesize = 0;

params->u.core.rootdev = 0;

params = tag_next (params);

}

标记列表必须以ATAG_CORE开始,setup_start_tag函数在内核的参数的开始地址设置了一个ATAG_CORE标记。

#ifdef CONFIG_SETUP_MEMORY_TAGS

static void setup_memory_tags (bd_t *bd)  //初始化内存相关tag

{

int i;

/*设置一个内存标记 */

for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {

params->hdr.tag = ATAG_MEM;

params->hdr.size = tag_size (tag_mem32);

params->u.mem.start = bd->bi_dram[i].start; //0x30000000

params->u.mem.size = bd->bi_dram[i].size;   //0x04000000(64M)

params = tag_next (params);

}

}

#endif /* CONFIG_SETUP_MEMORY_TAGS */

setup_memory_tags函数设置了一个ATAG_MEM标记,该标记包含内存起始地址,内存大小这两个参数。RAM相关参数在前面的setup_memory_tags函数中已经初始化.

static void setup_end_tag (bd_t *bd)

{

params->hdr.tag = ATAG_NONE;

params->hdr.size = 0;

}

这个静态的链表必须以标记ATAG_CORE开始,并以标记ATAG_NONE结束。setup_end_tag函数设置了一个ATAG_NONE标记,表示标记列表的结束。

d、最后do_bootm_linux函数调用theKernel (0, machid, bd->bi_boot_params)去启动内核并传递参数,可以看见r0是machid,r2是bi_boot_params参数的地址。

2、Kernel读取U-boot传递的相关参数

对于Linux Kernel,ARM平台启动时,先执行arch/arm/kernel/head.S,此时r2寄存器的值为参数的地址,此文件会调用arch/arm/kernel/head-common.S中的函数,并最后调用start_kernel,看下面head-common.S的源码:

14 #define ATAG_CORE 0x54410001

15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)

16 #define ATAG_CORE_SIZE_EMPTY ((2*4) >> 2)

17

18         .align  2

19         .type   __switch_data, %object

20 __switch_data:

21         .long   __mmap_switched

22         .long   __data_loc                      @ r4

23         .long   _data                           @ r5

24         .long   __bss_start                     @ r6

25         .long   _end                            @ r7

26         .long   processor_id                    @ r4

27         .long   __machine_arch_type             @ r5

28         .long   __atags_pointer                 @ r6

29         .long   cr_alignment                    @ r7

30         .long   init_thread_union + THREAD_START_SP @ sp

31

32 /*

33  * The following fragment of code is executed with the MMU on in MMU mode,

34  * and uses absolute addresses; this is not position independent.

35  *

36  *  r0  = cp#15 control register

37  *  r1  = machine ID

38  *  r2  = atags pointer

39  *  r9  = processor ID

40  */

41 __mmap_switched:

42         adr     r3, __switch_data + 4

43

44         ldmia   r3!, {r4, r5, r6, r7}

45         cmp     r4, r5                          @ Copy data segment if needed

46 1:      cmpne   r5, r6

47         ldrne   fp, [r4], #4

48         strne   fp, [r5], #4

49         bne     1b

50

51         mov     fp, #0                          @ Clear BSS (and zero fp)

52 1:      cmp     r6, r7

53         strcc   fp, [r6],#4

54         bcc     1b

55

56  ARM(   ldmia   r3, {r4, r5, r6, r7, sp})

57  THUMB( ldmia   r3, {r4, r5, r6, r7}    )

58  THUMB( ldr     sp, [r3, #16]           )

59         str     r9, [r4]                        @ Save processor ID

60         str     r1, [r5]                        @ Save machine type

61         str     r2, [r6]                        @ Save atags pointer

62         bic     r4, r0, #CR_A                   @ Clear 'A' bit

63         stmia   r7, {r0, r4}                    @ Save control register values

64         b       start_kernel

str r2,[r6]:因为通用寄存器2 (r2) 必须是 kernel parameter list 的物理地址(parameter list 是由boot loader传递给kernel,用来描述设备信息属性的列表),所以将uboot传递进来的tags物理地址数值存入__atags_pointer指针( [r6] )中,__atags_pointer在第28行定义并通过42、56行将其加载到r6中,在arch/arm/kernel/setup.c中的setup_arch中将引用__atags_pointer为指向参数的地址.

init/main.c中的start_kernel函数中会调用setup_arch函数来处理各种平台相关的动作

start_kernel()

{

……

setup_arch(&command_line);

……

}

包括了u-boot传递过来参数的分析和保存,对tag的处理代码也在setup_arch里面。以下是一部分的关键代码(setup_arch函数在arch/arm/kernel/setup.c文件中实现):

767 void __init setup_arch(char **cmdline_p)

768 {

769         struct tag *tags = (struct tag *)&init_tags;//tags指向默认的tag链表

770         struct machine_desc *mdesc;

771         char *from = default_command_line;

772

773         unwind_init();

774

775         setup_processor();

776         mdesc = setup_machine(machine_arch_type);// mdesc包含启动参数在内存中的地址

.....................................................................................................

782         if (__atags_pointer) //检查BootLoader是否传入参数

783                 tags = phys_to_virt(__atags_pointer);//bootloader有传递启动参数到内核

784         else if (mdesc->boot_params)//如果BootLoader没有传入参数则使用内核machine descriptor中设置的启动参数地址(arch/arm/mach-s3c2410/mach-smdk2410.c),这里设置的地址与BootLoader是否传入的一般是一致的。

785                 tags = phys_to_virt(mdesc->boot_params);

786

787 #if defined(CONFIG_DEPRECATED_PARAM_STRUCT)

788         /*

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

790          * a tag list.

791          */

792         if (tags->hdr.tag != ATAG_CORE)//如果是旧的启动参数结构,将其转成新的tag链表的形式,新的tag链表的形式内核参数列表第一项必须是ATAG_CORE类型,如果不是,则需要转换成新的内核参数类型。

793                 convert_to_tag_list(tags);//此函数完成新旧参数结构转换,将参数结构转换为tag list结构

794 #endif

795         if (tags->hdr.tag != ATAG_CORE)//转换失败,使用内置的启动参数

796                 tags = (struct tag *)&init_tags;//则选用默认的内核参数,init_tags文件中有定义。

797

798         if (mdesc->fixup)  //用内核参数列表填充meminfo,fixup函数出现在注册machine_desc中,即MACHINE_START、MACHINE_END定义中,这个函数,有些板子有,但在2410中没有定义这个函数。

799                 mdesc->fixup(mdesc, tags, &from, &meminfo);

800

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

802                 if (meminfo.nr_banks != 0) //说明内存被初始化过

803                         squash_mem_tags(tags);//如果在meminfo中有配置内存tag则跳过对内存tag的处理,如果是tag list,那么如果系统已经创建了默认的meminfo.nr_banks,清除tags中关于MEM的参数,以免再次被初始化

804                 save_atags(tags);

805                 parse_tags(tags);//做出一些针对各个tags的处理

806         }

.....................................................................................................

851 }

第769行tags指向默认的tag链表,内核中定义了一些默认的tags

init_tags在arch/arm/kernel/setup.c文件下定义如下

662 static struct init_tags {

663         struct tag_header hdr1;

664         struct tag_core   core;

665         struct tag_header hdr2;

666         struct tag_mem32  mem;

667         struct tag_header hdr3;

668 } init_tags __initdata = {

669         { tag_size(tag_core), ATAG_CORE },

670         { 1, PAGE_SIZE, 0xff },

671         { tag_size(tag_mem32), ATAG_MEM },

672         { MEM_SIZE, PHYS_OFFSET },

673         { 0, ATAG_NONE }

674 };

上述结构中一个tag_header和tag_xxx形成了tag的完整描述,tag_size返回tag_head和tag_xxx的总大小,在tag_size中我们要注意的是u32*指针加1地址值实际上地址加了4

#define tag_next(t) ((struct tag*)((u32*)(t)+(t)->hdr.size))

#define tag_size(type) ((sizeof(struct tag_header)+sizeof(struct type)) >> 2

tag_size实际上计算的是(tag_head+tag_xxx)/4。经过进一步的分析还发现每个tag在内存中的大小并不是相同的,这一点可以从tag_next看出,tag_next只是将指针移到了下一个tag的tag_header处,这种内存布局更加紧凑。

注:2.6.18内核smdk2410的meminfo没有设置nr_banks,所以必须在内核的启动参数里面传递mem=”memory size”@”memory base address”,否则系统识别内存错误,这点从系统的启动信息就可以看出来,而且在加载initrd的时候也会遇到内存溢出的错误

if (__atags_pointer)

tags = phys_to_virt(__atags_pointer);

指向各种tag起始位置的指针,定义如下:

unsigned int __atags_pointer __initdata;

此指针指向__initdata段,各种tag的信息保存在这个段中。

mdesc->fixup(mdesc, tags, &from, &meminfo):fixup函数是板级相关的,通常就是一些ram起址大小bank之类的设定函数,如果执行过了,nrbank就不为0了,那么继续执行后面的语句时:

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

if (meminfo.nr_banks != 0)

squash_mem_tags(tags);

parse_tags(tags);

}

就会调用squash_mem_tags把你u-boot传入的值给干掉,使parse_tags函数调用时不会处理ATAG_MEM。

然后执行到parse_tags

parse_tags定义如下(arch/arm/kernel/setup.c)

static void __init parse_tags(const struct tag *t)

{

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

if (!parse_tag(t)) //针对每个tag 调用parse_tag 函数

printk(KERN_WARNING

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

t->hdr.tag);

}

parse_tags遍历tag链表调用parse_tag对tag进行处理。parse_tags在tabtable中寻找tag的处理函数(通过tag_header结构中的tag)

static int __init parse_tag(const struct tag *tag)

{

extern struct tagtable __tagtable_begin, __tagtable_end;

struct tagtable *t;

for (t = &__tagtable_begin; t < &__tagtable_end; t++) //遍历tagtable列表,并调用处理函数,

if (tag->hdr.tag == t->tag) {

t->parse(tag); //调用处理函数

break;

}

return t < &__tagtable_end;

}

处理各种tags,其中包括了RAM参数的处理。这个函数处理如下tags:

561 __tagtable(ATAG_MEM, parse_tag_mem32);

554 __tagtable(ATAG_CORE, parse_tag_core);

555

对于处理RAM的tag,调用了parse_tag_mem32函数:

556 static int __init parse_tag_mem32(const struct tag *tag)

557 {

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

559 }

560

561 __tagtable(ATAG_MEM, parse_tag_mem32);

如上可见,parse_tag_mem32函数调用arm_add_memory函数把RAM的start和size等参数保存到了meminfo结构的meminfo结构体中。对照uboot部分内存初始化函数,我们知道uboot传递过来的tag->u.mem.start, tag->u.mem.size分别为0x30000000,0x4000000,现在再来分析arm_add_memory

arm_add_memory定义如下(arch/arm/kernel/setup.c)

static int __init arm_add_memory(unsigned long start, unsigned long size)

{

struct membank *bank = &meminfo.bank[meminfo.nr_banks];

if (meminfo.nr_banks >= NR_BANKS) {

printk(KERN_CRIT "NR_BANKS too low, "

"ignoring memory at %#lx\n", start);

return -EINVAL;

}

/*

* Ensure that start/size are aligned to a page boundary.

* Size is appropriately rounded down, start is rounded up.

*/

size -= start & ~PAGE_MASK;

bank->start = PAGE_ALIGN(start);

bank->size  = size & PAGE_MASK;

/*

* Check whether this memory region has non-zero size or

* invalid node number.

*/

if (bank->size == 0)

return -EINVAL;

meminfo.nr_banks++;

return 0;

}

经过这样的处理,setup.c文件中的meminfo可就不再是

struct meminfo meminfo  = { 0, };

而是

struct meminfo meminfo  = { 1,{0x30000000,0x4000000,0},{}, };

表示当前有一个内存区域,物理地址是从0x30000000开始,大小是64M

最后,在setup_arch中执行下面语句

paging_init(&meminfo, mdesc)

再来看看另一个参数处理函数

618 static int __init parse_tag_cmdline(const struct tag *tag)

619 {

620         strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);

621         return 0;

622 }

623

624 __tagtable(ATAG_CMDLINE, parse_tag_cmdline);

767 void __init setup_arch(char **cmdline_p)

768 {

769         struct tag *tags = (struct tag *)&init_tags;

770         struct machine_desc *mdesc;

771         char *from = default_command_line;

771行default_command_line在setup.c文件129行中定义如下:

static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;

其中CONFIG_CMDLINE在“.config”配置文件中定义的。定义如下:

CONFIG_CMDLINE="root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"

default_command_line 原来的内容是我们配置文件中确定的,但是现在,他被tag->u.cmdline.cmdline覆盖了。可见,从uboot传递过来的命令行参数的优先级要高于配置文件的默认命令行.

我们接着setup_arch中的parse_tags(tags)往下看:

808         init_mm.start_code = (unsigned long) _text;

809         init_mm.end_code   = (unsigned long) _etext;

810         init_mm.end_data   = (unsigned long) _edata;

811         init_mm.brk        = (unsigned long) _end;

812

813         /* parse_early_param needs a boot_command_line */

814         strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

815

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

817         strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);

818         *cmdline_p = cmd_line;

819

820         parse_early_param();

821

822         arm_memblock_init(&meminfo, mdesc);

823

824         paging_init(mdesc);

825         request_standard_resources(&meminfo, mdesc);

init_mm.brk    = (unsigned long) _end:从这儿之后的内存可以动态的分配了。填充 init_mm 的成员,这些数值在lds里面。分别是代码段,数据段和bss段。

strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);

上面的代码先把uboot传递过来的命令行参数保存起来,以备后用。

linux内核commandline参数解析过程

前面详细分析了u-boot与linux内核间的tag参数传递及解析过程,但对命令行参数没做详细的分析,在setup_arch函数的第817行我们看到把uboot传递过来的命令行参数保存起来,以备后用。这里的后用就是linux内核commandline参数解析,也就是给第820行代码备用的,对2.6.36以前版本没用 parse_early_param而是用parse_cmdline函数,在分析这两个函数前,我们先来看一下从u-boot到内核命令行参数设置及传递过程,以便更好的理解后面的分析。

在u-boot的include/configs/smdk2410.h配置文件中我们可以找到CONFIG_BOOTARGS配置项,在这里我们可以设置要传递的到内核的命令行参数,如:

*#define CONFIG_BOOTARGS   "root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"

再看u-boot的common/env_common.c文件

#ifdef CONFIG_BOOTARGS

"bootargs=" CONFIG_BOOTARGS   "\0"

#endif

#ifdef CONFIG_BOOTCOMMAND

"bootcmd=" CONFIG_BOOTCOMMAND  "\0"

#endif

#ifdef CONFIG_RAMBOOTCOMMAND

"ramboot=" CONFIG_RAMBOOTCOMMAND  "\0"

#endif

#ifdef CONFIG_NFSBOOTCOMMAND

"nfsboot=" CONFIG_NFSBOOTCOMMAND  "\0"

#endif

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)

"bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0"

#endif

#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)

"baudrate=" MK_STR(CONFIG_BAUDRATE)  "\0"

#endif

#ifdef CONFIG_LOADS_ECHO

"loads_echo=" MK_STR(CONFIG_LOADS_ECHO) "\0"

#endif

#ifdef CONFIG_ETHADDR

"ethaddr=" MK_STR(CONFIG_ETHADDR)  "\0"

#endif

#ifdef CONFIG_ETH1ADDR

"eth1addr=" MK_STR(CONFIG_ETH1ADDR)  "\0"

#endif

#ifdef CONFIG_ETH2ADDR

"eth2addr=" MK_STR(CONFIG_ETH2ADDR)  "\0"

#endif

#ifdef CONFIG_ETH3ADDR

"eth3addr=" MK_STR(CONFIG_ETH3ADDR)  "\0"

#endif

#ifdef CONFIG_IPADDR

"ipaddr=" MK_STR(CONFIG_IPADDR)  "\0"

#endif

#ifdef CONFIG_SERVERIP

"serverip=" MK_STR(CONFIG_SERVERIP)  "\0"

#endif

#ifdef CFG_AUTOLOAD

"autoload=" CFG_AUTOLOAD   "\0"

#endif

#ifdef CONFIG_PREBOOT

"preboot=" CONFIG_PREBOOT   "\0"

#endif

#ifdef CONFIG_ROOTPATH

"rootpath=" MK_STR(CONFIG_ROOTPATH)  "\0"

#endif

#ifdef CONFIG_GATEWAYIP

"gatewayip=" MK_STR(CONFIG_GATEWAYIP) "\0"

#endif

#ifdef CONFIG_NETMASK

"netmask=" MK_STR(CONFIG_NETMASK)  "\0"

#endif

#ifdef CONFIG_HOSTNAME

"hostname=" MK_STR(CONFIG_HOSTNAME)  "\0"

#endif

#ifdef CONFIG_BOOTFILE

"bootfile=" MK_STR(CONFIG_BOOTFILE)  "\0"

#endif

#ifdef CONFIG_LOADADDR

"loadaddr=" MK_STR(CONFIG_LOADADDR)  "\0"

#endif

。。。。。。。。。。。。。。。

可以知道CONFIG_BOOTARGS被转化为

"bootargs=""root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"

u-boot引导内核为调用u-boot的lib_arm/armlinux.c文件的do_bootm_linux函数

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],

ulong addr, ulong *len_ptr, int verify)

{

ulong len = 0, checksum;

ulong initrd_start, initrd_end;

ulong data;

void (*theKernel)(int zero, int arch, uint params);

image_header_t *hdr = &header;

bd_t *bd = gd->bd;

init/main.c

void __init parse_early_options(char *cmdline)

{

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

}

/* Arch code calls this early on, or if not, just before other parsing. */

void __init parse_early_param(void)

{

static __initdata int done = 0;

static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];

if (done)

return;

/* All fall through to do_early_param. */

strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);

parse_early_options(tmp_cmdline);

done = 1;

}

在上面我们可以看到最终调用的是 parse_args("early options", cmdline, NULL, 0, do_early_param);parse_args在kernel/params.c中定义,注意它与前面的parse_tags(const struct tag *t)区别。

/* Args looks like "foo=bar,bar2 baz=fuz wiz". */

int parse_args(const char *name,

char *args,

const struct kernel_param *params,

unsigned num,

int (*unknown)(char *param, char *val))

{

char *param, *val;

DEBUGP("Parsing ARGS: %s\n", args);

/* Chew leading spaces  跳过前面的空格*/

args = skip_spaces(args);

while (*args) {

int ret;

int irq_was_disabled;

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

irq_was_disabled = irqs_disabled();

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

if (irq_was_disabled && !irqs_disabled()) {

printk(KERN_WARNING "parse_args(): option '%s' enabled "

"irq's!\n", param);

}

switch (ret) {

case -ENOENT:

printk(KERN_ERR "%s: Unknown parameter `%s'\n",

name, param);

return ret;

case -ENOSPC:

printk(KERN_ERR

"%s: `%s' too large for parameter `%s'\n",

name, val ?: "", param);

return ret;

case 0:

break;

default:

printk(KERN_ERR

"%s: `%s' invalid for parameter `%s'\n",

name, val ?: "", param);

return ret;

}

}

/* All parsed OK. */

return 0;

}

#define isspace(c)    ((c) == ' ')

char *skip_spaces(const char *str) 跳过前面的空格函数

{

while (isspace(*str))

++str;

return (char *)str;

}

EXPORT_SYMBOL(skip_spaces);

此时相当于执行:parse_one("root", "/dev/mtdblock3",NULL, 0, do_early_param);

static int parse_one(char *param,

char *val,

const struct kernel_param *params,

unsigned num_params,

int (*handle_unknown)(char *param, char *val))

{

unsigned int i;

int err;

/* Find parameter */

for (i = 0; i < num_params; i++) {

if (parameq(param, params[i].name)) { //因为传入的params为NULL,这下面不执行

/* Noone handled NULL, so do it here. */

if (!val && params[i].ops->set != param_set_bool)

return -EINVAL;

DEBUGP("They are equal!  Calling %p\n",

params[i].ops->set);

mutex_lock(&param_lock);

err = params[i].ops->set(val, &params[i]);

mutex_unlock(&param_lock);

return err;

}

}

if (handle_unknown) { //调用do_early_param函数

DEBUGP("Unknown argument: calling %p\n", handle_unknown);

return handle_unknown(param, val);

}

DEBUGP("Unknown argument `%s'\n", param);

return -ENOENT;

}

以下只作了解:

阅读(30834) | 评论(2) | 转发(17) |

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值