U-boot會給Linux Kernel傳遞很多參數,如:串口,RAM,videofb、MAC地址等。而Linux kernel也會讀取和處理這些參數。兩者之間通過struct tag來傳遞參數。U-boot把要傳遞給kernel的東西保存在struct tag數據結構中,啟動kernel時,把這個結構體的物理地址傳給kernel;Linux kernel通過這個地址,用parse_tags分析出傳遞過來的參數。
本文主要以U-boot(1.1.6)傳遞RAM和Linux kernel讀取RAM參數為例進行說明。
1、u-boot給kernel傳RAM參數
在介紹該之前,我們需要看一看幾個數據結構,這些是u-boot中幾個重要的數據結構:
(1)gd_t結構體
U-Boot使用了一個結構體gd_t來存儲全局數據區的數據,這個結構體在U-Boot的include/asm-arm/global_data.h中定義如下:
typedef??? struct??? global_data {
??? bd_t??? ??? *bd;?? //與板子相關的結構,見下面
??? unsigned long??? flags;
??? unsigned long??? baudrate;
??? unsigned long??? have_console;??? /* serial_init() was called */
??? unsigned long??? reloc_off;??? /* Relocation Offset */
??? unsigned long??? env_addr;??? /* Address? of Environment struct */
??? unsigned long??? env_valid;??? /* Checksum of Environment valid? */
??? unsigned long??? fb_base;??? /* base address of frame buffer */
#ifdef CONFIG_VFD? //我們一般沒有配置這個,這個是frame buffer的首地址
??? unsigned char??? vfd_type;??? /* display type */
#endif
#if 0
??? unsigned long??? cpu_clk;??? /* CPU clock in Hz!??? ??? */
??? unsigned long??? bus_clk;
??? unsigned long??? ram_size;??? /* RAM size */
??? unsigned long??? reset_status;??? /* reset status register at boot */
#endif
??? void??? ??? **jt;??? ??? /* jump table */
} gd_t;
/*
?* Global Data Flags
?*/
#define??? GD_FLG_RELOC??? 0x00001??? ??? /* Code was relocated to RAM??? ??? */
#define??? GD_FLG_DEVINIT??? 0x00002??? ??? /* Devices have been initialized??? */
#define??? GD_FLG_SILENT??? 0x00004??? ??? /* Silent mode??? ??? ??? ??? */
#define DECLARE_GLOBAL_DATA_PTR???? register volatile gd_t *gd asm ("r8")
??? 在global_data.h中U-Boot使用了一個存儲在寄存器中的指針gd來記錄全局數據區的地址:
#define DECLARE_GLOBAL_DATA_PTR???? register volatile gd_t *gd asm ("r8")
??? DECLARE_GLOBAL_DATA_PTR定義一個gd_t全局數據結構的指針,這個指針存放在指定的寄存器r8中。這個聲明也避免編譯器把r8分配給其它的變量。任何想要訪問全局數據區的代碼,只要代碼開頭加入“DECLARE_GLOBAL_DATA_PTR”一行代碼,然后就可以使用gd指針來訪問全局數據區了。
??? 根據U-Boot內存使用圖中可以計算gd的值:
gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)
2)bd_t 保存與板子相關的配置參數
bd_t在U-Boot的include/asm-arm/u-boot.h中定義如下:
typedef struct bd_info {
??? int??? ??? ??? bi_baudrate;??? /* 串口通訊波特率 */
??? unsigned long??? bi_ip_addr;??? /* IP地址 */
??? unsigned char??? bi_enetaddr[6]; /* Ethernet adress */
??? struct environment_s??? ?????? *bi_env; /*環境變量開始地址 */
??? ulong? bi_arch_number;??? /* unique id for this board開發板的機器碼 */
??? ulong? bi_boot_params;??? /* where this board expects params 內核參數的開始地址*/
??? struct??? ??? ??? ??? /* RAM配置信息 */
??? {
??? ulong start;
??? ulong size;
??? } ??? bi_dram[CONFIG_NR_DRAM_BANKS]; //在我的板子上DRAM配置是1個
#ifdef CONFIG_HAS_ETH1
??? /* second onboard ethernet port */
??? unsigned char?? bi_enet1addr[6];
#endif
} bd_t;
#define bi_env_data bi_env->data
#define bi_env_crc? bi_env->crc
U-Boot啟動內核時要給內核傳遞參數,這時就要使用gd_t,bd_t結構體中的信息來設置標記列表。
3)啟動參數的數據結構
向內核傳遞啟動參數可保存在兩種數據結構中,param_struct和tag,前者是2.4內核用的,后者是2.6以后的內核更期望用的但是,到目前為止,2.6的內核也可以兼容前一種結構,內核參數通過一個靜態的param_struct或tag鏈表在啟動的時候傳遞到內核。需要注意的是,這兩個數據結構在uboot中和linux中分別有定義,這個定義必須一致才能正常傳遞參數如果實際使用中不一致的話就不能正常傳遞,可以自行修改 兩種數據結構具體定義如下(這里說的是內核源碼中的定義):?
struct param_struct {
??? union {
??? struct {
??????? unsigned long page_size;??????? /*? 0 */
??????? unsigned long nr_pages;??????? /*? 4 */
??????? unsigned long ramdisk_size;??????? /*? 8 */
??????? unsigned long flags;??????? /* 12 */
#define FLAG_READONLY??? 1
#define FLAG_RDLOAD??? 4
#define FLAG_RDPROMPT??? 8
??????? unsigned long rootdev;??????? /* 16 */
??????? unsigned long video_num_cols;??? /* 20 */
??????? unsigned long video_num_rows;??? /* 24 */
??????? unsigned long video_x;??????? /* 28 */
??????? unsigned long video_y;??????? /* 32 */
??????? unsigned long memc_control_reg;??? /* 36 */
??????? unsigned char sounddefault;??????? /* 40 */
??????? unsigned char adfsdrives;??????? /* 41 */
??????? unsigned char bytes_per_char_h;??? /* 42 */
??????? unsigned char bytes_per_char_v;??? /* 43 */
??????? unsigned long pages_in_bank[4];??? /* 44 */
??????? unsigned long pages_in_vram;??? /* 60 */
??????? unsigned long initrd_start;??????? /* 64 */
??????? unsigned long initrd_size;??????? /* 68 */
??????? unsigned long rd_start;??????? /* 72 */
??????? unsigned long system_rev;??????? /* 76 */
??????? unsigned long system_serial_low;??? /* 80 */
??????? unsigned long system_serial_high;??? /* 84 */
??????? unsigned long mem_fclk_21285;?????? /* 88 */
??? } s;
??? char unused[256];
??? } u1;
??? union {
??? char paths[8][128];
??? struct {
??????? unsigned long magic;
??????? char n[1024 - sizeof(unsigned long)];
??? } s;
??? } u2;
??? char commandline[COMMAND_LINE_SIZE];
};
param_struct只需要設置cmmandline,u1.s.page_size,u1.s.nr_pages三個域,下面是使用param_struct例子通過param_struct讓uboot中的go命令可以傳遞參數
分析:go的代碼在common/cmd_boot.c中,里面并沒有拷貝啟動參數的代碼,轉向內核的時候也沒有傳送
啟動參數所在的地址,因此添加如下代碼用于拷貝參數,可以看到,對于param_struct只需要設置cmmandline
u1.s.page_size,u1.s.nr_pages三個域?
??????? char *commandline = getenv("bootargs");
??????? struct param_struct *lxy_params=(struct param_struct *)0x80000100;
?
??????? printf("setup linux parameters at 0x80000100\n");
??????? memset(lxy_params,0,sizeof(struct param_struct));
??????? lxy_params->u1.s.page_size=(0x1<<12); //4K 這個是必須有的,否則無法啟動
??????? lxy_params->u1.s.nr_pages=(0x4000000)>>12; //64M 這個是必須有的,否則無法啟動
??????? memcpy(lxy_params->commandline,commandline,strlen(commandline)+1);
??????? printf("linux command line is: "%s"\n",lxy_params->commandline);
然后還要向內核傳遞參數地址,將下面一行代碼修改:
rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);? //需要被修改的代碼
rc = ((ulong(*)(int,int,uint))addr) (0, gd->bd->bi_arch_number,gd->bd->bi_boot_params);//修改之后的代碼?
關于param_struct不是這里重點,下面主要分析tag
??? 對于tag來說,在實際使用中是一個struct tag組成的列表,在tag->tag_header中,一項是u32 tag(重名,注意類型)其值用宏ATAG_CORE,ATAG_MEM,ATAG_CMDLINE,ATAG_NONE等等來表示,此時下面union就會使用與之相關的數據結構同時,規定tag列表中第一項必須是ATAG_CORE,最后一項必須是ATAG_NONE,比如在linux代碼中,找到啟動參數之后首先看tag列表中的第一項的tag->hdr.tag是否為ATAG_CORE,如果不是,就會認為啟動參數不是tag結構而是param_struct結構,然后調用函數來轉換.在tag->tag_header中,另一項是u32 size,表示tag的大小,tag組成列表的方式就是指針+size
tag數據結構在arch/arm/include/asm/setup.h(U-Boot的在include/asm-arm/setup.h定義,完全一樣)中定義如下:
struct tag {
??? struct tag_header hdr;
??? union {
??? ??? struct tag_core??? ??? core;
??? ??? struct tag_mem32??? mem;
??? ??? struct tag_videotext??? videotext;
??? ??? struct tag_ramdisk??? ramdisk;
??? ??? struct tag_initrd??? initrd;
??? ??? struct tag_serialnr??? serialnr;
??? ??? struct tag_revision??? revision;
??? ??? struct tag_videolfb??? videolfb;
??? ??? struct tag_cmdline??? cmdline;
??? ??? /*
??? ??? ?* Acorn specific
??? ??? ?*/
??? ??? struct tag_acorn??? acorn;
??? ??? /*
??? ??? ?* DC21285 specific
??? ??? ?*/
??? ??? struct tag_memclk??? memclk;
??? } u;
};
其中tag_header為tag頭,表明tag_xxx的類型和大小,之所以要標識tag_xxx的類型是因為不同的tag需要不同的處理函數
內核tag_header的結構(arch/arm/include/asm/setup.h)為
struct tag_header {
??????? __u32 size;
??????? __u32 tag;
};
U-Boot的在include/asm-arm/setup.h定義
struct tag_header {
??? u32 size;
??? u32 tag;
};
size表示tag的結構大小,tag為表示tag類型的常量。這個靜態的鏈表必須以tag_header.tag = ATAG_CORE開始,并以tag_header.tag = ATAG_NONE結束。由于不同的tag所使用的格式可能不盡相同,所以內核又定義了一個結構tagtable來把tag和相應的操作函數關聯起來
(arch/arm/include/asm/setup.h)
struct tagtable {
??????? __u32 tag;
??????? int (*parse)(const struct tag *);
};
其中tag為標識入ATAG_NONE,ATAG_CORE等。parse為處理函數。Linux內核將tagtable也組成了一個靜態的鏈表放入.taglist.init節中,這是通過__tagtable宏來實現的?
#define __tag __used __attribute__((__section__(".taglist.init")))
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn }
這個tagtable 列表 是怎么形成的?
如arch/arm/kernel/setup.c
556 static int __init parse_tag_mem32(const struct tag *tag)
557 {
558???????? return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
559 }
560?
561 __tagtable(ATAG_MEM, parse_tag_mem32);
607 __tagtable(ATAG_SERIAL, parse_tag_serialnr);
608?
609 static int __init parse_tag_revision(const struct tag *tag)
610 {
611???????? system_rev = tag->u.revision.rev;
612???????? return 0;
613 }
614?
615 __tagtable(ATAG_REVISION, parse_tag_revision);
618 static int __init parse_tag_cmdline(const struct tag *tag)
619 {
620???????? strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
621???????? return 0;
622 }
623?
624 __tagtable(ATAG_CMDLINE, parse_tag_cmdline);
根據前面相關宏定義,__tagtable(ATAG_CMDLINE, parse_tag_cmdline)展開后為
static struct tagtable __tagtable_parse_tag_cmdline __used __attribute__((__section__(".taglist.init"))) = { ATAG_CMDLINE, parse_tag_cmdline }
__tagtable將ATAG_CMDLINE和parse_tag_cmdline掛鉤,
再參看arch/arm/kernel/vmlinux.lds.S文件
?34???????????????? __proc_info_begin = .;
?35???????????????????????? *(.proc.info.init)
?36???????????????? __proc_info_end = .;
?37???????????????? __arch_info_begin = .;
?38???????????????????????? *(.arch.info.init)
?39???????????????? __arch_info_end = .;
?40???????????????? __tagtable_begin = .;
?41???????????????????????? *(.taglist.init)
?42???????????????? __tagtable_end = .;
tagtable 列表編譯連接后被存放在.taglist.init中。
??? 現在再來看一下U-boot給Linux Kernel傳遞啟動參數的傳遞過程
??? 啟動參數是包裝在struct tag數據結構里的,在linux kernel啟動的時候,bootloader把這個數據結構拷貝到某個地址,在改動PC跳向內核接口的同時,通過通用寄存器R2來傳遞這個地址的值,在bootm執行的流程中,會調用do_bootm_linux()在執行Linux內核,內核的起始地址如下:
void (*theKernel)(int zero, int arch, uint params);
image_header_t *hdr = &header;
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
header是uImage的頭部,通過頭部,得到內核映像起始的執行地址,標識為theKernel。從中也可以看到,內核接受三個參數,第一個為0,第二個為系統的ID號,第三個是傳入內核的參數。
在do_bootm_linux()的最后,會跳到內核去執行:
?????? theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
thekernel其實不是個函數,而是指向內核入口地址的指針,把它強行轉化為帶三個參數的函數指針,會把三個
參數保存到通用寄存器中,實現了向kernel傳遞信息的功能,在這個例子里,把R0賦值為0,R1賦值為機器號bd->bi_arch_number, R2賦值為啟動參數數據結構的首地址bd->bi_boot_params。最后兩個參數在board/smdk2410/smdk2410.c的board_init()中被初始化。
??? 因此,要向內核傳遞參數很簡單,只要把啟動參數封裝在linux預定好的數據結構里,拷貝到某個地址(一般
約定俗成是內存首地址+100dex,后面會見到)? p { margin-bottom: 0.21cm; }
U-boot向內核傳遞參數的具體實現過程
a、在include/asm-arm/global_data.h中聲名一個gd全局指針變量宏定義,并指定存放在r8寄存器中,在后面要用到gd全局指針變量時,只須要在文件開頭引用這個宏就可以了。
?64 #define DECLARE_GLOBAL_DATA_PTR???? register volatile gd_t *gd asm ("r8")
b、在start_armboot(lib_arm/board.c)主函數中計算全局數據結構的地址并賦值給指針gd,并對struct tag數據結構里參數賦值
下面是start_armboot函數部分代碼
?55 DECLARE_GLOBAL_DATA_PTR;?? //gd指針引用聲名
248???????? gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
249???????? /* compiler optimization barrier needed for GCC >= 3.4 */
250???????? __asm__ __volatile__("": : :"memory");
251?
252???????? memset ((void*)gd, 0, sizeof (gd_t));
253???????? gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
254???????? memset (gd->bd, 0, sizeof (bd_t));
255?
256???????? monitor_flash_len = _bss_start - _armboot_start;
257?
258???????? for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
259???????????????? if ((*init_fnc_ptr)() != 0) {
260???????????????????????? hang ();
261???????????????? }
262???????? }
首先在55行對gd指針引用聲名,在248行計算全局數據結構的地址并賦值給指針gd,具體計算請參看前面的說明,253行計算出結構體中bd指針的地址,然后在第258行逐個調用init_sequence初始化函數列表數組中的初始化函數對平臺硬件進行初始化,這里只分析后面用到的硬件初始化函數board_init、dram_init。這兩個函數都在board/smdk2410/smdk2410.c中實現
首先看board_init函數,以下是部分實現
31 DECLARE_GLOBAL_DATA_PTR;
105???????? /* arch number of SMDK2410-Board */
106???????? gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
107?
108???????? /* adress of boot parameters */
109???????? gd->bd->bi_boot_params = 0x30000100;//一般約定俗成是內存首地址+100dex
可以看到,theKernel最后兩個參數在這里的第106和109行被初始化,uboot傳給內核的參數表存被放在內存中起始偏移0x100的位置,這里只是指定了“指針”的位置,但還沒初始化其中的值,后面傳遞到內核的參數列表的構建才初始化其中的值,這是在 do_bootm_linux()中跳到內核前去完成的。值得注意的是, 內核的默認運行地址的0x30008000,前面就是留給參數用的。所以一般不要將內核下載到該地址之前,以免沖掉了傳給內核的參數。這里在55行同樣要對gd指針引用聲名,MACH_TYPE_SMDK2410在include/asm-arm/mach-types.h中定義,值為192
而dram_init函數是對struct tag數據結構里內存參數賦值,后面會用到。
117 int dram_init (void)
118 {
119???????? gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
120???????? gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
121?
122???????? return 0;
123 }
PHYS_SDRAM_1與PHYS_SDRAM_1_SIZE宏都在include/configs/smdk2410.h中定義。
#define CONFIG_NR_DRAM_BANKS??? 1????????? /* we have 1 bank of DRAM */
#define PHYS_SDRAM_1??????????? 0x30000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE?????? 0x04000000 /* 64 MB */
c、傳遞到內核的參數列表的構建
./common/cmd_bootm.c文件中,bootm命令對應的do_bootm函數,當分析uImage中信息發現OS是Linux時,調用./lib_arm/armlinux.c文件中的do_bootm_linux函數來啟動Linux kernel。在do_bootm_linux函數中(lib_arm/armlinux.c) ,以下是部分相關源碼:
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
??? defined (CONFIG_CMDLINE_TAG) || \
??? defined (CONFIG_INITRD_TAG) || \
??? defined (CONFIG_SERIAL_TAG) || \
??? defined (CONFIG_REVISION_TAG) || \
??? defined (CONFIG_LCD) || \
??? defined (CONFIG_VFD)
??? setup_start_tag (bd);??? /* 設置ATAG_CORE標志 */
#ifdef CONFIG_SERIAL_TAG
??? setup_serial_tag (?ms);
#endif
#ifdef CONFIG_REVISION_TAG
??? setup_revision_tag (?ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
??? setup_memory_tags (bd);? /* 設置內存標記 */
#endif
#ifdef CONFIG_CMDLINE_TAG
??? setup_commandline_tag (bd, commandline);? /* 設置命令行標記 */
#endif
#ifdef CONFIG_INITRD_TAG
??? if (initrd_start && initrd_end)
??? ??? setup_initrd_tag (bd, initrd_start, initrd_end);?
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
??? setup_videolfb_tag ((gd_t *) gd);
#endif
??? setup_end_tag (bd);? /* 設置ATAG_NONE標志 */??
#endif
在uboot中,進行設置傳遞到內核的參數列表tag的函數都在lib_arm/armlinux.c中,在這些函數前面是有ifdef的因此,如果你的bootm命令不能傳遞內核參數,就應該是在你的board的config文件里沒有對上述的宏進行設置,定義一下即可?
這里對于setup_start_tag、setup_memory_tags和setup_end_tag函數說明如下。它們都在lib_arm/armlinux.c文件中定義,如下
static void setup_start_tag (bd_t *bd)
{
??? params = (struct tag *) bd->bi_boot_params; /* 內核的參數的開始地址 */
??? params->hdr.tag = ATAG_CORE;
??? params->hdr.size = tag_size (tag_core);
??? params->u.core.flags = 0;
??? params->u.core.pagesize = 0;
??? params->u.core.rootdev = 0;
??? params = tag_next (params);
}
標記列表必須以ATAG_CORE開始,setup_start_tag函數在內核的參數的開始地址設置了一個ATAG_CORE標記。
#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd)? //初始化內存相關tag
{
??? int i;
??? /*設置一個內存標記 */
??? for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
??? ??? params->hdr.tag = ATAG_MEM;
??? ??? params->hdr.size = tag_size (tag_mem32);
??? ??? params->u.mem.start = bd->bi_dram[i].start; //0x30000000
??? ??? params->u.mem.size = bd->bi_dram[i].size;?? //0x04000000(64M)
??? ??? params = tag_next (params);
??? }??
}
#endif /* CONFIG_SETUP_MEMORY_TAGS */
setup_memory_tags函數設置了一個ATAG_MEM標記,該標記包含內存起始地址,內存大小這兩個參數。RAM相關參數在前面的setup_memory_tags函數中已經初始化.
78
static struct tag *setup_commandline_tag(struct tag *params, char *cmdline)
{
?if (!cmdline)
??return params;
/* eat leading white space */
?while (*cmdline == ' ') cmdline++;
/*
? * Don't include tags for empty command lines; let the kernel
? * use its default command line.
? */
?if (*cmdline == '\0')
??return params;
params->hdr.tag = ATAG_CMDLINE;
?params->hdr.size =
??(sizeof (struct tag_header) + strlen(cmdline) + 1 + 3) >> 2;
?strcpy(params->u.cmdline.cmdline, cmdline);
return tag_next(params);
}
static void setup_end_tag (bd_t *bd)
{
??????? params->hdr.tag = ATAG_NONE;
??????? params->hdr.size = 0;
}
這個靜態的鏈表必須以標記ATAG_CORE開始,并以標記ATAG_NONE結束。setup_end_tag函數設置了一個ATAG_NONE標記,表示標記列表的結束。
d、最后do_bootm_linux函數調用theKernel (0, machid, bd->bi_boot_params)去啟動內核并傳遞參數,可以看見r0是machid,r2是bi_boot_params參數的地址。
2、Kernel讀取U-boot傳遞的相關參數
對于Linux Kernel,ARM平臺啟動時,先執行arch/arm/kernel/head.S,此時r2寄存器的值為參數的地址,此文件會調用arch/arm/kernel/head-common.S中的函數,并最后調用start_kernel,看下面head-common.S的源碼:?
?14 #define ATAG_CORE 0x54410001
?15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)
?16 #define ATAG_CORE_SIZE_EMPTY ((2*4) >> 2)
?17?
?18???????? .align? 2
?19???????? .type?? __switch_data, %object
?20 __switch_data:
?21???????? .long?? __mmap_switched
?22???????? .long?? __data_loc????????????????????? @ r4
?23???????? .long?? _data?????????????????????????? @ r5
?24???????? .long?? __bss_start???????????????????? @ r6
?25???????? .long?? _end??????????????????????????? @ r7
?26???????? .long?? processor_id??????????????????? @ r4
?27???????? .long?? __machine_arch_type???????????? @ r5
?28???????? .long?? __atags_pointer???????????????? @ r6
?29???????? .long?? cr_alignment??????????????????? @ r7
?30???????? .long?? init_thread_union + THREAD_START_SP @ sp
?31?
?32 /*
?33? * The following fragment of code is executed with the MMU on in MMU mode,
?34? * and uses absolute addresses; this is not position independent.
?35? *
?36? *? r0? = cp#15 control register
?37? *? r1? = machine ID
?38? *? r2? = atags pointer
?39? *? r9? = processor ID
?40? */
?41 __mmap_switched:
?42???????? adr???? r3, __switch_data + 4
?43?
?44???????? ldmia?? r3!, {r4, r5, r6, r7}
?45???????? cmp???? r4, r5????????????????????????? @ Copy data segment if needed
?46 1:????? cmpne?? r5, r6
?47???????? ldrne?? fp, [r4], #4
?48???????? strne?? fp, [r5], #4
?49???????? bne???? 1b
?50?
?51???????? mov???? fp, #0????????????????????????? @ Clear BSS (and zero fp)
?52 1:????? cmp???? r6, r7
?53???????? strcc?? fp, [r6],#4
?54???????? bcc???? 1b
?55?
?56? ARM(?? ldmia?? r3, {r4, r5, r6, r7, sp})
?57? THUMB( ldmia?? r3, {r4, r5, r6, r7}??? )
?58? THUMB( ldr???? sp, [r3, #16]?????????? )
?59???????? str???? r9, [r4]??????????????????????? @ Save processor ID
?60???????? str???? r1, [r5]??????????????????????? @ Save machine type
?61???????? str???? r2, [r6]??????????????????????? @ Save atags pointer
?62???????? bic???? r4, r0, #CR_A?????????????????? @ Clear 'A' bit
?63???????? stmia?? r7, {r0, r4}??????????????????? @ Save control register values
?64???????? b?????? start_kernel
str r2,[r6]:因為通用寄存器2 (r2) 必須是 kernel parameter list 的物理地址(parameter list 是由boot loader傳遞給kernel,用來描述設備信息屬性的列表),所以將uboot傳遞進來的tags物理地址數值存入__atags_pointer指針( [r6] )中,__atags_pointer在第28行定義并通過42、56行將其加載到r6中,在arch/arm/kernel/setup.c中的setup_arch中將引用__atags_pointer為指向參數的地址.
init/main.c中的start_kernel函數中會調用setup_arch函數來處理各種平臺相關的動作
start_kernel()
{
……
setup_arch(&command_line);
……
}
包括了u-boot傳遞過來參數的分析和保存,對tag的處理代碼也在setup_arch里面。以下是一部分的關鍵代碼(setup_arch函數在arch/arm/kernel/setup.c文件中實現):?
767 void __init setup_arch(char **cmdline_p)
768 {
769???????? struct tag *tags = (struct tag *)&init_tags;//tags指向默認的tag鏈表
770???????? struct machine_desc *mdesc;
771???????? char *from = default_command_line;
772?
773???????? unwind_init();
774?
775???????? setup_processor();
776???????? mdesc = setup_machine(machine_arch_type);// mdesc包含啟動參數在內存中的地址
.....................................................................................................
782???????? if (__atags_pointer) //檢查BootLoader是否傳入參數
783???????????????? tags = phys_to_virt(__atags_pointer);//bootloader有傳遞啟動參數到內核
784???????? else if (mdesc->boot_params)//如果BootLoader沒有傳入參數則使用內核machine descriptor中設置的啟動參數地址(arch/arm/mach-s3c2410/mach-smdk2410.c),這里設置的地址與BootLoader是否傳入的一般是一致的。
785???????????????? tags = phys_to_virt(mdesc->boot_params);
786?
787 #if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
788???????? /*
789????????? * If we have the old style parameters, convert them to
790????????? * a tag list.
791????????? */
792???????? if (tags->hdr.tag != ATAG_CORE)//如果是舊的啟動參數結構,將其轉成新的tag鏈表的形式,新的tag鏈表的形式內核參數列表第一項必須是ATAG_CORE類型,如果不是,則需要轉換成新的內核參數類型。
793???????????????? convert_to_tag_list(tags);//此函數完成新舊參數結構轉換,將參數結構轉換為tag list結構
794 #endif
795???????? if (tags->hdr.tag != ATAG_CORE)//轉換失敗,使用內置的啟動參數
796???????????????? tags = (struct tag *)&init_tags;//則選用默認的內核參數,init_tags文件中有定義。
797?
798???????? if (mdesc->fixup)? //用內核參數列表填充meminfo,fixup函數出現在注冊machine_desc中,即MACHINE_START、MACHINE_END定義中,這個函數,有些板子有,但在2410中沒有定義這個函數。
799???????????????? mdesc->fixup(mdesc, tags, &from, &meminfo);
800?
801???????? if (tags->hdr.tag == ATAG_CORE) {?
802???????????????? if (meminfo.nr_banks != 0) //說明內存被初始化過
803???????????????????????? squash_mem_tags(tags);//如果在meminfo中有配置內存tag則跳過對內存tag的處理,如果是tag list,那么如果系統已經創建了默認的meminfo.nr_banks,清除tags中關于MEM的參數,以免再次被初始化
804???????????????? save_atags(tags);
805???????????????? parse_tags(tags);//做出一些針對各個tags的處理
806???????? }
.....................................................................................................
851 }
第769行tags指向默認的tag鏈表,內核中定義了一些默認的tags
init_tags在arch/arm/kernel/setup.c文件下定義如下
662 static struct init_tags {
663???????? struct tag_header hdr1;
664???????? struct tag_core?? core;
665???????? struct tag_header hdr2;
666???????? struct tag_mem32? mem;
667???????? struct tag_header hdr3;
668 } init_tags __initdata = {
669???????? { tag_size(tag_core), ATAG_CORE },
670???????? { 1, PAGE_SIZE, 0xff },
671???????? { tag_size(tag_mem32), ATAG_MEM },
672???????? { MEM_SIZE, PHYS_OFFSET },
673???????? { 0, ATAG_NONE }
674 };
上述結構中一個tag_header和tag_xxx形成了tag的完整描述,tag_size返回tag_head和tag_xxx的總大小,在tag_size中我們要注意的是u32*指針加1地址值實際上地址加了4
#define tag_next(t) ((struct tag*)((u32*)(t)+(t)->hdr.size))
#define tag_size(type) ((sizeof(struct tag_header)+sizeof(struct type)) >> 2
tag_size實際上計算的是(tag_head+tag_xxx)/4。經過進一步的分析還發現每個tag在內存中的大小并不是相同的,這一點可以從tag_next看出,tag_next只是將指針移到了下一個tag的tag_header處,這種內存布局更加緊湊。
注:2.6.18內核smdk2410的meminfo沒有設置nr_banks,所以必須在內核的啟動參數里面傳遞mem=”memory size”@”memory base address”,否則系統識別內存錯誤,這點從系統的啟動信息就可以看出來,而且在加載initrd的時候也會遇到內存溢出的錯誤
if (__atags_pointer)?????????????????????????????????????????????????????????????????????????
??????? tags = phys_to_virt(__atags_pointer);
指向各種tag起始位置的指針,定義如下:
unsigned int __atags_pointer __initdata;
此指針指向__initdata段,各種tag的信息保存在這個段中。
mdesc->fixup(mdesc, tags, &from, &meminfo):fixup函數是板級相關的,通常就是一些ram起址大小bank之類的設定函數,如果執行過了,nrbank就不為0了,那么繼續執行后面的語句時:
if (tags->hdr.tag == ATAG_CORE) {
???????????? if (meminfo.nr_banks != 0)
???????????????????? squash_mem_tags(tags);
???????????? parse_tags(tags);
}
就會調用squash_mem_tags把你u-boot傳入的值給干掉,使parse_tags函數調用時不會處理ATAG_MEM。
然后執行到parse_tags
parse_tags定義如下(arch/arm/kernel/setup.c)
static void __init parse_tags(const struct tag *t)
{
??? for (; t->hdr.size; t = tag_next(t))
??? ??? if (!parse_tag(t)) //針對每個tag?調用parse_tag 函數
??? ??? ??? printk(KERN_WARNING
??? ??? ??? ??? "Ignoring unrecognised tag 0x%08x\n",
??? ??? ??? ??? t->hdr.tag);
}
parse_tags遍歷tag鏈表調用parse_tag對tag進行處理。parse_tags在tabtable中尋找tag的處理函數(通過tag_header結構中的tag)
static int __init parse_tag(const struct tag *tag)
{
??? extern struct tagtable __tagtable_begin, __tagtable_end;
??? struct tagtable *t;
??? for (t = &__tagtable_begin; t < &__tagtable_end; t++) //遍歷tagtable列表,并調用處理函數,
??? ??? if (tag->hdr.tag == t->tag) {
??? ??? ??? t->parse(tag); //調用處理函數
??? ??? ??? break;
??? ??? }
??? return t < &__tagtable_end;
}
處理各種tags,其中包括了RAM參數的處理。這個函數處理如下tags:
561 __tagtable(ATAG_MEM, parse_tag_mem32);
554 __tagtable(ATAG_CORE, parse_tag_core);
555?
對于處理RAM的tag,調用了parse_tag_mem32函數:
556 static int __init parse_tag_mem32(const struct tag *tag)
557 {
558???????? return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
559 }
560?
561 __tagtable(ATAG_MEM, parse_tag_mem32);
如上可見,parse_tag_mem32函數調用arm_add_memory函數把RAM的start和size等參數保存到了meminfo結構的meminfo結構體中。對照uboot部分內存初始化函數,我們知道uboot傳遞過來的tag->u.mem.start, tag->u.mem.size分別為0x30000000,0x4000000,現在再來分析arm_add_memory
arm_add_memory定義如下(arch/arm/kernel/setup.c)
static int __init arm_add_memory(unsigned long start, unsigned long size)
{
??? struct membank *bank = &meminfo.bank[meminfo.nr_banks];
??? if (meminfo.nr_banks >= NR_BANKS) {
??? ??? printk(KERN_CRIT "NR_BANKS too low, "
??? ??? ??? "ignoring memory at %#lx\n", start);
??? ??? return -EINVAL;
??? }
??? /*
??? ?* Ensure that start/size are aligned to a page boundary.
??? ?* Size is appropriately rounded down, start is rounded up.
??? ?*/
??? size -= start & ~PAGE_MASK;
??? bank->start = PAGE_ALIGN(start);
??? bank->size? = size & PAGE_MASK;
??? /*
??? ?* Check whether this memory region has non-zero size or
??? ?* invalid node number.
??? ?*/
??? if (bank->size == 0)
??? ??? return -EINVAL;
??? meminfo.nr_banks++;
??? return 0;
}
經過這樣的處理,setup.c文件中的meminfo可就不再是
struct meminfo meminfo? = { 0, };
而是
struct meminfo meminfo? = { 1,{0x30000000,0x4000000,0},{}, };
表示當前有一個內存區域,物理地址是從0x30000000開始,大小是64M
最后,在setup_arch中執行下面語句
paging_init(&meminfo, mdesc)
再來看看另一個參數處理函數
618 static int __init parse_tag_cmdline(const struct tag *tag)
619 {
620???????? strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
621???????? return 0;
622 }
623?
624 __tagtable(ATAG_CMDLINE, parse_tag_cmdline);
767 void __init setup_arch(char **cmdline_p)
768 {
769???????? struct tag *tags = (struct tag *)&init_tags;
770???????? struct machine_desc *mdesc;
771???????? char *from = default_command_line;
771行default_command_line在setup.c文件129行中定義如下:
static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;
其中CONFIG_CMDLINE在“.config”配置文件中定義的。定義如下:
CONFIG_CMDLINE="root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
default_command_line 原來的內容是我們配置文件中確定的,但是現在,他被tag->u.cmdline.cmdline覆蓋了。可見,從uboot傳遞過來的命令行參數的優先級要高于配置文件的默認命令行.
我們接著setup_arch中的parse_tags(tags)往下看:
808???????? init_mm.start_code = (unsigned long) _text;
809???????? init_mm.end_code?? = (unsigned long) _etext;
810???????? init_mm.end_data?? = (unsigned long) _edata;
811???????? init_mm.brk??????? = (unsigned long) _end;
812?
813???????? /* parse_early_param needs a boot_command_line */
814???????? strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
815?
816???????? /* populate cmd_line too for later use, preserving boot_command_line */
817???????? strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
818???????? *cmdline_p = cmd_line;
819?
820???????? parse_early_param();
821?
822???????? arm_memblock_init(&meminfo, mdesc);
823?
824???????? paging_init(mdesc);
825???????? request_standard_resources(&meminfo, mdesc);
init_mm.brk??? = (unsigned long) _end:從這兒之后的內存可以動態的分配了。填充 init_mm 的成員,這些數值在lds里面。分別是代碼段,數據段和bss段。
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
上面的代碼先把uboot傳遞過來的命令行參數保存起來,以備后用。
?
linux內核commandline參數解析過程
前面詳細分析了u-boot與linux內核間的tag參數傳遞及解析過程,但對命令行參數沒做詳細的分析,在setup_arch函數的第817行我們看到把uboot傳遞過來的命令行參數保存起來,以備后用。這里的后用就是linux內核commandline參數解析,也就是給第820行代碼備用的,對2.6.36以前版本沒用 parse_early_param而是用parse_cmdline函數,在分析這兩個函數前,我們先來看一下從u-boot到內核命令行參數設置及傳遞過程,以便更好的理解后面的分析。
在u-boot的include/configs/smdk2410.h配置文件中我們可以找到CONFIG_BOOTARGS配置項,在這里我們可以設置要傳遞的到內核的命令行參數,如:
*#define CONFIG_BOOTARGS ? "root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
再看u-boot的common/env_common.c文件
static uchar env_get_char_init (int index);
uchar (*env_get_char)(int) = env_get_char_init;
/************************************************************************
?* Default settings to be used when no valid environment is found
?*/
#define XMK_STR(x)?#x
#define MK_STR(x)?XMK_STR(x)
uchar default_environment[] = {
#ifdef?CONFIG_BOOTARGS
?"bootargs="?CONFIG_BOOTARGS???"\0"
#endif
#ifdef?CONFIG_BOOTCOMMAND
?"bootcmd="?CONFIG_BOOTCOMMAND??"\0"
#endif
#ifdef?CONFIG_RAMBOOTCOMMAND
?"ramboot="?CONFIG_RAMBOOTCOMMAND??"\0"
#endif
#ifdef?CONFIG_NFSBOOTCOMMAND
?"nfsboot="?CONFIG_NFSBOOTCOMMAND??"\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
?"bootdelay="?MK_STR(CONFIG_BOOTDELAY)?"\0"
#endif
#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
?"baudrate="?MK_STR(CONFIG_BAUDRATE)??"\0"
#endif
#ifdef?CONFIG_LOADS_ECHO
?"loads_echo="?MK_STR(CONFIG_LOADS_ECHO)?"\0"
#endif
#ifdef?CONFIG_ETHADDR
?"ethaddr="?MK_STR(CONFIG_ETHADDR)??"\0"
#endif
#ifdef?CONFIG_ETH1ADDR
?"eth1addr="?MK_STR(CONFIG_ETH1ADDR)??"\0"
#endif
#ifdef?CONFIG_ETH2ADDR
?"eth2addr="?MK_STR(CONFIG_ETH2ADDR)??"\0"
#endif
#ifdef?CONFIG_ETH3ADDR
?"eth3addr="?MK_STR(CONFIG_ETH3ADDR)??"\0"
#endif
#ifdef?CONFIG_IPADDR
?"ipaddr="?MK_STR(CONFIG_IPADDR)??"\0"
#endif
#ifdef?CONFIG_SERVERIP
?"serverip="?MK_STR(CONFIG_SERVERIP)??"\0"
#endif
#ifdef?CFG_AUTOLOAD
?"autoload="?CFG_AUTOLOAD???"\0"
#endif
#ifdef?CONFIG_PREBOOT
?"preboot="?CONFIG_PREBOOT???"\0"
#endif
#ifdef?CONFIG_ROOTPATH
?"rootpath="?MK_STR(CONFIG_ROOTPATH)??"\0"
#endif
#ifdef?CONFIG_GATEWAYIP
?"gatewayip="?MK_STR(CONFIG_GATEWAYIP)?"\0"
#endif
#ifdef?CONFIG_NETMASK
?"netmask="?MK_STR(CONFIG_NETMASK)??"\0"
#endif
#ifdef?CONFIG_HOSTNAME
?"hostname="?MK_STR(CONFIG_HOSTNAME)??"\0"
#endif
#ifdef?CONFIG_BOOTFILE
?"bootfile="?MK_STR(CONFIG_BOOTFILE)??"\0"
#endif
#ifdef?CONFIG_LOADADDR
?"loadaddr="?MK_STR(CONFIG_LOADADDR)??"\0"
#endif
。。。。。。。。。。。。。。。
可以知道CONFIG_BOOTARGS被轉化為
"bootargs=""root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
u-boot引導內核為調用u-boot的lib_arm/armlinux.c文件的do_bootm_linux函數
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
?????? ulong addr, ulong *len_ptr, int verify)
{
?ulong len = 0, checksum;
?ulong initrd_start, initrd_end;
?ulong data;
?void (*theKernel)(int zero, int arch, uint params);
?image_header_t *hdr = &header;
?bd_t *bd = gd->bd;
#ifdef CONFIG_CMDLINE_TAG
?char *commandline = getenv ("bootargs");
#endif
245
#ifdef CONFIG_CMDLINE_TAG
?setup_commandline_tag (bd, commandline);
#endif
..........................
}
在這里它首先調用getenv ("bootargs")函數獲得命令行參數并讓commandline指向它,然后調用setup_commandline_tag函數將命令行參數放到tag參數例表,
static struct tag *setup_commandline_tag(struct tag *params, char *cmdline)
{
?if (!cmdline)
??return params;
/* eat leading white space */
?while (*cmdline == ' ') cmdline++;
/*
? * Don't include tags for empty command lines; let the kernel
? * use its default command line.
? */
?if (*cmdline == '\0')
??return params;
params->hdr.tag = ATAG_CMDLINE;
?params->hdr.size =
??(sizeof (struct tag_header) + strlen(cmdline) + 1 + 3) >> 2;
?strcpy(params->u.cmdline.cmdline, cmdline);
return tag_next(params);
}
關于tag參數例表前面己有詳細分析,這里我只對u-boot取命令行環境參數函數getenv進行分析,它定義在common/cmd_nvedit.c文件中
char *getenv (char *name)
{
?int i, nxt;
WATCHDOG_RESET();
for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
??int val;
for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
???if (nxt >= CFG_ENV_SIZE) {
????return (NULL);
???}
??}
??if ((val=envmatch((uchar *)name, i)) < 0)
???continue;
??return ((char *)env_get_addr(val));
?}
return (NULL);
}
這里重點理解env_get_char函數,它定義在common/env_common.c中:
static uchar env_get_char_init (int index);
uchar (*env_get_char)(int) = env_get_char_init;
/************************************************************************
?* Default settings to be used when no valid environment is found
?*/
#define XMK_STR(x)?#x
#define MK_STR(x)?XMK_STR(x)
uchar default_environment[] = {
#ifdef?CONFIG_BOOTARGS
?"bootargs="?CONFIG_BOOTARGS???"\0"
#endif
.................
static uchar env_get_char_init (int index)
{
?uchar c;
/* if crc was bad, use the default environment */
?if (gd->env_valid)
?{
??c = env_get_char_spec(index);
?} else {
??c = default_environment[index];
?}
return (c);
}
這里gd->env_valid參數在start_armboot函數中的初始化函數例表中的env_init函數中設置,如果配置參數保存在flash中,gd->env_valid被設置為1,這里就通過env_get_char_spec函數從flash中取參數,否則gd->env_valid設置為0,使用默認環境變量參數,默認環境變量參數定義在u-boot的common/env_common.c文件uchar default_environment[] ,也就是include/configs/smdk2410.h配置文件中配置的參數。這里針對不同的flash存儲芯片有不同的env_get_char_spec定義
common/env_flash.c
uchar env_get_char_spec (int index)
{
?return ( *((uchar *)(gd->env_addr + index)) );
}
common/env_nand.c
DECLARE_GLOBAL_DATA_PTR;
uchar env_get_char_spec (int index)
{
?return ( *((uchar *)(gd->env_addr + index)) );
}
common/env_nvram.c
#ifdef CONFIG_AMIGAONEG3SE
uchar env_get_char_spec (int index)
{
#ifdef CFG_NVRAM_ACCESS_ROUTINE
?uchar c;
nvram_read(&c, CFG_ENV_ADDR+index, 1);
return c;
#else
?uchar retval;
?enable_nvram();
?retval = *((uchar *)(gd->env_addr + index));
?disable_nvram();
?return retval;
#endif
}
#else
uchar env_get_char_spec (int index)
{
#ifdef CFG_NVRAM_ACCESS_ROUTINE
?uchar c;
nvram_read(&c, CFG_ENV_ADDR+index, 1);
return c;
#else
?return *((uchar *)(gd->env_addr + index));
#endif
}
#endif
為確定gd->env_addr,我們來看一下env_init函數,這里以flash為例,它在common/env_flash.c中
int? env_init(void)
{
#ifdef CONFIG_OMAP2420H4
??? int flash_probe(void);
??? if(flash_probe() == 0)
??? ??? goto bad_flash;
#endif
??? if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
??? ??? gd->env_addr? = (ulong)&(env_ptr->data);
??? ??? gd->env_valid = 1;
??? ??? return(0);
??? }
#ifdef CONFIG_OMAP2420H4
bad_flash:
#endif
??? gd->env_addr? = (ulong)&default_environment[0];
??? gd->env_valid = 0;? 使用默認環境變量參數,gd->env_valid設置為0
??? return (0);
}
而在include/configs/smdk2410.h配置文件中關于flsah的配置如下:
#define PHYS_FLASH_1??????????? 0x00000000 /* Flash Bank #1 */
#define CFG_FLASH_BASE????????? PHYS_FLASH_1
/*-----------------------------------------------------------------------
?* FLASH and environment organization
?*/
#define CONFIG_AMD_LV400??????? 1?????? /* uncomment this if you have a LV400 flash */
#if 0
#define CONFIG_AMD_LV800??????? 1?????? /* uncomment this if you have a LV800 flash */
#endif
#define CFG_MAX_FLASH_BANKS???? 1?????? /* max number of memory banks */
#ifdef CONFIG_AMD_LV800
#define PHYS_FLASH_SIZE???????? 0x00100000 /* 1MB */
#define CFG_MAX_FLASH_SECT????? (19)??? /* max number of sectors on one chip */
#define CFG_ENV_ADDR??????????? (CFG_FLASH_BASE + 0x0F0000) /* addr of environment */
#endif
#ifdef CONFIG_AMD_LV400
#define PHYS_FLASH_SIZE???????? 0x00080000 /* 512KB */
#define CFG_MAX_FLASH_SECT????? (11)??? /* max number of sectors on one chip */
#define CFG_ENV_ADDR??????????? (CFG_FLASH_BASE + 0x070000) /* addr of environment */
#endif
/* timeout values are in ticks */
#define CFG_FLASH_ERASE_TOUT??? (5*CFG_HZ) /* Timeout for Flash Erase */
#define CFG_FLASH_WRITE_TOUT??? (5*CFG_HZ) /* Timeout for Flash Write */
#define CFG_ENV_IS_IN_FLASH???? 1
#define CFG_ENV_SIZE??????????? 0x10000 /* Total Size of Environment Sector */
#endif? /* __CONFIG_H */
在common/env_flash.c中對env_ptr定義如下:
char * env_name_spec = "Flash";
#ifdef ENV_IS_EMBEDDED
extern uchar environment[];
env_t *env_ptr = (env_t *)(&environment[0]);
#ifdef CMD_SAVEENV
/* static env_t *flash_addr = (env_t *)(&environment[0]);-broken on ARM-wd-*/
static env_t *flash_addr = (env_t *)CFG_ENV_ADDR;
#endif
#else /* ! ENV_IS_EMBEDDED */
env_t *env_ptr = (env_t *)CFG_ENV_ADDR;
#ifdef CMD_SAVEENV
static env_t *flash_addr = (env_t *)CFG_ENV_ADDR;
#endif
#endif /* ENV_IS_EMBEDDED */
通過上面幾個文件相關定義,我們很容易知道env_get_char_init函數功能就是如果保存了參數到flsah中就調用env_get_char_spec從指定的flash地址中讀取參數字符,否則就從默認環境變量參數中讀取參數字符。
理解完env_get_char_init函數后,再來看envmatch函數,定義在common/cmd_nvedit.c
/************************************************************************
?* Match a name / name=value pair
?*
?* s1 is either a simple 'name', or a 'name=value' pair.
?* i2 is the environment index for a 'name2=value2' pair.
?* If the names match, return the index for the value2, else NULL.
?*/
static int
envmatch (uchar *s1, int i2)
{
while (*s1 == env_get_char(i2++))
??if (*s1++ == '=')
???return(i2);
?if (*s1 == '\0' && env_get_char(i2-1) == '=')
??return(i2);
?return(-1);
}
這個函數功能是查找符號變量,如果找到則返回等號后面的字符串指針,即為變量的值,環境變量表是一個字符串數組,而其中的變量之間通過’\0’符號隔開,即是當遇到該符號時,則表示一個變量結束而另一個變量開始。
common/env_common.c
uchar *env_get_addr (int index)
{
?if (gd->env_valid) {
??return ( ((uchar *)(gd->env_addr + index)) );
?} else {
??return (&default_environment[index]);
?}
}
這個函數功能是返回找到的環境變量字符串數組地址。
此至,命令行參數在u-boot中設置及傳遞過程分析完了,下面我們再來看linux內核commandline參數解析過程,內核在start_kernel函數調用start_arch獲取tag參數地址后,再調用parse_tags完成了tag參數解釋,之后就是linux內核commandline參數解析,也就調用parse_early_param或parse_cmdline(對2.6.36以前版本)函數來完成對命令行參數的解釋。
linux內核commandline參數解析過程
前面使用
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
??? *cmdline_p = cmd_line;
將命令行參數保存到了cmd_line中
parse_early_param();
現在再來看看start_arch函數中第820行的parse_early_param函數
init/main.c
void __init parse_early_options(char *cmdline)
{
??? parse_args("early options", cmdline, NULL, 0, do_early_param);
}
/* Arch code calls this early on, or if not, just before other parsing. */
void __init parse_early_param(void)
{
??? static __initdata int done = 0;
??? static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];
??? if (done)
??? ??? return;
??? /* All fall through to do_early_param. */
??? strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
??? parse_early_options(tmp_cmdline);
??? done = 1;
}
在上面我們可以看到最終調用的是 parse_args("early options", cmdline, NULL, 0, do_early_param);parse_args在kernel/params.c中定義,注意它與前面的parse_tags(const struct tag *t)區別。
/* Args looks like "foo=bar,bar2 baz=fuz wiz". */
int parse_args(const char *name,
??? ?????? char *args,
??? ?????? const struct kernel_param *params,
??? ?????? unsigned num,
??? ?????? int (*unknown)(char *param, char *val))
{
??? char *param, *val;
??? DEBUGP("Parsing ARGS: %s\n", args);
??? /* Chew leading spaces? 跳過前面的空格*/
??? args = skip_spaces(args);
??? while (*args) {
??? ??? int ret;
??? ??? int irq_was_disabled;
??? ??? args = next_arg(args, ?m, &val);
??? ??? irq_was_disabled = irqs_disabled();
??? ??? ret = parse_one(param, val, params, num, unknown);
??? ??? if (irq_was_disabled && !irqs_disabled()) {
??? ??? ??? printk(KERN_WARNING "parse_args(): option '%s' enabled "
??? ??? ??? ??? ??? "irq's!\n", param);
??? ??? }
??? ??? switch (ret) {
??? ??? case -ENOENT:
??? ??? ??? printk(KERN_ERR "%s: Unknown parameter `%s'\n",
??? ??? ??? ?????? name, param);
??? ??? ??? return ret;
??? ??? case -ENOSPC:
??? ??? ??? printk(KERN_ERR
??? ??? ??? ?????? "%s: `%s' too large for parameter `%s'\n",
??? ??? ??? ?????? name, val ?: "", param);
??? ??? ??? return ret;
??? ??? case 0:
??? ??? ??? break;
??? ??? default:
??? ??? ??? printk(KERN_ERR
??? ??? ??? ?????? "%s: `%s' invalid for parameter `%s'\n",
??? ??? ??? ?????? name, val ?: "", param);
??? ??? ??? return ret;
??? ??? }
??? }
??? /* All parsed OK. */
??? return 0;
}
#define isspace(c)??? ((c) == ' ')
char *skip_spaces(const char *str) 跳過前面的空格函數
{
??? while (isspace(*str))
??? ??? ++str;
??? return (char *)str;
}
EXPORT_SYMBOL(skip_spaces);
?
對于next_arg就在parse_args前定義如下:
它的功能解釋"root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"參數表,以第一個root=/dev/mtdblock3為例說明:
static char *next_arg(char *args, char **param, char **val)
{
??? unsigned int i, equals = 0;
??? int in_quote = 0, quoted = 0; //in_quote字符串結束標志
??? char *next;
?? // [args] 指向內容"root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
??? if (*args == '"') {
??? ??? args++;? //[args]指向內容root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
??? ??? in_quote = 1;? //in_quote字符串開始標志
??? ??? quoted = 1;
??? }
??? for (i = 0; args[i]; i++) { //循環完后,
??? ??? if (isspace(args[i]) && !in_quote)? //空格或沒有字符了,退出。
??? ??? ??? break;
??? ??? if (equals == 0) { //查找到第一個=號位置
??? ??? ??? if (args[i] == '=')
??? ??? ??? ??? equals = i;
??? ??? }
??? ??? if (args[i] == '"')? //最后一個結束字符'"'嗎?是的話設置in_quote = !in_quote
??? ??? ??? in_quote = !in_quote;
??? }
??? *param = args;??
?? //[args] 指向內容root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
??? if (!equals)
??? ??? *val = NULL;
??? else {
??? ??? args[equals] = '\0';
????? //[args]指向內容root /dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
??? ??? *val = args + equals + 1;??
????? // *val指向內容/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
??? ??? /* Don't include quotes in value. 去掉引號*/
??? ??? if (**val == '"') {
??? ??? ??? (*val)++;
??? ??? ??? if (args[i-1] == '"')
??? ??? ??? ??? args[i-1] = '\0';
??? ??? }
??? ??? if (quoted && args[i-1] == '"')
??? ??? ??? args[i-1] = '\0';
??? }
??? if (args[i]) {
??? ??? args[i] = '\0';
??? ??? next = args + i + 1;
??? } else
??? ??? next = args + i;
??? /* Chew up trailing spaces. */
??? return skip_spaces(next);
}
第一次執行后,[*param] = root? [*val] = /dev/mtdblock3?
next指向init=/linuxrc console=ttySAC0,115200 mem=64M
現在再看parse_one,它定義在同一文件下:
此時相當于執行:parse_one("root", "/dev/mtdblock3",NULL, 0, do_early_param);
static int parse_one(char *param,
?? ??? ????? char *val,
?? ??? ????? const struct kernel_param *params,
?? ??? ????? unsigned num_params,
?? ??? ????? int (*handle_unknown)(char *param, char *val))
{
?? ?unsigned int i;
?? ?int err;
?? ?/* Find parameter */
?? ?for (i = 0; i < num_params; i++) {
?? ??? ?if (parameq(param, params[i].name)) { //因為傳入的params為NULL,這下面不執行
?? ??? ??? ?/* Noone handled NULL, so do it here. */
?? ??? ??? ?if (!val && params[i].ops->set != param_set_bool)
?? ??? ??? ??? ?return -EINVAL;
?? ??? ??? ?DEBUGP("They are equal!? Calling %p\n",
?? ??? ??? ??????? params[i].ops->set);
?? ??? ??? ?mutex_lock(?m_lock);
?? ??? ??? ?err = params[i].ops->set(val, ?ms[i]);
?? ??? ??? ?mutex_unlock(?m_lock);
?? ??? ??? ?return err;
?? ??? ?}
?? ?}
?? ?if (handle_unknown) { //調用do_early_param函數
?? ??? ?DEBUGP("Unknown argument: calling %p\n", handle_unknown);
?? ??? ?return handle_unknown(param, val);?
?? ?}
?? ?DEBUGP("Unknown argument `%s'\n", param);
?? ?return -ENOENT;
}
以下只作了解:
kernel/params.c
static inline char dash2underscore(char c)
{
??? if (c == '-')
??? ??? return '_';
??? return c;
}
static inline int parameq(const char *input, const char *paramname)
{
??? unsigned int i;
??? for (i = 0; dash2underscore(input[i]) == paramname[i]; i++)
??? ??? if (input[i] == '\0')
??? ??? ??? return 1;
??? return 0;
}?
我們再來看一下do_early_param函數,它在init/main.c中
static int __init do_early_param(char *param, char *val)
{
??? const struct obs_kernel_param *p;
??? 這里的__setup_start和_-setup_end分別是.init.setup段的起始和結束的地址
??? for (p = __setup_start; p < __setup_end; p++) { 如果沒有early標志則跳過
??? ??? if ((p->early && strcmp(param, p->str) == 0) ||
??? ??? ??? (strcmp(param, "console") == 0 &&
??? ??? ???? strcmp(p->str, "earlycon") == 0)
??? ??? ) {???
??? ??? ??? if (p->setup_func(val) != 0)? 調用處理函數
??? ??? ??? ??? printk(KERN_WARNING
??? ??? ??? ??? ?????? "Malformed early option '%s'\n", param);
??? ??? }
??? }
??? /* We accept everything at this stage. */
??? return 0;
}
在do_early_param函數中for循環遍歷obs_kernel_param數組,這里首先要說明一下struct obs_kernel_param結構及這個數組的由來。
obs_kernel_param結構定義在include/linux/init.h文件中
218 struct obs_kernel_param {
219???????? const char *str;
220???????? int (*setup_func)(char *);
221???????? int early;
222 };
前兩個參數很簡單,一個是key,一個是處理函數。最后一個參數其實也就是類似于優先級的一個標志flag,因為傳遞給內核的參數中,有一些需要比另外的一些更早的解析。(這也就是為什么early_param和setup傳遞的最后一個參數的不同的原因了,后面會講到)。
先說一下系統啟動時對bootloader傳遞參數的初始化,即linux啟動參數的實現,啟動參數的實現,我們知道boot傳遞給內核的參數都是 "name_varibale=value"這種形式的,如下:
CONFIG_CMDLINE="root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
那么內核如何知道傳遞進來的參數該怎么去處理呢?
內核是通過__setup宏或者early_param宏來將參數與參數所處理的函數相關聯起來的。我們接下來就來看這個宏以及相關的結構的定義:
include/linux/init.h
230 #define __setup_param(str, unique_id, fn, early)??????????????????????? \
231???????? static const char __setup_str_##unique_id[] __initconst \
232???????????????? __aligned(1) = str; \
233???????? static struct obs_kernel_param __setup_##unique_id????? \
234???????????????? __used __section(.init.setup)?????????????????? \
235???????????????? __attribute__((aligned((sizeof(long)))))??????? \
236???????????????? = { __setup_str_##unique_id, fn, early }
237?
238 #define __setup(str, fn)??????????????????????????????????????? \
239???????? __setup_param(str, fn, fn, 0)
240?
241 /* NOTE: fn is as per module_param, not __setup!? Emits warning if fn
242? * returns non-zero. */
243 #define early_param(str, fn)??????????????????????????????????? \
244???????? __setup_param(str, fn, fn, 1)
可見,__setup宏的作用是使用str值和函數句柄fn初始化一個static結構體 obs_kernel_param。該結構體在鏈接后存在于.init.setup段。其實該段也就是所有內核參數所在的處。該段的起始地址是__setup_start,結束地址為__setup_end。同樣的還有一個early_param宏,也是設置一個內核參數,不過改參數是早期啟動時相關的。
看起來很復雜。 首先setup宏第一個參數是一個key。比如"netdev="這樣的,而第二個參數是這個key對應的處理函數。這里要注意相同的handler能聯系到不同的key。__setup與early_param不同的是,early_param宏注冊的內核選項必須要在其他內核選項之前被處理。在函數 start_kernel中,parse_early_param處理early_param定義的參數,parse_args處理__setup定義的參數。
early_param和setup唯一不同的就是傳遞給__setup_param的最后一個參數,這個參數下面會說明,而接下來 _setup_param定義了一個struct obs_kernel_param類型的結構,然后通過_section宏,使這個變量在鏈接的時候能夠放置在段.init.setup(后面會詳細介紹內核中的這些初始化段).
比如說定義一個內核參數來實現對init程序的指定。見init/main.c中
1,所有的系統啟動參數都是由形如
static int __init init_setup(char *str)的函數來支持的
static int __init init_setup(char *str)
{
??? unsigned int i;
??? execute_command = str;
??? for (i = 1; i < MAX_INIT_ARGS; i++)
??? ??? argv_init[i] = NULL;
??? return 1;
}
__setup("init=", init_setup);
注:(include/linux/init.h):
#define __init????????? __section(.init.text) __cold notrace申明所有的啟動參數支持函數都放入.init.text段
2.1,用__setup宏來導出參數的支持函數
__setup("init=", init_setup);
展開后就是如下的形式
static const char __setup_str_init_setup[] __initdata = "init=";???
static struct obs_kernel_param __setup_init_setup? ?????????????
??????? __used __section__(".init.setup")
??????? __attribute__((aligned((sizeof(long)))))???
??????? = { __setup_str_init_setup, init_setup, 0 };//"init=",init_setup,0
也就是說,啟動參數(函數指針)被封裝到obs_kernel_param結構中,
所有的內核啟動參數形成內核映像.init.setup段中的一個
obs_kernel_param數組,而在do_early_param函數中for循環就是遍歷obs_kernel_param數組,首先看是否
設置early,如果有設置并查找到到對就的key,就調用相關early_param函數處理。
用early_param宏來申明需要'早期'處理的啟動參數,例如在
arch/arm/kernel/setup.c就有如下的申明:
468 early_param("mem", early_mem);
展開后和__setup是一樣的只是early參數不一樣,因此會在do_early_param
中被處理
443 static int __init early_mem(char *p)
444 {
445???????? static int usermem __initdata = 0;
446???????? unsigned long size, start;
447???????? char *endp;
448?
449???????? /*
453????????? */
454???????? if (usermem == 0) {
455???????????????? usermem = 1;
456???????????????? meminfo.nr_banks = 0;
457???????? }
458?
459???????? start = PHYS_OFFSET;
460???????? size? = memparse(p, &endp);
461???????? if (*endp == '@')
462???????????????? start = memparse(endp + 1, NULL);
463?
464???????? arm_add_memory(start, size);
465?
466???????? return 0;
467 }
init/main.c中啟動參數申明列表:
early_param("nosmp", nosmp);
early_param("nr_cpus", nrcpus);
early_param("maxcpus", maxcpus);
__setup("reset_devices", set_reset_devices);
early_param("debug", debug_kernel);
early_param("quiet", quiet_kernel);
early_param("loglevel", loglevel);
__setup("init=", init_setup);
__setup("rdinit=", rdinit_setup);
arch/arm/kernel/setup.c中啟動參數申明列表:
__setup("fpe=", fpe_setup);
early_param("mem", early_mem);
early_param("elfcorehdr", setup_elfcorehdr);
注意在2.6.36版本中,除在start_kernel中的start_arch函數中第820調用parse_early_param()外,還在start_arch函數后的580行和581行分別執行下面代碼再次解釋命令行參數
579???????? printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
580???????? parse_early_param();
581???????? parse_args("Booting kernel", static_command_line, __start___param,
582??????????????????? __stop___param - __start___param,
583??????????????????? &unknown_bootoption);
對于parse_early_param我們已經很熟悉,在這里不知道為什么它再做的一次,下面看第581行,我們再來看看parse_args函數,見前面的代碼分解出命令行的參數后,會調用
ret = parse_one(param, val, params, num, unknown);
相當于:
ret = parse_one(param, val, __start___param,__stop___param - __start___param,&unknown_bootoption);
這里提到了兩個參數__start___param和__stop___param,它涉及到kernel_param 結構體和參數的存儲
關于內核參數結構體的定義,見include/linux/moduleparam.h
?99 #define module_param(name, type, perm)????????????????????????? \
100???????? module_param_named(name, name, type, perm)
113 #define module_param_named(name, value, type, perm)??????????????????????? \
114???????? param_check_##type(name, &(value));??????????????????????????????? \
115???????? module_param_cb(name, ?m_ops_##type, &value, perm);??????????? \
116???????? __MODULE_PARM_TYPE(name, #type)
126 #define module_param_cb(name, ops, arg, perm)???????????????????????????????????? \
127???????? __module_param_call(MODULE_PARAM_PREFIX,????????????????????????????????? \
128???????????????????????????? name, ops, arg, __same_type((arg), bool *), perm??? )
142 #define __module_param_call(prefix, name, ops, arg, isbool, perm)?????? \
143???????? /* Default value instead of permissions? */???????????????????? \
144???????? static int __param_perm_check_##name __attribute__((unused)) =? \
145???????? BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2))? \
146???????? + BUILD_BUG_ON_ZERO(sizeof(""prefix) > MAX_PARAM_PREFIX_LEN);?? \
147???????? static const char __param_str_##name[] = prefix #name;????????? \
148???????? static struct kernel_param __moduleparam_const __param_##name?? \
149???????? __used????????????????????????????????????????????????????????? \
150???? __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *))))???? \
151???????? = { __param_str_##name, ops, perm, isbool ? KPARAM_ISBOOL : 0,? \
152???????????? { arg } }
這里也就是填充了 struct kernel_param的結構體,并將這個變量標記為__param段,以便于鏈接器將此變量裝載到指定的段,和結構體 obs_kernel_param類似,該宏函數保持所有實例存在于__param段。該段的起始地址是__start___param,結束地址為__stop___param。具體鏈接腳本在include/asm-generic/vmlinux.lds.h
350???????? /* Built-in module parameters. */?????????????????????????????? \
351???????? __param : AT(ADDR(__param) - LOAD_OFFSET) {???????????????????? \
352???????????????? VMLINUX_SYMBOL(__start___param) = .;??????????????????? \
353???????????????? *(__param)????????????????????????????????????????????? \
354???????????????? VMLINUX_SYMBOL(__stop___param) = .;???????????????????? \
355???????????????? . = ALIGN((align));???????????????????????????????????? \
356???????????????? VMLINUX_SYMBOL(__end_rodata) = .;?????????????????????? \
357???????? }?????????????????????????????????????????????????????????????? \
358???????? . = ALIGN((align));
這里給個例子net/ipv4/netfilter/nf_nat_irc.c
static int warn_set(const char *val, struct kernel_param *kp)
{
??????? printk(KERN_INFO KBUILD_MODNAME
?????????????? ": kernel >= 2.6.10 only uses 'ports' for conntrack modules\n");
??????? return 0;
}
module_param_call(ports, warn_set, NULL, NULL, 0);
處理module_param_call之外,還有core_param也可以定義內核參數,不過內核參數不可以模塊化,也不可以使用前綴命名(如“printk.”)。
接下來我們來看struct kernel_param這個結構:
include/linux/moduleparam.h
struct kernel_param;
/* Flag bits for kernel_param.flags */
#define KPARAM_ISBOOL??? ??? 2
struct kernel_param {
??? const char *name;
??? const struct kernel_param_ops *ops;
??? u16 perm;
??? u16 flags;
??? union { 傳遞給上面kernel_param_ops中兩個函數的參數
??? ??? void *arg;
??? ??? const struct kparam_string *str;
??? ??? const struct kparam_array *arr;
??? };
};
其中,聯合體內定義的三個成員,第一個其實是字符類型的封裝。
/* Special one for strings we want to copy into */
struct kparam_string {
??? unsigned int maxlen;
??? char *string;
};
第二個是數組類型的封裝。
/* Special one for arrays */
struct kparam_array
{
??? unsigned int max;
??? unsigned int *num;
??? const struct kernel_param_ops *ops;
??? unsigned int elemsize;
??? void *elem;
};
還剩下的常字符串類型成員name為內核參數的名稱,而perm為權限????。
同時數組類型的封裝kernel_param_ops中還定義了兩個方法,以函數指針存在。分別是設置和讀取操作。
struct kernel_param_ops {
??? /* Returns 0, or -errno.? arg is in kp->arg. */
??? int (*set)(const char *val, const struct kernel_param *kp);設置參數的函數
??? /* Returns length written or -errno.? Buffer is 4k (ie. be short!) */
??? int (*get)(char *buffer, const struct kernel_param *kp);讀取參數的函數?
??? /* Optional function to free kp->arg when module unloaded. */
??? void (*free)(void *arg);
};
現在我們再回到parse_one函數中,我們前面有部分沒有分析,現在再來看一下
static int parse_one(char *param,
??? ??? ???? char *val,
??? ??? ???? const struct kernel_param *params,
??? ??? ???? unsigned num_params,
??? ??? ???? int (*handle_unknown)(char *param, char *val))
{
??? unsigned int i;
??? int err;
???? 如果是early_param則直接跳過這步,而非early的,則要通過這步來設置一些內置模塊的參數。
??? /* Find parameter */
??? for (i = 0; i < num_params; i++) {
??? ??? if (parameq(param, params[i].name)) { //如果是內置模塊的參數
??? ??? ??? /* Noone handled NULL, so do it here. */
??? ??? ??? if (!val && params[i].ops->set != param_set_bool)??
??? ??? ??? ??? return -EINVAL;
??? ??? ??? DEBUGP("They are equal!? Calling %p\n",
??? ??? ??? ?????? params[i].ops->set);
??? ??? ??? mutex_lock(?m_lock);
??? ??? ??? err = params[i].ops->set(val, ?ms[i]); //調用參數設置函數來設置對應的內置模塊的參數。
??? ??? ??? mutex_unlock(?m_lock);
??? ??? ??? return err;
??? ??? }
??? }
??? if (handle_unknown) {
??? ??? DEBUGP("Unknown argument: calling %p\n", handle_unknown);
??? ??? return handle_unknown(param, val);
??? }
??? DEBUGP("Unknown argument `%s'\n", param);
??? return -ENOENT;
}
parse_one它調用unknown_bootoption函數處理__setup定義的參數,unknown_bootoption函數在init/main.c中定義如下:
static int __init unknown_bootoption(char *param, char *val)
{
??? /* Change NUL term back to "=", to make "param" the whole string. */
??? if (val) {
??? ??? /* param=val or param="val"? */
??? ??? if (val == param+strlen(param)+1)
??? ??? ??? val[-1] = '=';
??? ??? else if (val == param+strlen(param)+2) {
??? ??? ??? val[-2] = '=';
??? ??? ??? memmove(val-1, val, strlen(val)+1);
??? ??? ??? val--;
??? ??? } else
??? ??? ??? BUG();
??? }
??? /* Handle obsolete-style parameters */
??? if (obsolete_checksetup(param))
??? ??? return 0;
??? /* Unused module parameter. */
??? if (strchr(param, '.') && (!val || strchr(param, '.') < val))
??? ??? return 0;
??? if (panic_later)
??? ??? return 0;
??? if (val) {
??? ??? /* Environment option */
??? ??? unsigned int i;
??? ??? for (i = 0; envp_init[i]; i++) {
??? ??? ??? if (i == MAX_INIT_ENVS) {
??? ??? ??? ??? panic_later = "Too many boot env vars at `%s'";
??? ??? ??? ??? panic_param = param;
??? ??? ??? }
??? ??? ??? if (!strncmp(param, envp_init[i], val - param))
??? ??? ??? ??? break;
??? ??? }
??? ??? envp_init[i] = param;
??? } else {
??? ??? /* Command line option */
??? ??? unsigned int i;
??? ??? for (i = 0; argv_init[i]; i++) {
??? ??? ??? if (i == MAX_INIT_ARGS) {
??? ??? ??? ??? panic_later = "Too many boot init vars at `%s'";
??? ??? ??? ??? panic_param = param;
??? ??? ??? }
??? ??? }
??? ??? argv_init[i] = param;
??? }
??? return 0;
}
在這個函數中它調用了obsolete_checksetup函數,該函數也定義在init/main.c中
static int __init obsolete_checksetup(char *line)
{
??? const struct obs_kernel_param *p;
??? int had_early_param = 0;
??? p = __setup_start;
??? do {
??? ??? int n = strlen(p->str);
??? ??? if (!strncmp(line, p->str, n)) {
??? ??? ??? if (p->early) {
??? ??? ??? ??? /* Already done in parse_early_param?
??? ??? ??? ??? ?* (Needs exact match on param part).
??? ??? ??? ??? ?* Keep iterating, as we can have early
??? ??? ??? ??? ?* params and __setups of same names 8( */
??? ??? ??? ??? if (line[n] == '\0' || line[n] == '=')
??? ??? ??? ??? ??? had_early_param = 1;
??? ??? ??? } else if (!p->setup_func) {
??? ??? ??? ??? printk(KERN_WARNING "Parameter %s is obsolete,"
??? ??? ??? ??? ?????? " ignored\n", p->str);
??? ??? ??? ??? return 1;
??? ??? ??? } else if (p->setup_func(line + n)) //調用支持函數
??? ??? ??? ??? return 1;
??? ??? }
??? ??? p++;
??? } while (p < __setup_end);
??? return had_early_param;
}
在這里parse_early_param()主要的作用是處理內核命令行(boot_command_line)的內核參數。也就是處理在內核命令行中有定義的早期參數值(early=1),特別的還包括內核參數console和earlycon。都和輸出流有關,內核啟動時的打印信息就要求該設備的正確配置。
總結一下:
內核分三類參數的傳遞與設置
1、內置模塊的參數設置
2、高優先級命令行參數設置
3、一般命令行參數設置
三種參婁的設置都由parse_args函數來實現,對第一種內置模塊的參數設置在parse_args函數中調用parse_one函數遍歷__start___param和__stop___param,如果查找到對應的key,就調用相關參數設置函數處理。
對第二種高優先級命令行參數設置通過parse_args函數傳遞調用函數do_early_param到parse_one函數中由do_early_param實現遍歷obs_kernel_param數組(__setup_start,__setup_end),首先看是否設置early,如果有設置并查找到對應的key,就調用相關early_param函數處理。
對第三種一般命令行參數設置通過parse_args函數傳遞調用函數unknown_bootoption到parse_one函數中由unknown_bootoption實現遍歷 obs_kernel_param數組(__setup_start,__setup_end),首先看是否設置early,如果查找到對應的key,就調用相關__setup函數處理。
對2.6.36以前版本沒用 parse_early_param而是用parse_cmdline函數,我們來看看這個函數
static void __init parse_cmdline(char **cmdline_p, char *from)
{
char c = ' ', *to = command_line;
int len = 0;
for (;;) {
if (c == ' ') {
//尋找c=空格 的條件,空格表示一個新的命令行選項,假設:mem=xxx noinitrd root=yyy init=/linuxrc console=ttySAC0,這個掃描是一個一個的掃描命令行里的參數的。
extern struct early_params __early_begin, __early_end; //這些變量在lds中
struct early_params *p;
for (p = &__early_begin; p < &__early_end; p++) { //掃描這個區間的所有early_params結構。
int len = strlen(p->arg); //測量這個字符串的長度。比如"mem="長度是4
if (memcmp(from, p->arg, len) == 0) { //這里也pass,這里不pass就意味著不能解析。
if (to != command_line) //防止得到兩個空格
to -= 1;
from += len;? //跳過這個選項,得到具體數據,現在from指向“xxx noinitrd...“
p->fn(&from); //調用這個函數處理這個xxx
while (*from != ' ' && *from != '\0') //跳過xxx部分,因為這是mem=xxx已經處理完了,可以扔掉了。
from++;
break; // 終止這次處理,針對mem=xxx的處理,現在from指向“ noinitrd ...“,注意最前面的空格。
}
}
}
c = *from++; //這次c又得到的是空格。第2次,取到的是noinitrd的n
if (!c)? //如果到了uboot命令行參數的結尾,或者 命令行參數太長,都會結束掃描
break;
if (COMMAND_LINE_SIZE <= ++len)
break;
*to++ = c; //保存空格,第2此保存了n,依次類推。
}
*to = '\0';
*cmdline_p = command_line; //給cmdline_p賦值,這個指針是start_kernel里的。
}
struct early_params {
??? const char *arg;??? //字符串指針
??? void (*fn)(char **p); //私有的函數
};
在System.map中
c0027cdc T __early_begin
c0027cdc t __early_early_mem
c0027cdc T __setup_end
c0027ce4 t __early_early_initrd
c0027cec t __early_early_vmalloc
c0027cf4 t __early_early_ecc
c0027cfc t __early_early_nowrite
c0027d04 t __early_early_nocache
c0027d0c t __early_early_cachepolicy
c0027d14 t __early_uart_parent_setup
c0027d1c t __early_jtag_wfi_setup
c0027d24 t __early_system_rev_setup
c0027d2c T __early_end
比如arch/arm/kernel/setup.c中
static void __init early_mem(char **p)
{
static int usermem __initdata = 0;
unsigned long size, start;
/*
* If the user specifies memory size, we
* blow away any automatically generated
* size.
*/
if (usermem == 0) {
usermem = 1;
meminfo.nr_banks = 0;
}
start = PHYS_OFFSET;
size? = memparse(*p, p);
if (**p == '@')
start = memparse(*p + 1, p);
arm_add_memory(start, size);
}
__early_param("mem=", early_mem);
可以發現能夠在這里處理的命令行參數有mem initrd ecc cachepolicy nowrite nocache這六個。
parse_cmdline做了三件事,首先它解析了from所指向的完整的內核參數中關于內存的部分,其次它將沒有解析的部分復制到command_line中,最后它將start_kernel()傳進來的內核參數指針指向command_line。
內核參數中的mem=xxxM@ xxx將會被parse_cmdline解析,并根據結果設置meminfo,而其余部分則被復制到command_line中就是說,能解析的都解析了,不能解析的留下來,等著以后解析,保存在command_line中,可以發現uboot的命令行參數具有最高的優先級,如果指定mem=xxxM@yyy的話,將覆蓋掉標記列表的mem32配置,如果多次使用mem= mem=的話,將得到多個內存bank,通過代碼來看是這樣,沒有驗證過。
最后。通過上面的分析,我們可以自定義通過命令行傳入一個參數設置,這里以uboot向內核傳遞MAC地址為例說明添加過程
?我們使用的系統中的CS8900a沒有外接eeprom,所以在默認的情況,Linux下的CS8900a的驅動使用的是一個偽MAC地址。在單一的系統中,這是沒有問題的,但是當我們在同一個子網中使用或測試多個設備是,就會產生沖突了。所以我們需要能夠方便的改變網卡的MAC地址,而不是將MAC地址硬編碼進內核中,每次修改都得重新編譯內核。
一、添加內核參數的處理函數
?????? 向Linux驅動傳遞參數的方式有兩種,一為在系統啟動的時候由bootloader傳入,還有一種是將驅動編譯成模塊,將參數作為模塊加載的參數傳入。
?????? 這里使用的是由bootloader傳入的方式。內核通過setup接口接受Bootloader傳入的參數。方式如下:
static int __init param_mac_setup(char *str)
{
……
}
__setup("mac=", param_mac_setup);
這樣,當在Bootloader中指定“mac=00:2E:79:38:6D:4E”,系統在加載這個模塊的時候,就會執行相應的param_mac_setup()函數,而傳入給它的參數就是等號后面的物理地址 “00:2E:79:38:6D:4E”。這樣,該函數就可以對它進行相應的處理。
二、將MAC地址參數傳入命令行參數
?????? 為了傳入命令行參數,uboot所作的是:
????????????? char *commandline = getenv ("bootargs");
????????????? setup_commandline_tag (bd, commandline);
?????? 現在想要把MAC地址也加入到命令行參數中,只需要修改配置文件include/configs/smdk2410.h中的CONFIG_BOOTARGS:
#define CONFIG_BOOTARGS?????? "root=ramfs devfs=mount console=ttySA0,9600"
在后面添加mac參數如下:
#define CONFIG_BOOTARGS?????? "root=ramfs devfs=mount console=ttySA0,9600 mac=00:2E:79:38:6D:4E"
這樣就不需要每次在命令行后面手工追加MAC地址參數了?
?
評論
查看更多