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地址参数了