您的位置 首页 主动

你了解u-boot与linux内核间的参数传递进程?

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

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)

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 (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
    setup_revision_tag (¶ms);
#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函数中现已初始化.

78

static struct tag *setup_commandline_tag(struct tag *params, char *cmdline)
{
 if (!cmdline)
  return params;

/* eat leading white space */
 while (*cmdline == ' ') cmdline++;

/*
  * Don't include tags for empty command lines; let the kernel
  * use its default command line.
  */
 if (*cmdline == '\0')
  return params;

params->hdr.tag = ATAG_CMDLINE;
 params->hdr.size =
  (sizeof (struct tag_header) + strlen(cmdline) + 1 + 3) >> 2;
 strcpy(params->u.cmdline.cmdline, cmdline);

return tag_next(params);
}


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文件

static uchar env_get_char_init (int index);
uchar (*env_get_char)(int) = env_get_char_init;

/************************************************************************
 * Default settings to be used when no valid environment is found
 */
#define XMK_STR(x) #x
#define MK_STR(x) XMK_STR(x)

uchar default_environment[] = {
#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;


#ifdef CONFIG_CMDLINE_TAG
 char *commandline = getenv (“bootargs”);
#endif

245

#ifdef CONFIG_CMDLINE_TAG
 setup_commandline_tag (bd, commandline);
#endif

……………………..

}
在这儿它首要调用getenv (“bootargs”)函数取得指令行参数并让commandline指向它,然后调用setup_commandline_tag函数将指令行参数放到tag参数例表,

static struct tag *setup_commandline_tag(struct tag *params, char *cmdline)
{
 if (!cmdline)
  return params;

/* eat leading white space */
 while (*cmdline == ' ') cmdline++;

/*
  * Don't include tags for empty command lines; let the kernel
  * use its default command line.
  */
 if (*cmdline == '\0')
  return params;

params->hdr.tag = ATAG_CMDLINE;
 params->hdr.size =
  (sizeof (struct tag_header) + strlen(cmdline) + 1 + 3) >> 2;
 strcpy(params->u.cmdline.cmdline, cmdline);

return tag_next(params);
}

关于tag参数例表前面己有详细剖析,这儿我只对u-boot取指令行环境参数函数getenv进行剖析,它界说在common/cmd_nvedit.c文件中

char *getenv (char *name)
{
 int i, nxt;

WATCHDOG_RESET();

for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
  int val;

for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
   if (nxt >= CFG_ENV_SIZE) {
    return (NULL);
   }
  }
  if ((val=envmatch((uchar *)name, i)) < 0)
   continue;
  return ((char *)env_get_addr(val));
 }

return (NULL);
}
这儿要点了解env_get_char函数,它界说在common/env_common.c中:

static uchar env_get_char_init (int index);
uchar (*env_get_char)(int) = env_get_char_init;

/************************************************************************
 * Default settings to be used when no valid environment is found
 */
#define XMK_STR(x) #x
#define MK_STR(x) XMK_STR(x)

uchar default_environment[] = {
#ifdef CONFIG_BOOTARGS
 “bootargs=” CONFIG_BOOTARGS   “\0”
#endif

……………..

static uchar env_get_char_init (int index)
{
 uchar c;

/* if crc was bad, use the default environment */
 if (gd->env_valid)
 {
  c = env_get_char_spec(index);
 } else {
  c = default_environment[index];
 }

return (c);
}
这儿gd->env_valid参数在start_armboot函数中的初始化函数例表中的env_init函数中设置,假如装备参数保存在flash中,gd->env_valid被设置为1,这儿就经过env_get_char_spec函数从flash中取参数,不然gd->env_valid设置为0,运用默许环境变量参数,默许环境变量参数界说在u-boot的common/env_common.c文件uchar default_environment[] ,也便是include/configs/smdk2410.h装备文件中装备的参数。这儿针对不同的flash存储芯片有不同的env_get_char_spec界说

common/env_flash.c

uchar env_get_char_spec (int index)
{
 return ( *((uchar *)(gd->env_addr + index)) );
}

common/env_nand.c

DECLARE_GLOBAL_DATA_PTR;

uchar env_get_char_spec (int index)
{
 return ( *((uchar *)(gd->env_addr + index)) );
}

common/env_nvram.c

#ifdef CONFIG_AMIGAONEG3SE
uchar env_get_char_spec (int index)
{
#ifdef CFG_NVRAM_ACCESS_ROUTINE
 uchar c;

nvram_read(&c, CFG_ENV_ADDR+index, 1);

return c;
#else
 uchar retval;
 enable_nvram();
 retval = *((uchar *)(gd->env_addr + index));
 disable_nvram();
 return retval;
#endif
}
#else
uchar env_get_char_spec (int index)
{
#ifdef CFG_NVRAM_ACCESS_ROUTINE
 uchar c;

nvram_read(&c, CFG_ENV_ADDR+index, 1);

return c;
#else
 return *((uchar *)(gd->env_addr + index));
#endif
}
#endif
为确认gd->env_addr,咱们来看一下env_init函数,这儿以flash为例,它在common/env_flash.c中
int  env_init(void)
{
#ifdef CONFIG_OMAP2420H4
    int flash_probe(void);

    if(flash_probe() == 0)
        goto bad_flash;
#endif
    if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
        gd->env_addr  = (ulong)&(env_ptr->data);
        gd->env_valid = 1;
        return(0);
    }
#ifdef CONFIG_OMAP2420H4
bad_flash:
#endif
    gd->env_addr  = (ulong)&default_environment[0];
    gd->env_valid = 0;  运用默许环境变量参数,gd->env_valid设置为0
    return (0);
}
而在include/configs/smdk2410.h装备文件中关于flsah的装备如下:
#define PHYS_FLASH_1            0x00000000 /* Flash Bank #1 */
#define CFG_FLASH_BASE          PHYS_FLASH_1

/*———————————————————————–
 * FLASH and environment organization
 */
#define CONFIG_AMD_LV400        1       /* uncomment this if you have a LV400 flash */
#if 0
#define CONFIG_AMD_LV800        1       /* uncomment this if you have a LV800 flash */
#endif

#define CFG_MAX_FLASH_BANKS     1       /* max number of memory banks */
#ifdef CONFIG_AMD_LV800
#define PHYS_FLASH_SIZE         0x00100000 /* 1MB */
#define CFG_MAX_FLASH_SECT      (19)    /* max number of sectors on one chip */
#define CFG_ENV_ADDR            (CFG_FLASH_BASE + 0x0F0000) /* addr of environment */
#endif
#ifdef CONFIG_AMD_LV400
#define PHYS_FLASH_SIZE         0x00080000 /* 512KB */
#define CFG_MAX_FLASH_SECT      (11)    /* max number of sectors on one chip */
#define CFG_ENV_ADDR            (CFG_FLASH_BASE + 0x070000) /* addr of environment */
#endif

/* timeout values are in ticks */
#define CFG_FLASH_ERASE_TOUT    (5*CFG_HZ) /* Timeout for Flash Erase */
#define CFG_FLASH_WRITE_TOUT    (5*CFG_HZ) /* Timeout for Flash Write */

#define CFG_ENV_IS_IN_FLASH     1
#define CFG_ENV_SIZE            0x10000 /* Total Size of Environment Sector */

#endif  /* __CONFIG_H */
在common/env_flash.c中对env_ptr界说如下:
char * env_name_spec = “Flash”;

#ifdef ENV_IS_EMBEDDED

extern uchar environment[];
env_t *env_ptr = (env_t *)(&environment[0]);

#ifdef CMD_SAVEENV
/* static env_t *flash_addr = (env_t *)(&environment[0]);-broken on ARM-wd-*/
static env_t *flash_addr = (env_t *)CFG_ENV_ADDR;
#endif

#else /* ! ENV_IS_EMBEDDED */

env_t *env_ptr = (env_t *)CFG_ENV_ADDR;
#ifdef CMD_SAVEENV
static env_t *flash_addr = (env_t *)CFG_ENV_ADDR;
#endif

#endif /* ENV_IS_EMBEDDED */
经过上面几个文件相关界说,咱们很简单知道env_get_char_init函数功用便是假如保存了参数到flsah中就调用env_get_char_spec从指定的flash地址中读取参数字符,不然就从默许环境变量参数中读取参数字符。

了解完env_get_char_init函数后,再来看envmatch函数,界说在common/cmd_nvedit.c

/************************************************************************
 * Match a name / name=value pair
 *
 * s1 is either a simple 'name', or a 'name=value' pair.
 * i2 is the environment index for a 'name2=value2' pair.
 * If the names match, return the index for the value2, else NULL.
 */

static int
envmatch (uchar *s1, int i2)
{

while (*s1 == env_get_char(i2++))
  if (*s1++ == '=')
   return(i2);
 if (*s1 == '\0' && env_get_char(i2-1) == '=')
  return(i2);
 return(-1);
}
这个函数功用是查找符号变量,假如找到则回来等号后边的字符串指针,即为变量的值,环境变量表是一个字符串数组,而其间的变量之间经过’\0’符号离隔,便是当遇到该符号时,则标明一个变量完毕而另一个变量开端。

common/env_common.c

uchar *env_get_addr (int index)
{
 if (gd->env_valid) {
  return ( ((uchar *)(gd->env_addr + index)) );
 } else {
  return (&default_environment[index]);
 }
}
这个函数功用是回来找到的环境变量字符串数组地址。
此至,指令行参数在u-boot中设置及传递进程剖析完了,下面咱们再来看linux内核commandline参数解析进程,内核在start_kernel函数调用start_arch获取tag参数地址后,再调用parse_tags完结了tag参数解说,之后便是linux内核commandline参数解析,也就调用parse_early_param或parse_cmdline(对2.6.36曾经版别)函数来完结对指令行参数的解说。

linux内核commandline参数解析进程
前面运用
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
    *cmdline_p = cmd_line;
将指令行参数保存到了cmd_line中
parse_early_param();

现在再来看看start_arch函数中第820行的parse_early_param函数
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, ¶m, &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);
 

关于next_arg就在parse_args前界说如下:
它的功用解说”root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M”参数表,以第一个root=/dev/mtdblock3为例阐明:
static char *next_arg(char *args, char **param, char **val)
{
    unsigned int i, equals = 0;
    int in_quote = 0, quoted = 0; //in_quote字符串完毕标志
    char *next;
   // [args] 指向内容”root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M”
    if (*args == '”') {
        args++;  //[args]指向内容root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M”
        in_quote = 1;  //in_quote字符串开端标志
        quoted = 1;
    }

    for (i = 0; args[i]; i++) { //循环完后,
        if (isspace(args[i]) && !in_quote)  //空格或没有字符了,退出。
            break;
        if (equals == 0) { //查找到第一个=号方位
            if (args[i] == '=')
                equals = i;
        }
        if (args[i] == '”')  //终究一个完毕字符'”'吗?是的话设置in_quote = !in_quote
            in_quote = !in_quote;
    }

    *param = args;  
   //[args] 指向内容root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M”
    if (!equals)
        *val = NULL;
    else {
        args[equals] = '\0';
      //[args]指向内容root /dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M”
        *val = args + equals + 1;  
      // *val指向内容/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M”

        /* Don't include quotes in value. 去掉引号*/
        if (**val == '”') {
            (*val)++;
            if (args[i-1] == '”')
                args[i-1] = '\0';
        }
        if (quoted && args[i-1] == '”')
            args[i-1] = '\0';
    }

    if (args[i]) {
        args[i] = '\0';
        next = args + i + 1;
    } else
        next = args + i;

    /* Chew up trailing spaces. */
    return skip_spaces(next);
}

第一次履行后,[*param] = root  [*val] = /dev/mtdblock3 
next指向init=/linuxrc console=ttySAC0,115200 mem=64M

现在再看parse_one,它界说在同一文件下:
此刻相当于履行: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(¶m_lock);
            err = params[i].ops->set(val, ¶ms[i]);
            mutex_unlock(¶m_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;
}
以下只作了解:

kernel/params.c
static inline char dash2underscore(char c)
{
    if (c == '-')
        return '_';
    return c;
}

static inline int parameq(const char *input, const char *paramname)
{
    unsigned int i;
    for (i = 0; dash2underscore(input[i]) == paramname[i]; i++)
        if (input[i] == '\0')
            return 1;
    return 0;

咱们再来看一下do_early_param函数,它在init/main.c中
static int __init do_early_param(char *param, char *val)
{
    const struct obs_kernel_param *p;
    这儿的__setup_start和_-setup_end别离是.init.setup段的开端和完毕的地址
    for (p = __setup_start; p < __setup_end; p++) { 假如没有early标志则越过
        if ((p->early && strcmp(param, p->str) == 0) ||
            (strcmp(param, “console”) == 0 &&
             strcmp(p->str, “earlycon”) == 0)
        ) {   
            if (p->setup_func(val) != 0)  调用处理函数
                printk(KERN_WARNING
                       “Malformed early option '%s'\n”, param);
        }
    }
    /* We accept everything at this stage. */
    return 0;
}
在do_early_param函数中for循环遍历obs_kernel_param数组,这儿首要要阐明一下struct obs_kernel_param结构及这个数组的由来。
obs_kernel_param结构界说在include/linux/init.h文件中
218 struct obs_kernel_param {
219         const char *str;
220         int (*setup_func)(char *);
221         int early;
222 };

前两个参数很简单,一个是key,一个是处理函数。终究一个参数其实也便是相似于优先级的一个标志flag,由于传递给内核的参数中,有一些需求比别的的一些更早的解析。(这也便是为什么early_param和setup传递的终究一个参数的不同的原因了,后边会讲到)。
先说一下体系发动时对bootloader传递参数的初始化,即linux发动参数的完结,发动参数的完结,咱们知道boot传递给内核的参数都是 “name_varibale=value”这种方法的,如下:
CONFIG_CMDLINE=”root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M”
那么内核怎样知道传递进来的参数该怎样去处理呢?
内核是经过__setup宏或许early_param宏来将参数与参数所处理的函数相相关起来的。咱们接下来就来看这个宏以及相关的结构的界说:
include/linux/init.h
230 #define __setup_param(str, unique_id, fn, early)                        \
231         static const char __setup_str_##unique_id[] __initconst \
232                 __aligned(1) = str; \
233         static struct obs_kernel_param __setup_##unique_id      \
234                 __used __section(.init.setup)                   \
235                 __attribute__((aligned((sizeof(long)))))        \
236                 = { __setup_str_##unique_id, fn, early }
237 
238 #define __setup(str, fn)                                        \
239         __setup_param(str, fn, fn, 0)
240 
241 /* NOTE: fn is as per module_param, not __setup!  Emits warning if fn
242  * returns non-zero. */
243 #define early_param(str, fn)                                    \
244         __setup_param(str, fn, fn, 1)
可见,__setup宏的作用是运用str值和函数句柄fn初始化一个static结构体 obs_kernel_param。该结构体在链接后存在于.init.setup段。其实该段也便是一切内核参数地点的处。该段的开端地址是__setup_start,完毕地址为__setup_end。相同的还有一个early_param宏,也是设置一个内核参数,不过改参数是前期发动时相关的。
看起来很杂乱。 首要setup宏第一个参数是一个key。比方”netdev=”这样的,而第二个参数是这个key对应的处理函数。这儿要留意相同的handler能联系到不同的key。__setup与early_param不同的是,early_param宏注册的内核选项有必要要在其他内核选项之前被处理。在函数 start_kernel中,parse_early_param处理early_param界说的参数,parse_args处理__setup界说的参数。
early_param和setup仅有不同的便是传递给__setup_param的终究一个参数,这个参数下面会阐明,而接下来 _setup_param界说了一个struct obs_kernel_param类型的结构,然后经过_section宏,使这个变量在链接的时分可以放置在段.init.setup(后边会详细介绍内核中的这些初始化段).

比方说界说一个内核参数来完结对init程序的指定。见init/main.c中
1,一切的体系发动参数都是由形如
static int __init init_setup(char *str)的函数来支撑的

static int __init init_setup(char *str)
{
    unsigned int i;

    execute_command = str;
    for (i = 1; i < MAX_INIT_ARGS; i++)
        argv_init[i] = NULL;
    return 1;
}
__setup(“init=”, init_setup);
注:(include/linux/init.h):
#define __init          __section(.init.text) __cold notrace声明一切的发动参数支撑函数都放入.init.text段
2.1,用__setup宏来导出参数的支撑函数
__setup(“init=”, init_setup);
打开后便是如下的方法

static const char __setup_str_init_setup[] __initdata = “init=”;   
static struct obs_kernel_param __setup_init_setup               
        __used __section__(“.init.setup”)
        __attribute__((aligned((sizeof(long)))))   
        = { __setup_str_init_setup, init_setup, 0 };//”init=”,init_setup,0
也便是说,发动参数(函数指针)被封装到obs_kernel_param结构中,
一切的内核发动参数构成内核映像.init.setup段中的一个
obs_kernel_param数组,而在do_early_param函数中for循环便是遍历obs_kernel_param数组,首要看是否
设置early,假如有设置并查找到到对就的key,就调用相关early_param函数处理。
用early_param宏来声明需求'前期'处理的发动参数,例如在
arch/arm/kernel/setup.c就有如下的声明:
468 early_param(“mem”, early_mem);
打开后和__setup是相同的仅仅early参数不相同,因而会在do_early_param
中被处理
443 static int __init early_mem(char *p)
444 {
445         static int usermem __initdata = 0;
446         unsigned long size, start;
447         char *endp;
448 
449         /*
453          */
454         if (usermem == 0) {
455                 usermem = 1;
456                 meminfo.nr_banks = 0;
457         }
458 
459         start = PHYS_OFFSET;
460         size  = memparse(p, &endp);
461         if (*endp == '@')
462                 start = memparse(endp + 1, NULL);
463 
464         arm_add_memory(start, size);
465 
466         return 0;
467 }

init/main.c中发动参数声明列表:
early_param(“nosmp”, nosmp);
early_param(“nr_cpus”, nrcpus);
early_param(“maxcpus”, maxcpus);
__setup(“reset_devices”, set_reset_devices);
early_param(“debug”, debug_kernel);
early_param(“quiet”, quiet_kernel);
early_param(“loglevel”, loglevel);
__setup(“init=”, init_setup);
__setup(“rdinit=”, rdinit_setup);
arch/arm/kernel/setup.c中发动参数声明列表:
__setup(“fpe=”, fpe_setup);
early_param(“mem”, early_mem);
early_param(“elfcorehdr”, setup_elfcorehdr);

留意在2.6.36版别中,除在start_kernel中的start_arch函数中第820调用parse_early_param()外,还在start_arch函数后的580行和581行别离履行下面代码再次解说指令行参数
579         printk(KERN_NOTICE “Kernel command line: %s\n”, boot_command_line);
580         parse_early_param();
581         parse_args(“Booting kernel”, static_command_line, __start___param,
582                    __stop___param – __start___param,
583                    &unknown_bootoption);
关于parse_early_param咱们现已很熟悉,在这儿不知道为什么它再做的一次,下面看第581行,咱们再来看看parse_args函数,见前面的代码分解出指令行的参数后,会调用
ret = parse_one(param, val, params, num, unknown);
相当于:
ret = parse_one(param, val, __start___param,__stop___param – __start___param,&unknown_bootoption);
这儿提到了两个参数__start___param和__stop___param,它涉及到kernel_param 结构体和参数的存储
关于内核参数结构体的界说,见include/linux/moduleparam.h
 99 #define module_param(name, type, perm)                          \
100         module_param_named(name, name, type, perm)

113 #define module_param_named(name, value, type, perm)                        \
114         param_check_##type(name, &(value));                                \
115         module_param_cb(name, ¶m_ops_##type, &value, perm);            \
116         __MODULE_PARM_TYPE(name, #type)

126 #define module_param_cb(name, ops, arg, perm)                                     \
127         __module_param_call(MODULE_PARAM_PREFIX,                                  \
128                             name, ops, arg, __same_type((arg), bool *), perm    )

142 #define __module_param_call(prefix, name, ops, arg, isbool, perm)       \
143         /* Default value instead of permissions? */                     \
144         static int __param_perm_check_##name __attribute__((unused)) =  \
145         BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2))  \
146         + BUILD_BUG_ON_ZERO(sizeof(“”prefix) > MAX_PARAM_PREFIX_LEN);   \
147         static const char __param_str_##name[] = prefix #name;          \
148         static struct kernel_param __moduleparam_const __param_##name   \
149         __used                                                          \
150     __attribute__ ((unused,__section__ (“__param”),aligned(sizeof(void *))))     \
151         = { __param_str_##name, ops, perm, isbool ? KPARAM_ISBOOL : 0,  \
152             { arg } }
这儿也便是填充了 struct kernel_param的结构体,并将这个变量符号为__param段,以便于链接器将此变量装载到指定的段,和结构体 obs_kernel_param相似,该宏函数坚持一切实例存在于__param段。该段的开端地址是__start___param,完毕地址为__stop___param。详细链接脚本在include/asm-generic/vmlinux.lds.h
350         /* Built-in module parameters. */                               \
351         __param : AT(ADDR(__param) – LOAD_OFFSET) {                     \
352                 VMLINUX_SYMBOL(__start___param) = .;                    \
353                 *(__param)                                              \
354                 VMLINUX_SYMBOL(__stop___param) = .;                     \
355                 . = ALIGN((align));                                     \
356                 VMLINUX_SYMBOL(__end_rodata) = .;                       \
357         }                                                               \
358         . = ALIGN((align));
这儿给个比方net/ipv4/netfilter/nf_nat_irc.c
static int warn_set(const char *val, struct kernel_param *kp)
{
        printk(KERN_INFO KBUILD_MODNAME
               “: kernel >= 2.6.10 only uses 'ports' for conntrack modules\n”);
        return 0;
}
module_param_call(ports, warn_set, NULL, NULL, 0);
处理module_param_call之外,还有core_param也可以界说内核参数,不过内核参数不可以模块化,也不可以运用前缀命名(如“printk.”)。

接下来咱们来看struct kernel_param这个结构:
include/linux/moduleparam.h
struct kernel_param;

/* Flag bits for kernel_param.flags */
#define KPARAM_ISBOOL        2

struct kernel_param {
    const char *name;
    const struct kernel_param_ops *ops;
    u16 perm;
    u16 flags;
    union { 传递给上面kernel_param_ops中两个函数的参数
        void *arg;
        const struct kparam_string *str;
        const struct kparam_array *arr;
    };
};
其间,联合体内界说的三个成员,第一个其实是字符类型的封装。
/* Special one for strings we want to copy into */
struct kparam_string {
    unsigned int maxlen;
    char *string;
};
第二个是数组类型的封装。
/* Special one for arrays */
struct kparam_array
{
    unsigned int max;
    unsigned int *num;
    const struct kernel_param_ops *ops;
    unsigned int elemsize;
    void *elem;
};
还剩余的常字符串类型成员name为内核参数的称号,而perm为权限????。
一起数组类型的封装kernel_param_ops中还界说了两个办法,以函数指针存在。别离是设置和读取操作。
struct kernel_param_ops {
    /* Returns 0, or -errno.  arg is in kp->arg. */
    int (*set)(const char *val, const struct kernel_param *kp);设置参数的函数
    /* Returns length written or -errno.  Buffer is 4k (ie. be short!) */
    int (*get)(char *buffer, const struct kernel_param *kp);读取参数的函数 
    /* Optional function to free kp->arg when module unloaded. */
    void (*free)(void *arg);
};

现在咱们再回到parse_one函数中,咱们前面有部分没有剖析,现在再来看一下
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;
     假如是early_param则直接越过这步,而非early的,则要经过这步来设置一些内置模块的参数。
    /* Find parameter */
    for (i = 0; i < num_params; i++) {
        if (parameq(param, params[i].name)) { //假如是内置模块的参数
            /* 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(¶m_lock);
            err = params[i].ops->set(val, ¶ms[i]); //调用参数设置函数来设置对应的内置模块的参数。
            mutex_unlock(¶m_lock);
            return err;
        }
    }

    if (handle_unknown) {
        DEBUGP(“Unknown argument: calling %p\n”, handle_unknown);
        return handle_unknown(param, val);
    }

    DEBUGP(“Unknown argument `%s'\n”, param);
    return -ENOENT;
}

parse_one它调用unknown_bootoption函数处理__setup界说的参数,unknown_bootoption函数在init/main.c中界说如下:
static int __init unknown_bootoption(char *param, char *val)
{
    /* Change NUL term back to “=”, to make “param” the whole string. */
    if (val) {
        /* param=val or param=”val”? */
        if (val == param+strlen(param)+1)
            val[-1] = '=';
        else if (val == param+strlen(param)+2) {
            val[-2] = '=';
            memmove(val-1, val, strlen(val)+1);
            val–;
        } else
            BUG();
    }

    /* Handle obsolete-style parameters */
    if (obsolete_checksetup(param))
        return 0;

    /* Unused module parameter. */
    if (strchr(param, '.') && (!val || strchr(param, '.') < val))
        return 0;

    if (panic_later)
        return 0;

    if (val) {
        /* Environment option */
        unsigned int i;
        for (i = 0; envp_init[i]; i++) {
            if (i == MAX_INIT_ENVS) {
                panic_later = “Too many boot env vars at `%s'”;
                panic_param = param;
            }
            if (!strncmp(param, envp_init[i], val – param))
                break;
        }
        envp_init[i] = param;
    } else {
        /* Command line option */
        unsigned int i;
        for (i = 0; argv_init[i]; i++) {
            if (i == MAX_INIT_ARGS) {
                panic_later = “Too many boot init vars at `%s'”;
                panic_param = param;
            }
        }
        argv_init[i] = param;
    }
    return 0;
}
在这个函数中它调用了obsolete_checksetup函数,该函数也界说在init/main.c中
static int __init obsolete_checksetup(char *line)
{
    const struct obs_kernel_param *p;
    int had_early_param = 0;

    p = __setup_start;
    do {
        int n = strlen(p->str);
        if (!strncmp(line, p->str, n)) {
            if (p->early) {
                /* Already done in parse_early_param?
                 * (Needs exact match on param part).
                 * Keep iterating, as we can have early
                 * params and __setups of same names 8( */
                if (line[n] == '\0' || line[n] == '=')
                    had_early_param = 1;
            } else if (!p->setup_func) {
                printk(KERN_WARNING “Parameter %s is obsolete,”
                       ” ignored\n”, p->str);
                return 1;
            } else if (p->setup_func(line + n)) //调用支撑函数
                return 1;
        }
        p++;
    } while (p < __setup_end);     return had_early_param;
}

在这儿parse_early_param()首要的作用是处理内核指令行(boot_command_line)的内核参数。也便是处理在内核指令行中有界说的前期参数值(early=1),特别的还包含内核参数console和earlycon。都和输出流有关,内核发动时的打印信息就要求该设备的正确装备。
总结一下:
内核分三类参数的传递与设置
1、内置模块的参数设置
2、高优先级指令行参数设置
3、一般指令行参数设置
三种参娄的设置都由parse_args函数来完结,对第一种内置模块的参数设置在parse_args函数中调用parse_one函数遍历__start___param和__stop___param,假如查找到对应的key,就调用相关参数设置函数处理。
对第二种高优先级指令行参数设置经过parse_args函数传递调用函数do_early_param到parse_one函数中由do_early_param完结遍历obs_kernel_param数组(__setup_start,__setup_end),首要看是否设置early,假如有设置并查找到对应的key,就调用相关early_param函数处理。
对第三种一般指令行参数设置经过parse_args函数传递调用函数unknown_bootoption到parse_one函数中由unknown_bootoption完结遍历 obs_kernel_param数组(__setup_start,__setup_end),首要看是否设置early,假如查找到对应的key,就调用相关__setup函数处理。

对2.6.36曾经版别没用 parse_early_param而是用parse_cmdline函数,咱们来看看这个函数

static void __init parse_cmdline(char **cmdline_p, char *from)

{

char c = ' ', *to = command_line;

int len = 0;

for (;;) {

if (c == ' ') {

//寻觅c=空格 的条件,空格标明一个新的指令行选项,假定:mem=xxx noinitrd root=yyy init=/linuxrc console=ttySAC0,这个扫描是一个一个的扫描指令行里的参数的。

extern struct early_params __early_begin, __early_end; //这些变量在lds中

struct early_params *p;

for (p = &__early_begin; p < &__early_end; p++) { //扫描这个区间的一切early_params结构。

int len = strlen(p->arg); //丈量这个字符串的长度。比方”mem=”长度是4

if (memcmp(from, p->arg, len) == 0) { //这儿也pass,这儿不pass就意味着不能解析。

if (to != command_line) //避免得到两个空格

to -= 1;

from += len;  //越过这个选项,得到详细数据,现在from指向“xxx noinitrd…“

p->fn(&from); //调用这个函数处理这个xxx

while (*from != ' ' && *from != '\0') //越过xxx部分,由于这是mem=xxx现已处理完了,可以扔掉了。

from++;

break; // 停止这次处理,针对mem=xxx的处理,现在from指向“ noinitrd …“,留意最前面的空格。

}

}

}

c = *from++; //这次c又得到的是空格。第2次,取到的是noinitrd的n

if (!c)  //假如到了uboot指令行参数的完毕,或许 指令行参数太长,都会完毕扫描

break;

if (COMMAND_LINE_SIZE <= ++len)

break;

*to++ = c; //保存空格,第2此保存了n,顺次类推。

}

*to = '\0';

*cmdline_p = command_line; //给cmdline_p赋值,这个指针是start_kernel里的。

}

struct early_params {
    const char *arg;    //字符串指针
    void (*fn)(char **p); //私有的函数
};

在System.map中

c0027cdc T __early_begin

c0027cdc t __early_early_mem

c0027cdc T __setup_end

c0027ce4 t __early_early_initrd

c0027cec t __early_early_vmalloc

c0027cf4 t __early_early_ecc

c0027cfc t __early_early_nowrite

c0027d04 t __early_early_nocache

c0027d0c t __early_early_cachepolicy

c0027d14 t __early_uart_parent_setup

c0027d1c t __early_jtag_wfi_setup

c0027d24 t __early_system_rev_setup

c0027d2c T __early_end
比方arch/arm/kernel/setup.c中

static void __init early_mem(char **p)

{

static int usermem __initdata = 0;

unsigned long size, start;

/*

* If the user specifies memory size, we

* blow away any automatically generated

* size.

*/

if (usermem == 0) {

usermem = 1;

meminfo.nr_banks = 0;

}

start = PHYS_OFFSET;

size  = memparse(*p, p);

if (**p == '@')

start = memparse(*p + 1, p);

arm_add_memory(start, size);

}

__early_param(“mem=”, early_mem);
可以发现可以在这儿处理的指令行参数有mem initrd ecc cachepolicy nowrite nocache这六个。
parse_cmdline做了三件事,首要它解析了from所指向的完好的内核参数中关于内存的部分,其次它将没有解析的部分复制到command_line中,终究它将start_kernel()传进来的内核参数指针指向command_line。
内核参数中的mem=xxxM@ xxx将会被parse_cmdline解析,并依据成果设置meminfo,而其余部分则被复制到command_line中便是说,能解析的都解析了,不能解析的留下来,等着今后解析,保存在command_line中,可以发现uboot的指令行参数具有最高的优先级,假如指定mem=xxxM@yyy的话,将覆盖掉符号列表的mem32装备,假如屡次运用mem= mem=的话,将得到多个内存bank,经过代码来看是这样,没有验证过。

终究。经过上面的剖析,咱们可以自界说经过指令行传入一个参数设置,这儿以uboot向内核传递MAC地址为例阐明增加进程
 咱们运用的体系中的CS8900a没有外接eeprom,所以在默许的状况,Linux下的CS8900a的驱动运用的是一个伪MAC地址。在单一的体系中,这是没有问题的,可是当咱们在同一个子网中运用或测验多个设备是,就会发生抵触了。所以咱们需求可以便利的改动网卡的MAC地址,而不是将MAC地址硬编码进内核中,每次修正都得从头编译内核。

一、增加内核参数的处理函数
       向Linux驱动传递参数的办法有两种,一为在体系发动的时分由bootloader传入,还有一种是将驱动编译成模块,将参数作为模块加载的参数传入。
       这儿运用的是由bootloader传入的办法。内核经过setup接口承受Bootloader传入的参数。办法如下:

static int __init param_mac_setup(char *str)
{
……
}
__setup(“mac=”, param_mac_setup);
这样,当在Bootloader中指定“mac=00:2E:79:38:6D:4E”,体系在加载这个模块的时分,就会履行相应的param_mac_setup()函数,而传入给它的参数便是等号后边的物理地址 “00:2E:79:38:6D:4E”。这样,该函数就可以对它进行相应的处理。
二、将MAC地址参数传入指令行参数
       为了传入指令行参数,uboot所作的是:
              char *commandline = getenv (“bootargs”);
              setup_commandline_tag (bd, commandline);

       现在想要把MAC地址也参加到指令行参数中,只需求修正装备文件include/configs/smdk2410.h中的CONFIG_BOOTARGS:
#define CONFIG_BOOTARGS       “root=ramfs devfs=mount console=ttySA0,9600”
在后边增加mac参数如下:
#define CONFIG_BOOTARGS       “root=ramfs devfs=mount console=ttySA0,9600 mac=00:2E:79:38:6D:4E”
这样就不需求每次在指令行后边手艺追加MAC地址参数了 

 

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/ziliao/zhudong/90633.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部