U-boot會給Linux Kernel傳遞很多參數(shù),如:串口,RAM,videofb、MAC地址等。而Linux kernel也會讀取和處理這些參數(shù)。兩者之間通過struct tag來傳遞參數(shù)。U-boot把要傳遞給kernel的東西保存在struct tag數(shù)據(jù)結(jié)構(gòu)中,啟動kernel時,把這個結(jié)構(gòu)體的物理地址傳給kernel;Linux kernel通過這個地址,用parse_tags分析出傳遞過來的參數(shù)。
本文主要以U-boot(1.1.6)傳遞RAM和Linux kernel讀取RAM參數(shù)為例進(jìn)行說明。
1、u-boot給kernel傳RAM參數(shù)
在介紹該之前,我們需要看一看幾個數(shù)據(jù)結(jié)構(gòu),這些是u-boot中幾個重要的數(shù)據(jù)結(jié)構(gòu):
(1)gd_t結(jié)構(gòu)體
U-Boot使用了一個結(jié)構(gòu)體gd_t來存儲全局?jǐn)?shù)據(jù)區(qū)的數(shù)據(jù),這個結(jié)構(gòu)體在U-Boot的include/asm-arm/global_data.h中定義如下:
typedef??? struct??? global_data {
??? bd_t??? ??? *bd;?? //與板子相關(guān)的結(jié)構(gòu),見下面
??? 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來記錄全局?jǐn)?shù)據(jù)區(qū)的地址:
#define DECLARE_GLOBAL_DATA_PTR???? register volatile gd_t *gd asm ("r8")
??? DECLARE_GLOBAL_DATA_PTR定義一個gd_t全局?jǐn)?shù)據(jù)結(jié)構(gòu)的指針,這個指針存放在指定的寄存器r8中。這個聲明也避免編譯器把r8分配給其它的變量。任何想要訪問全局?jǐn)?shù)據(jù)區(qū)的代碼,只要代碼開頭加入“DECLARE_GLOBAL_DATA_PTR”一行代碼,然后就可以使用gd指針來訪問全局?jǐn)?shù)據(jù)區(qū)了。
??? 根據(jù)U-Boot內(nèi)存使用圖中可以計算gd的值:
gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)
2)bd_t 保存與板子相關(guān)的配置參數(shù)
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; /*環(huán)境變量開始地址 */
??? ulong? bi_arch_number;??? /* unique id for this board開發(fā)板的機(jī)器碼 */
??? ulong? bi_boot_params;??? /* where this board expects params 內(nèi)核參數(shù)的開始地址*/
??? 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啟動內(nèi)核時要給內(nèi)核傳遞參數(shù),這時就要使用gd_t,bd_t結(jié)構(gòu)體中的信息來設(shè)置標(biāo)記列表。
3)啟動參數(shù)的數(shù)據(jù)結(jié)構(gòu)
向內(nèi)核傳遞啟動參數(shù)可保存在兩種數(shù)據(jù)結(jié)構(gòu)中,param_struct和tag,前者是2.4內(nèi)核用的,后者是2.6以后的內(nèi)核更期望用的但是,到目前為止,2.6的內(nèi)核也可以兼容前一種結(jié)構(gòu),內(nèi)核參數(shù)通過一個靜態(tài)的param_struct或tag鏈表在啟動的時候傳遞到內(nèi)核。需要注意的是,這兩個數(shù)據(jù)結(jié)構(gòu)在uboot中和linux中分別有定義,這個定義必須一致才能正常傳遞參數(shù)如果實(shí)際使用中不一致的話就不能正常傳遞,可以自行修改 兩種數(shù)據(jù)結(jié)構(gòu)具體定義如下(這里說的是內(nèi)核源碼中的定義):?
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只需要設(shè)置cmmandline,u1.s.page_size,u1.s.nr_pages三個域,下面是使用param_struct例子通過param_struct讓uboot中的go命令可以傳遞參數(shù)
分析:go的代碼在common/cmd_boot.c中,里面并沒有拷貝啟動參數(shù)的代碼,轉(zhuǎn)向內(nèi)核的時候也沒有傳送
啟動參數(shù)所在的地址,因此添加如下代碼用于拷貝參數(shù),可以看到,對于param_struct只需要設(shè)置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);
然后還要向內(nèi)核傳遞參數(shù)地址,將下面一行代碼修改:
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);//修改之后的代碼?
關(guān)于param_struct不是這里重點(diǎn),下面主要分析tag
??? 對于tag來說,在實(shí)際使用中是一個struct tag組成的列表,在tag->tag_header中,一項(xiàng)是u32 tag(重名,注意類型)其值用宏ATAG_CORE,ATAG_MEM,ATAG_CMDLINE,ATAG_NONE等等來表示,此時下面union就會使用與之相關(guān)的數(shù)據(jù)結(jié)構(gòu)同時,規(guī)定tag列表中第一項(xiàng)必須是ATAG_CORE,最后一項(xiàng)必須是ATAG_NONE,比如在linux代碼中,找到啟動參數(shù)之后首先看tag列表中的第一項(xiàng)的tag->hdr.tag是否為ATAG_CORE,如果不是,就會認(rèn)為啟動參數(shù)不是tag結(jié)構(gòu)而是param_struct結(jié)構(gòu),然后調(diào)用函數(shù)來轉(zhuǎn)換.在tag->tag_header中,另一項(xiàng)是u32 size,表示tag的大小,tag組成列表的方式就是指針+size
tag數(shù)據(jù)結(jié)構(gòu)在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的類型和大小,之所以要標(biāo)識tag_xxx的類型是因?yàn)椴煌膖ag需要不同的處理函數(shù)
內(nèi)核tag_header的結(jié)構(gòu)(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的結(jié)構(gòu)大小,tag為表示tag類型的常量。這個靜態(tài)的鏈表必須以tag_header.tag = ATAG_CORE開始,并以tag_header.tag = ATAG_NONE結(jié)束。由于不同的tag所使用的格式可能不盡相同,所以內(nèi)核又定義了一個結(jié)構(gòu)tagtable來把tag和相應(yīng)的操作函數(shù)關(guān)聯(lián)起來
(arch/arm/include/asm/setup.h)
struct tagtable {
??????? __u32 tag;
??????? int (*parse)(const struct tag *);
};
其中tag為標(biāo)識入ATAG_NONE,ATAG_CORE等。parse為處理函數(shù)。Linux內(nèi)核將tagtable也組成了一個靜態(tài)的鏈表放入.taglist.init節(jié)中,這是通過__tagtable宏來實(shí)現(xiàn)的?
#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);
根據(jù)前面相關(guān)宏定義,__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中。
??? 現(xiàn)在再來看一下U-boot給Linux Kernel傳遞啟動參數(shù)的傳遞過程
??? 啟動參數(shù)是包裝在struct tag數(shù)據(jù)結(jié)構(gòu)里的,在linux kernel啟動的時候,bootloader把這個數(shù)據(jù)結(jié)構(gòu)拷貝到某個地址,在改動PC跳向內(nèi)核接口的同時,通過通用寄存器R2來傳遞這個地址的值,在bootm執(zhí)行的流程中,會調(diào)用do_bootm_linux()在執(zhí)行Linux內(nèi)核,內(nèi)核的起始地址如下:
void (*theKernel)(int zero, int arch, uint params);
image_header_t *hdr = &header;
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
header是uImage的頭部,通過頭部,得到內(nèi)核映像起始的執(zhí)行地址,標(biāo)識為theKernel。從中也可以看到,內(nèi)核接受三個參數(shù),第一個為0,第二個為系統(tǒng)的ID號,第三個是傳入內(nèi)核的參數(shù)。
在do_bootm_linux()的最后,會跳到內(nèi)核去執(zhí)行:
?????? theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
thekernel其實(shí)不是個函數(shù),而是指向內(nèi)核入口地址的指針,把它強(qiáng)行轉(zhuǎn)化為帶三個參數(shù)的函數(shù)指針,會把三個
參數(shù)保存到通用寄存器中,實(shí)現(xiàn)了向kernel傳遞信息的功能,在這個例子里,把R0賦值為0,R1賦值為機(jī)器號bd->bi_arch_number, R2賦值為啟動參數(shù)數(shù)據(jù)結(jié)構(gòu)的首地址bd->bi_boot_params。最后兩個參數(shù)在board/smdk2410/smdk2410.c的board_init()中被初始化。
??? 因此,要向內(nèi)核傳遞參數(shù)很簡單,只要把啟動參數(shù)封裝在linux預(yù)定好的數(shù)據(jù)結(jié)構(gòu)里,拷貝到某個地址(一般
約定俗成是內(nèi)存首地址+100dex,后面會見到)? p { margin-bottom: 0.21cm; }
U-boot向內(nèi)核傳遞參數(shù)的具體實(shí)現(xiàn)過程
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)主函數(shù)中計算全局?jǐn)?shù)據(jù)結(jié)構(gòu)的地址并賦值給指針gd,并對struct tag數(shù)據(jù)結(jié)構(gòu)里參數(shù)賦值
下面是start_armboot函數(shù)部分代碼
?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行計算全局?jǐn)?shù)據(jù)結(jié)構(gòu)的地址并賦值給指針gd,具體計算請參看前面的說明,253行計算出結(jié)構(gòu)體中bd指針的地址,然后在第258行逐個調(diào)用init_sequence初始化函數(shù)列表數(shù)組中的初始化函數(shù)對平臺硬件進(jìn)行初始化,這里只分析后面用到的硬件初始化函數(shù)board_init、dram_init。這兩個函數(shù)都在board/smdk2410/smdk2410.c中實(shí)現(xiàn)
首先看board_init函數(shù),以下是部分實(shí)現(xiàn)
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;//一般約定俗成是內(nèi)存首地址+100dex
可以看到,theKernel最后兩個參數(shù)在這里的第106和109行被初始化,uboot傳給內(nèi)核的參數(shù)表存被放在內(nèi)存中起始偏移0x100的位置,這里只是指定了“指針”的位置,但還沒初始化其中的值,后面?zhèn)鬟f到內(nèi)核的參數(shù)列表的構(gòu)建才初始化其中的值,這是在 do_bootm_linux()中跳到內(nèi)核前去完成的。值得注意的是, 內(nèi)核的默認(rèn)運(yùn)行地址的0x30008000,前面就是留給參數(shù)用的。所以一般不要將內(nèi)核下載到該地址之前,以免沖掉了傳給內(nèi)核的參數(shù)。這里在55行同樣要對gd指針引用聲名,MACH_TYPE_SMDK2410在include/asm-arm/mach-types.h中定義,值為192
而dram_init函數(shù)是對struct tag數(shù)據(jù)結(jié)構(gòu)里內(nèi)存參數(shù)賦值,后面會用到。
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、傳遞到內(nèi)核的參數(shù)列表的構(gòu)建
./common/cmd_bootm.c文件中,bootm命令對應(yīng)的do_bootm函數(shù),當(dāng)分析uImage中信息發(fā)現(xiàn)OS是Linux時,調(diào)用./lib_arm/armlinux.c文件中的do_bootm_linux函數(shù)來啟動Linux kernel。在do_bootm_linux函數(shù)中(lib_arm/armlinux.c) ,以下是部分相關(guān)源碼:
#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);??? /* 設(shè)置ATAG_CORE標(biāo)志 */
#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);? /* 設(shè)置內(nèi)存標(biāo)記 */
#endif
#ifdef CONFIG_CMDLINE_TAG
??? setup_commandline_tag (bd, commandline);? /* 設(shè)置命令行標(biāo)記 */
#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);? /* 設(shè)置ATAG_NONE標(biāo)志 */??
#endif
在uboot中,進(jìn)行設(shè)置傳遞到內(nèi)核的參數(shù)列表tag的函數(shù)都在lib_arm/armlinux.c中,在這些函數(shù)前面是有ifdef的因此,如果你的bootm命令不能傳遞內(nèi)核參數(shù),就應(yīng)該是在你的board的config文件里沒有對上述的宏進(jìn)行設(shè)置,定義一下即可?
這里對于setup_start_tag、setup_memory_tags和setup_end_tag函數(shù)說明如下。它們都在lib_arm/armlinux.c文件中定義,如下
static void setup_start_tag (bd_t *bd)
{
??? params = (struct tag *) bd->bi_boot_params; /* 內(nèi)核的參數(shù)的開始地址 */
??? 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);
}
標(biāo)記列表必須以ATAG_CORE開始,setup_start_tag函數(shù)在內(nèi)核的參數(shù)的開始地址設(shè)置了一個ATAG_CORE標(biāo)記。
#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd)? //初始化內(nèi)存相關(guān)tag
{
??? int i;
??? /*設(shè)置一個內(nèi)存標(biāo)記 */
??? 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函數(shù)設(shè)置了一個ATAG_MEM標(biāo)記,該標(biāo)記包含內(nèi)存起始地址,內(nèi)存大小這兩個參數(shù)。RAM相關(guān)參數(shù)在前面的setup_memory_tags函數(shù)中已經(jīng)初始化.
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;
}
這個靜態(tài)的鏈表必須以標(biāo)記ATAG_CORE開始,并以標(biāo)記ATAG_NONE結(jié)束。setup_end_tag函數(shù)設(shè)置了一個ATAG_NONE標(biāo)記,表示標(biāo)記列表的結(jié)束。
d、最后do_bootm_linux函數(shù)調(diào)用theKernel (0, machid, bd->bi_boot_params)去啟動內(nèi)核并傳遞參數(shù),可以看見r0是machid,r2是bi_boot_params參數(shù)的地址。
2、Kernel讀取U-boot傳遞的相關(guān)參數(shù)
對于Linux Kernel,ARM平臺啟動時,先執(zhí)行arch/arm/kernel/head.S,此時r2寄存器的值為參數(shù)的地址,此文件會調(diào)用arch/arm/kernel/head-common.S中的函數(shù),并最后調(diào)用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]:因?yàn)橥ㄓ眉拇嫫? (r2) 必須是 kernel parameter list 的物理地址(parameter list 是由boot loader傳遞給kernel,用來描述設(shè)備信息屬性的列表),所以將uboot傳遞進(jìn)來的tags物理地址數(shù)值存入__atags_pointer指針( [r6] )中,__atags_pointer在第28行定義并通過42、56行將其加載到r6中,在arch/arm/kernel/setup.c中的setup_arch中將引用__atags_pointer為指向參數(shù)的地址.
init/main.c中的start_kernel函數(shù)中會調(diào)用setup_arch函數(shù)來處理各種平臺相關(guān)的動作
start_kernel()
{
……
setup_arch(&command_line);
……
}
包括了u-boot傳遞過來參數(shù)的分析和保存,對tag的處理代碼也在setup_arch里面。以下是一部分的關(guān)鍵代碼(setup_arch函數(shù)在arch/arm/kernel/setup.c文件中實(shí)現(xiàn)):?
767 void __init setup_arch(char **cmdline_p)
768 {
769???????? struct tag *tags = (struct tag *)&init_tags;//tags指向默認(rèn)的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包含啟動參數(shù)在內(nèi)存中的地址
.....................................................................................................
782???????? if (__atags_pointer) //檢查BootLoader是否傳入?yún)?shù)
783???????????????? tags = phys_to_virt(__atags_pointer);//bootloader有傳遞啟動參數(shù)到內(nèi)核
784???????? else if (mdesc->boot_params)//如果BootLoader沒有傳入?yún)?shù)則使用內(nèi)核machine descriptor中設(shè)置的啟動參數(shù)地址(arch/arm/mach-s3c2410/mach-smdk2410.c),這里設(shè)置的地址與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)//如果是舊的啟動參數(shù)結(jié)構(gòu),將其轉(zhuǎn)成新的tag鏈表的形式,新的tag鏈表的形式內(nèi)核參數(shù)列表第一項(xiàng)必須是ATAG_CORE類型,如果不是,則需要轉(zhuǎn)換成新的內(nèi)核參數(shù)類型。
793???????????????? convert_to_tag_list(tags);//此函數(shù)完成新舊參數(shù)結(jié)構(gòu)轉(zhuǎn)換,將參數(shù)結(jié)構(gòu)轉(zhuǎn)換為tag list結(jié)構(gòu)
794 #endif
795???????? if (tags->hdr.tag != ATAG_CORE)//轉(zhuǎn)換失敗,使用內(nèi)置的啟動參數(shù)
796???????????????? tags = (struct tag *)&init_tags;//則選用默認(rèn)的內(nèi)核參數(shù),init_tags文件中有定義。
797?
798???????? if (mdesc->fixup)? //用內(nèi)核參數(shù)列表填充meminfo,fixup函數(shù)出現(xiàn)在注冊machine_desc中,即MACHINE_START、MACHINE_END定義中,這個函數(shù),有些板子有,但在2410中沒有定義這個函數(shù)。
799???????????????? mdesc->fixup(mdesc, tags, &from, &meminfo);
800?
801???????? if (tags->hdr.tag == ATAG_CORE) {?
802???????????????? if (meminfo.nr_banks != 0) //說明內(nèi)存被初始化過
803???????????????????????? squash_mem_tags(tags);//如果在meminfo中有配置內(nèi)存tag則跳過對內(nèi)存tag的處理,如果是tag list,那么如果系統(tǒng)已經(jīng)創(chuàng)建了默認(rèn)的meminfo.nr_banks,清除tags中關(guān)于MEM的參數(shù),以免再次被初始化
804???????????????? save_atags(tags);
805???????????????? parse_tags(tags);//做出一些針對各個tags的處理
806???????? }
.....................................................................................................
851 }
第769行tags指向默認(rèn)的tag鏈表,內(nèi)核中定義了一些默認(rèn)的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 };
上述結(jié)構(gòu)中一個tag_header和tag_xxx形成了tag的完整描述,tag_size返回tag_head和tag_xxx的總大小,在tag_size中我們要注意的是u32*指針加1地址值實(shí)際上地址加了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實(shí)際上計算的是(tag_head+tag_xxx)/4。經(jīng)過進(jìn)一步的分析還發(fā)現(xiàn)每個tag在內(nèi)存中的大小并不是相同的,這一點(diǎn)可以從tag_next看出,tag_next只是將指針移到了下一個tag的tag_header處,這種內(nèi)存布局更加緊湊。
注:2.6.18內(nèi)核smdk2410的meminfo沒有設(shè)置nr_banks,所以必須在內(nèi)核的啟動參數(shù)里面?zhèn)鬟fmem=”memory size”@”memory base address”,否則系統(tǒng)識別內(nèi)存錯誤,這點(diǎn)從系統(tǒng)的啟動信息就可以看出來,而且在加載initrd的時候也會遇到內(nèi)存溢出的錯誤
if (__atags_pointer)?????????????????????????????????????????????????????????????????????????
??????? tags = phys_to_virt(__atags_pointer);
指向各種tag起始位置的指針,定義如下:
unsigned int __atags_pointer __initdata;
此指針指向__initdata段,各種tag的信息保存在這個段中。
mdesc->fixup(mdesc, tags, &from, &meminfo):fixup函數(shù)是板級相關(guān)的,通常就是一些ram起址大小bank之類的設(shè)定函數(shù),如果執(zhí)行過了,nrbank就不為0了,那么繼續(xù)執(zhí)行后面的語句時:
if (tags->hdr.tag == ATAG_CORE) {
???????????? if (meminfo.nr_banks != 0)
???????????????????? squash_mem_tags(tags);
???????????? parse_tags(tags);
}
就會調(diào)用squash_mem_tags把你u-boot傳入的值給干掉,使parse_tags函數(shù)調(diào)用時不會處理ATAG_MEM。
然后執(zhí)行到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?調(diào)用parse_tag 函數(shù)
??? ??? ??? printk(KERN_WARNING
??? ??? ??? ??? "Ignoring unrecognised tag 0x%08x\n",
??? ??? ??? ??? t->hdr.tag);
}
parse_tags遍歷tag鏈表調(diào)用parse_tag對tag進(jìn)行處理。parse_tags在tabtable中尋找tag的處理函數(shù)(通過tag_header結(jié)構(gòu)中的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列表,并調(diào)用處理函數(shù),
??? ??? if (tag->hdr.tag == t->tag) {
??? ??? ??? t->parse(tag); //調(diào)用處理函數(shù)
??? ??? ??? break;
??? ??? }
??? return t < &__tagtable_end;
}
處理各種tags,其中包括了RAM參數(shù)的處理。這個函數(shù)處理如下tags:
561 __tagtable(ATAG_MEM, parse_tag_mem32);
554 __tagtable(ATAG_CORE, parse_tag_core);
555?
對于處理RAM的tag,調(diào)用了parse_tag_mem32函數(shù):
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函數(shù)調(diào)用arm_add_memory函數(shù)把RAM的start和size等參數(shù)保存到了meminfo結(jié)構(gòu)的meminfo結(jié)構(gòu)體中。對照uboot部分內(nèi)存初始化函數(shù),我們知道uboot傳遞過來的tag->u.mem.start, tag->u.mem.size分別為0x30000000,0x4000000,現(xiàn)在再來分析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;
}
經(jīng)過這樣的處理,setup.c文件中的meminfo可就不再是
struct meminfo meminfo? = { 0, };
而是
struct meminfo meminfo? = { 1,{0x30000000,0x4000000,0},{}, };
表示當(dāng)前有一個內(nèi)存區(qū)域,物理地址是從0x30000000開始,大小是64M
最后,在setup_arch中執(zhí)行下面語句
paging_init(&meminfo, mdesc)
再來看看另一個參數(shù)處理函數(shù)
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 原來的內(nèi)容是我們配置文件中確定的,但是現(xiàn)在,他被tag->u.cmdline.cmdline覆蓋了??梢?,從uboot傳遞過來的命令行參數(shù)的優(yōu)先級要高于配置文件的默認(rèn)命令行.
我們接著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:從這兒之后的內(nèi)存可以動態(tài)的分配了。填充 init_mm 的成員,這些數(shù)值在lds里面。分別是代碼段,數(shù)據(jù)段和bss段。
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
上面的代碼先把uboot傳遞過來的命令行參數(shù)保存起來,以備后用。
?
linux內(nèi)核commandline參數(shù)解析過程
前面詳細(xì)分析了u-boot與linux內(nèi)核間的tag參數(shù)傳遞及解析過程,但對命令行參數(shù)沒做詳細(xì)的分析,在setup_arch函數(shù)的第817行我們看到把uboot傳遞過來的命令行參數(shù)保存起來,以備后用。這里的后用就是linux內(nèi)核commandline參數(shù)解析,也就是給第820行代碼備用的,對2.6.36以前版本沒用 parse_early_param而是用parse_cmdline函數(shù),在分析這兩個函數(shù)前,我們先來看一下從u-boot到內(nèi)核命令行參數(shù)設(shè)置及傳遞過程,以便更好的理解后面的分析。
在u-boot的include/configs/smdk2410.h配置文件中我們可以找到CONFIG_BOOTARGS配置項(xiàng),在這里我們可以設(shè)置要傳遞的到內(nèi)核的命令行參數(shù),如:
*#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被轉(zhuǎn)化為
"bootargs=""root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
u-boot引導(dǎo)內(nèi)核為調(diào)用u-boot的lib_arm/armlinux.c文件的do_bootm_linux函數(shù)
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
..........................
}
在這里它首先調(diào)用getenv ("bootargs")函數(shù)獲得命令行參數(shù)并讓commandline指向它,然后調(diào)用setup_commandline_tag函數(shù)將命令行參數(shù)放到tag參數(shù)例表,
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);
}
關(guān)于tag參數(shù)例表前面己有詳細(xì)分析,這里我只對u-boot取命令行環(huán)境參數(shù)函數(shù)getenv進(jìn)行分析,它定義在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);
}
這里重點(diǎn)理解env_get_char函數(shù),它定義在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參數(shù)在start_armboot函數(shù)中的初始化函數(shù)例表中的env_init函數(shù)中設(shè)置,如果配置參數(shù)保存在flash中,gd->env_valid被設(shè)置為1,這里就通過env_get_char_spec函數(shù)從flash中取參數(shù),否則gd->env_valid設(shè)置為0,使用默認(rèn)環(huán)境變量參數(shù),默認(rèn)環(huán)境變量參數(shù)定義在u-boot的common/env_common.c文件uchar default_environment[] ,也就是include/configs/smdk2410.h配置文件中配置的參數(shù)。這里針對不同的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函數(shù),這里以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;? 使用默認(rèn)環(huán)境變量參數(shù),gd->env_valid設(shè)置為0
??? return (0);
}
而在include/configs/smdk2410.h配置文件中關(guān)于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 */
通過上面幾個文件相關(guān)定義,我們很容易知道env_get_char_init函數(shù)功能就是如果保存了參數(shù)到flsah中就調(diào)用env_get_char_spec從指定的flash地址中讀取參數(shù)字符,否則就從默認(rèn)環(huán)境變量參數(shù)中讀取參數(shù)字符。
理解完env_get_char_init函數(shù)后,再來看envmatch函數(shù),定義在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);
}
這個函數(shù)功能是查找符號變量,如果找到則返回等號后面的字符串指針,即為變量的值,環(huán)境變量表是一個字符串?dāng)?shù)組,而其中的變量之間通過’\0’符號隔開,即是當(dāng)遇到該符號時,則表示一個變量結(jié)束而另一個變量開始。
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]);
?}
}
這個函數(shù)功能是返回找到的環(huán)境變量字符串?dāng)?shù)組地址。
此至,命令行參數(shù)在u-boot中設(shè)置及傳遞過程分析完了,下面我們再來看linux內(nèi)核commandline參數(shù)解析過程,內(nèi)核在start_kernel函數(shù)調(diào)用start_arch獲取tag參數(shù)地址后,再調(diào)用parse_tags完成了tag參數(shù)解釋,之后就是linux內(nèi)核commandline參數(shù)解析,也就調(diào)用parse_early_param或parse_cmdline(對2.6.36以前版本)函數(shù)來完成對命令行參數(shù)的解釋。
linux內(nèi)核commandline參數(shù)解析過程
前面使用
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
??? *cmdline_p = cmd_line;
將命令行參數(shù)保存到了cmd_line中
parse_early_param();
現(xiàn)在再來看看start_arch函數(shù)中第820行的parse_early_param函數(shù)
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;
}
在上面我們可以看到最終調(diào)用的是 parse_args("early options", cmdline, NULL, 0, do_early_param);parse_args在kernel/params.c中定義,注意它與前面的parse_tags(const struct tag *t)區(qū)別。
/* 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) 跳過前面的空格函數(shù)
{
??? 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"參數(shù)表,以第一個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字符串結(jié)束標(biāo)志
??? char *next;
?? // [args] 指向內(nèi)容"root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
??? if (*args == '"') {
??? ??? args++;? //[args]指向內(nèi)容root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
??? ??? in_quote = 1;? //in_quote字符串開始標(biāo)志
??? ??? quoted = 1;
??? }
??? for (i = 0; args[i]; i++) { //循環(huán)完后,
??? ??? if (isspace(args[i]) && !in_quote)? //空格或沒有字符了,退出。
??? ??? ??? break;
??? ??? if (equals == 0) { //查找到第一個=號位置
??? ??? ??? if (args[i] == '=')
??? ??? ??? ??? equals = i;
??? ??? }
??? ??? if (args[i] == '"')? //最后一個結(jié)束字符'"'嗎?是的話設(shè)置in_quote = !in_quote
??? ??? ??? in_quote = !in_quote;
??? }
??? *param = args;??
?? //[args] 指向內(nèi)容root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
??? if (!equals)
??? ??? *val = NULL;
??? else {
??? ??? args[equals] = '\0';
????? //[args]指向內(nèi)容root /dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
??? ??? *val = args + equals + 1;??
????? // *val指向內(nèi)容/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);
}
第一次執(zhí)行后,[*param] = root? [*val] = /dev/mtdblock3?
next指向init=/linuxrc console=ttySAC0,115200 mem=64M
現(xiàn)在再看parse_one,它定義在同一文件下:
此時相當(dāng)于執(zhí)行: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)) { //因?yàn)閭魅氲膒arams為NULL,這下面不執(zhí)行
?? ??? ??? ?/* 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) { //調(diào)用do_early_param函數(shù)
?? ??? ?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函數(shù),它在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段的起始和結(jié)束的地址
??? for (p = __setup_start; p < __setup_end; p++) { 如果沒有early標(biāo)志則跳過
??? ??? if ((p->early && strcmp(param, p->str) == 0) ||
??? ??? ??? (strcmp(param, "console") == 0 &&
??? ??? ???? strcmp(p->str, "earlycon") == 0)
??? ??? ) {???
??? ??? ??? if (p->setup_func(val) != 0)? 調(diào)用處理函數(shù)
??? ??? ??? ??? printk(KERN_WARNING
??? ??? ??? ??? ?????? "Malformed early option '%s'\n", param);
??? ??? }
??? }
??? /* We accept everything at this stage. */
??? return 0;
}
在do_early_param函數(shù)中for循環(huán)遍歷obs_kernel_param數(shù)組,這里首先要說明一下struct obs_kernel_param結(jié)構(gòu)及這個數(shù)組的由來。
obs_kernel_param結(jié)構(gòu)定義在include/linux/init.h文件中
218 struct obs_kernel_param {
219???????? const char *str;
220???????? int (*setup_func)(char *);
221???????? int early;
222 };
前兩個參數(shù)很簡單,一個是key,一個是處理函數(shù)。最后一個參數(shù)其實(shí)也就是類似于優(yōu)先級的一個標(biāo)志flag,因?yàn)閭鬟f給內(nèi)核的參數(shù)中,有一些需要比另外的一些更早的解析。(這也就是為什么early_param和setup傳遞的最后一個參數(shù)的不同的原因了,后面會講到)。
先說一下系統(tǒng)啟動時對bootloader傳遞參數(shù)的初始化,即linux啟動參數(shù)的實(shí)現(xiàn),啟動參數(shù)的實(shí)現(xiàn),我們知道boot傳遞給內(nèi)核的參數(shù)都是 "name_varibale=value"這種形式的,如下:
CONFIG_CMDLINE="root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
那么內(nèi)核如何知道傳遞進(jìn)來的參數(shù)該怎么去處理呢?
內(nèi)核是通過__setup宏或者early_param宏來將參數(shù)與參數(shù)所處理的函數(shù)相關(guān)聯(lián)起來的。我們接下來就來看這個宏以及相關(guān)的結(jié)構(gòu)的定義:
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值和函數(shù)句柄fn初始化一個static結(jié)構(gòu)體 obs_kernel_param。該結(jié)構(gòu)體在鏈接后存在于.init.setup段。其實(shí)該段也就是所有內(nèi)核參數(shù)所在的處。該段的起始地址是__setup_start,結(jié)束地址為__setup_end。同樣的還有一個early_param宏,也是設(shè)置一個內(nèi)核參數(shù),不過改參數(shù)是早期啟動時相關(guān)的。
看起來很復(fù)雜。 首先setup宏第一個參數(shù)是一個key。比如"netdev="這樣的,而第二個參數(shù)是這個key對應(yīng)的處理函數(shù)。這里要注意相同的handler能聯(lián)系到不同的key。__setup與early_param不同的是,early_param宏注冊的內(nèi)核選項(xiàng)必須要在其他內(nèi)核選項(xiàng)之前被處理。在函數(shù) start_kernel中,parse_early_param處理early_param定義的參數(shù),parse_args處理__setup定義的參數(shù)。
early_param和setup唯一不同的就是傳遞給__setup_param的最后一個參數(shù),這個參數(shù)下面會說明,而接下來 _setup_param定義了一個struct obs_kernel_param類型的結(jié)構(gòu),然后通過_section宏,使這個變量在鏈接的時候能夠放置在段.init.setup(后面會詳細(xì)介紹內(nèi)核中的這些初始化段).
比如說定義一個內(nèi)核參數(shù)來實(shí)現(xiàn)對init程序的指定。見init/main.c中
1,所有的系統(tǒng)啟動參數(shù)都是由形如
static int __init init_setup(char *str)的函數(shù)來支持的
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申明所有的啟動參數(shù)支持函數(shù)都放入.init.text段
2.1,用__setup宏來導(dǎo)出參數(shù)的支持函數(shù)
__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
也就是說,啟動參數(shù)(函數(shù)指針)被封裝到obs_kernel_param結(jié)構(gòu)中,
所有的內(nèi)核啟動參數(shù)形成內(nèi)核映像.init.setup段中的一個
obs_kernel_param數(shù)組,而在do_early_param函數(shù)中for循環(huán)就是遍歷obs_kernel_param數(shù)組,首先看是否
設(shè)置early,如果有設(shè)置并查找到到對就的key,就調(diào)用相關(guān)early_param函數(shù)處理。
用early_param宏來申明需要'早期'處理的啟動參數(shù),例如在
arch/arm/kernel/setup.c就有如下的申明:
468 early_param("mem", early_mem);
展開后和__setup是一樣的只是early參數(shù)不一樣,因此會在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中啟動參數(shù)申明列表:
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中啟動參數(shù)申明列表:
__setup("fpe=", fpe_setup);
early_param("mem", early_mem);
early_param("elfcorehdr", setup_elfcorehdr);
注意在2.6.36版本中,除在start_kernel中的start_arch函數(shù)中第820調(diào)用parse_early_param()外,還在start_arch函數(shù)后的580行和581行分別執(zhí)行下面代碼再次解釋命令行參數(shù)
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我們已經(jīng)很熟悉,在這里不知道為什么它再做的一次,下面看第581行,我們再來看看parse_args函數(shù),見前面的代碼分解出命令行的參數(shù)后,會調(diào)用
ret = parse_one(param, val, params, num, unknown);
相當(dāng)于:
ret = parse_one(param, val, __start___param,__stop___param - __start___param,&unknown_bootoption);
這里提到了兩個參數(shù)__start___param和__stop___param,它涉及到kernel_param 結(jié)構(gòu)體和參數(shù)的存儲
關(guān)于內(nèi)核參數(shù)結(jié)構(gòu)體的定義,見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的結(jié)構(gòu)體,并將這個變量標(biāo)記為__param段,以便于鏈接器將此變量裝載到指定的段,和結(jié)構(gòu)體 obs_kernel_param類似,該宏函數(shù)保持所有實(shí)例存在于__param段。該段的起始地址是__start___param,結(jié)束地址為__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也可以定義內(nèi)核參數(shù),不過內(nèi)核參數(shù)不可以模塊化,也不可以使用前綴命名(如“printk.”)。
接下來我們來看struct kernel_param這個結(jié)構(gòu):
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中兩個函數(shù)的參數(shù)
??? ??? void *arg;
??? ??? const struct kparam_string *str;
??? ??? const struct kparam_array *arr;
??? };
};
其中,聯(lián)合體內(nèi)定義的三個成員,第一個其實(shí)是字符類型的封裝。
/* Special one for strings we want to copy into */
struct kparam_string {
??? unsigned int maxlen;
??? char *string;
};
第二個是數(shù)組類型的封裝。
/* 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為內(nèi)核參數(shù)的名稱,而perm為權(quán)限????。
同時數(shù)組類型的封裝kernel_param_ops中還定義了兩個方法,以函數(shù)指針存在。分別是設(shè)置和讀取操作。
struct kernel_param_ops {
??? /* Returns 0, or -errno.? arg is in kp->arg. */
??? int (*set)(const char *val, const struct kernel_param *kp);設(shè)置參數(shù)的函數(shù)
??? /* Returns length written or -errno.? Buffer is 4k (ie. be short!) */
??? int (*get)(char *buffer, const struct kernel_param *kp);讀取參數(shù)的函數(shù)?
??? /* Optional function to free kp->arg when module unloaded. */
??? void (*free)(void *arg);
};
現(xiàn)在我們再回到parse_one函數(shù)中,我們前面有部分沒有分析,現(xiàn)在再來看一下
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的,則要通過這步來設(shè)置一些內(nèi)置模塊的參數(shù)。
??? /* Find parameter */
??? for (i = 0; i < num_params; i++) {
??? ??? if (parameq(param, params[i].name)) { //如果是內(nèi)置模塊的參數(shù)
??? ??? ??? /* 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]); //調(diào)用參數(shù)設(shè)置函數(shù)來設(shè)置對應(yīng)的內(nèi)置模塊的參數(shù)。
??? ??? ??? 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它調(diào)用unknown_bootoption函數(shù)處理__setup定義的參數(shù),unknown_bootoption函數(shù)在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;
}
在這個函數(shù)中它調(diào)用了obsolete_checksetup函數(shù),該函數(shù)也定義在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)) //調(diào)用支持函數(shù)
??? ??? ??? ??? return 1;
??? ??? }
??? ??? p++;
??? } while (p < __setup_end);
??? return had_early_param;
}
在這里parse_early_param()主要的作用是處理內(nèi)核命令行(boot_command_line)的內(nèi)核參數(shù)。也就是處理在內(nèi)核命令行中有定義的早期參數(shù)值(early=1),特別的還包括內(nèi)核參數(shù)console和earlycon。都和輸出流有關(guān),內(nèi)核啟動時的打印信息就要求該設(shè)備的正確配置。
總結(jié)一下:
內(nèi)核分三類參數(shù)的傳遞與設(shè)置
1、內(nèi)置模塊的參數(shù)設(shè)置
2、高優(yōu)先級命令行參數(shù)設(shè)置
3、一般命令行參數(shù)設(shè)置
三種參婁的設(shè)置都由parse_args函數(shù)來實(shí)現(xiàn),對第一種內(nèi)置模塊的參數(shù)設(shè)置在parse_args函數(shù)中調(diào)用parse_one函數(shù)遍歷__start___param和__stop___param,如果查找到對應(yīng)的key,就調(diào)用相關(guān)參數(shù)設(shè)置函數(shù)處理。
對第二種高優(yōu)先級命令行參數(shù)設(shè)置通過parse_args函數(shù)傳遞調(diào)用函數(shù)do_early_param到parse_one函數(shù)中由do_early_param實(shí)現(xiàn)遍歷obs_kernel_param數(shù)組(__setup_start,__setup_end),首先看是否設(shè)置early,如果有設(shè)置并查找到對應(yīng)的key,就調(diào)用相關(guān)early_param函數(shù)處理。
對第三種一般命令行參數(shù)設(shè)置通過parse_args函數(shù)傳遞調(diào)用函數(shù)unknown_bootoption到parse_one函數(shù)中由unknown_bootoption實(shí)現(xiàn)遍歷 obs_kernel_param數(shù)組(__setup_start,__setup_end),首先看是否設(shè)置early,如果查找到對應(yīng)的key,就調(diào)用相關(guān)__setup函數(shù)處理。
對2.6.36以前版本沒用 parse_early_param而是用parse_cmdline函數(shù),我們來看看這個函數(shù)
static void __init parse_cmdline(char **cmdline_p, char *from)
{
char c = ' ', *to = command_line;
int len = 0;
for (;;) {
if (c == ' ') {
//尋找c=空格 的條件,空格表示一個新的命令行選項(xiàng),假設(shè):mem=xxx noinitrd root=yyy init=/linuxrc console=ttySAC0,這個掃描是一個一個的掃描命令行里的參數(shù)的。
extern struct early_params __early_begin, __early_end; //這些變量在lds中
struct early_params *p;
for (p = &__early_begin; p < &__early_end; p++) { //掃描這個區(qū)間的所有early_params結(jié)構(gòu)。
int len = strlen(p->arg); //測量這個字符串的長度。比如"mem="長度是4
if (memcmp(from, p->arg, len) == 0) { //這里也pass,這里不pass就意味著不能解析。
if (to != command_line) //防止得到兩個空格
to -= 1;
from += len;? //跳過這個選項(xiàng),得到具體數(shù)據(jù),現(xiàn)在from指向“xxx noinitrd...“
p->fn(&from); //調(diào)用這個函數(shù)處理這個xxx
while (*from != ' ' && *from != '\0') //跳過xxx部分,因?yàn)檫@是mem=xxx已經(jīng)處理完了,可以扔掉了。
from++;
break; // 終止這次處理,針對mem=xxx的處理,現(xiàn)在from指向“ noinitrd ...“,注意最前面的空格。
}
}
}
c = *from++; //這次c又得到的是空格。第2次,取到的是noinitrd的n
if (!c)? //如果到了uboot命令行參數(shù)的結(jié)尾,或者 命令行參數(shù)太長,都會結(jié)束掃描
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); //私有的函數(shù)
};
在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);
可以發(fā)現(xiàn)能夠在這里處理的命令行參數(shù)有mem initrd ecc cachepolicy nowrite nocache這六個。
parse_cmdline做了三件事,首先它解析了from所指向的完整的內(nèi)核參數(shù)中關(guān)于內(nèi)存的部分,其次它將沒有解析的部分復(fù)制到command_line中,最后它將start_kernel()傳進(jìn)來的內(nèi)核參數(shù)指針指向command_line。
內(nèi)核參數(shù)中的mem=xxxM@ xxx將會被parse_cmdline解析,并根據(jù)結(jié)果設(shè)置meminfo,而其余部分則被復(fù)制到command_line中就是說,能解析的都解析了,不能解析的留下來,等著以后解析,保存在command_line中,可以發(fā)現(xiàn)uboot的命令行參數(shù)具有最高的優(yōu)先級,如果指定mem=xxxM@yyy的話,將覆蓋掉標(biāo)記列表的mem32配置,如果多次使用mem= mem=的話,將得到多個內(nèi)存bank,通過代碼來看是這樣,沒有驗(yàn)證過。
最后。通過上面的分析,我們可以自定義通過命令行傳入一個參數(shù)設(shè)置,這里以uboot向內(nèi)核傳遞MAC地址為例說明添加過程
?我們使用的系統(tǒng)中的CS8900a沒有外接eeprom,所以在默認(rèn)的情況,Linux下的CS8900a的驅(qū)動使用的是一個偽MAC地址。在單一的系統(tǒng)中,這是沒有問題的,但是當(dāng)我們在同一個子網(wǎng)中使用或測試多個設(shè)備是,就會產(chǎn)生沖突了。所以我們需要能夠方便的改變網(wǎng)卡的MAC地址,而不是將MAC地址硬編碼進(jìn)內(nèi)核中,每次修改都得重新編譯內(nèi)核。
一、添加內(nèi)核參數(shù)的處理函數(shù)
?????? 向Linux驅(qū)動傳遞參數(shù)的方式有兩種,一為在系統(tǒng)啟動的時候由bootloader傳入,還有一種是將驅(qū)動編譯成模塊,將參數(shù)作為模塊加載的參數(shù)傳入。
?????? 這里使用的是由bootloader傳入的方式。內(nèi)核通過setup接口接受Bootloader傳入的參數(shù)。方式如下:
static int __init param_mac_setup(char *str)
{
……
}
__setup("mac=", param_mac_setup);
這樣,當(dāng)在Bootloader中指定“mac=00:2E:79:38:6D:4E”,系統(tǒng)在加載這個模塊的時候,就會執(zhí)行相應(yīng)的param_mac_setup()函數(shù),而傳入給它的參數(shù)就是等號后面的物理地址 “00:2E:79:38:6D:4E”。這樣,該函數(shù)就可以對它進(jìn)行相應(yīng)的處理。
二、將MAC地址參數(shù)傳入命令行參數(shù)
?????? 為了傳入命令行參數(shù),uboot所作的是:
????????????? char *commandline = getenv ("bootargs");
????????????? setup_commandline_tag (bd, commandline);
?????? 現(xiàn)在想要把MAC地址也加入到命令行參數(shù)中,只需要修改配置文件include/configs/smdk2410.h中的CONFIG_BOOTARGS:
#define CONFIG_BOOTARGS?????? "root=ramfs devfs=mount console=ttySA0,9600"
在后面添加mac參數(shù)如下:
#define CONFIG_BOOTARGS?????? "root=ramfs devfs=mount console=ttySA0,9600 mac=00:2E:79:38:6D:4E"
這樣就不需要每次在命令行后面手工追加MAC地址參數(shù)了?
?
評論
查看更多