參考:
u-boot向內核傳遞參數解析:http://blog.chinaunix.net/u3/109474/showart_2205327.html
vivi學習(十七):vivi與Linux kernel的參數傳遞情景分析(下):http://tech.sunplusedu.com/space/post-6910.aspx
【環境】
Linux內核版本:V2.6.20
U-boot版本:V1.1.4
【簡介】
本文描述Bootloader在啓動時如何將內核啓動的參數傳遞給內核,從內核和loader的角度分別描述其原理。
【描述】
在嵌入式設備中,Linux內核一般無法直接啓動,而需要bootloader先初始化硬件環境,完成後加載內核,並將相關參數傳遞給內核。因此,在參數傳遞中,內核和bootloader需要約定兩件事:1:參數表的地址;2:參數的結構體,以及相關賦值內容。
本文後續將詳細描述這兩個方面,其中參數表的地址爲雙方約定,並在編譯時已經確定了;參數結構體,雙方採用結構體struct tag(後續將有詳細介紹)。
而傳遞的參數內容,當前爲止有以下幾部分:
#define ATAG_MEM 0x54410002 //內存參數,一定要傳遞
#define ATAG_VIDEOTEXT 0x54410003
#define ATAG_RAMDISK 0x54410004
#define ATAG_INITRD 0x54410005
#define ATAG_INITRD2 0x54420005
#define ATAG_SERIAL 0x54410006
#define ATAG_REVISION 0x54410007
#define ATAG_VIDEOLFB 0x54410008
#define ATAG_CMDLINE 0x54410009 //命令行參數,用得比較多,如在uboot中設置的bootargs
#define ATAG_ACORN 0x41000101
#define ATAG_MEMCLK 0x41000402
【Linux中內核啓動參數地址的設定以及原理】
<啓動參數地址的設置>
以smdk2410平臺爲例,在arch/arm/mach-s3c2410/mach-smdk2410.c有如下設置:
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
* to SMDK2410 */
/* Maintainer: Jonas Dietsche */
.phys_ram = S3C2410_SDRAM_PA,
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = smdk2410_map_io,
.init_irq = smdk2410_init_irq,
.timer = &s3c24xx_timer,
MACHINE_END
其中的boot_params設置了啓動參數。
<啓動參數地址在啓動時的初始化>
在arch/arm/kernel/setup.c中:
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;【4】
struct machine_desc *mdesc;
char *from = default_command_line;
......
mdesc = setup_machine(machine_arch_type);【1】
......
i
if (mdesc->boot_params) 【2】
tags = phys_to_virt(mdesc->boot_params);
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
parse_tags(tags);【3】
}
......
memcpy(saved_command_line, from, COMMAND_LINE_SIZE);
saved_command_line[COMMAND_LINE_SIZE-1] = '/0';
parse_cmdline(cmdline_p, from);
......
}
【1】
[setup_machine() 函數]
該函數實際上就是在__arch_info_begin與__arch_info_end中找匹配的machine_desc,如下所示:
static struct machine_desc * __init setup_machine(unsigned int nr)
{
extern struct machine_desc __arch_info_begin, __arch_info_end;
struct machine_desc *list;
/*
* locate architecture in the list of supported architectures.
*/
for (list = &__arch_info_begin; list < &__arch_info_end; list++)
if (list->nr == nr)
break;
/*
* If the architecture type is not recognised, then we
* can co nothing...
*/
if (list >= &__arch_info_end) {
printk("Architecture configuration botched (nr %d), unable "
"to continue./n", nr);
while (1);
}
printk("Machine: %s/n", list->name);
return list;
}
對於smdk2410平臺,有如下定義:
//File: arch/arm/mach-smdk2410.c
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
* to SMDK2410 */
/* Maintainer: Jonas Dietsche */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = smdk2410_map_io,
.init_irq = s3c24xx_init_irq,
.init_machine = smdk_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
針對宏MACHINE_START與MACHINE_END的定義如下:
#define MACHINE_START(_type,_name) /
const struct machine_desc __mach_desc_##_type /
__attribute__((__section__(".arch.info"))) = { /
nr: MACH_TYPE_##_type, /
name: _name,
#define MAINTAINER(n)
#define BOOT_MEM(_pram,_pio,_vio) /
phys_ram: _pram, /
phys_io: _pio, /
io_pg_offst: ((_vio)>>18)&0xfffc,
#define BOOT_PARAMS(_params) /
param_offset: _params,
#define VIDEO(_start,_end) /
video_start: _start, /
video_end: _end,
#define DISABLE_PARPORT(_n) /
reserve_lp##_n: 1,
#define BROKEN_HLT /* unused */
#define SOFT_REBOOT /
soft_reboot: 1,
#define FIXUP(_func) /
fixup: _func,
#define MAPIO(_func) /
map_io: _func,
#define INITIRQ(_func) /
init_irq: _func,
#define MACHINE_END /
};
在編譯鏈接腳本中有如下定義:
File: arch/arm/vmlinux-armv.lds.in
.init : { /* Init code and data */
_stext = .;
__init_begin = .;
*(.text.init)
__proc_info_begin = .;
*(.proc.info)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist)
__tagtable_end = .;
因此,實際上在__arch_info_begin與__arch_info_end之間存儲了一個關於smdk2410配置的machine_desc類型的結構體。
在函數setup_machine()中,實際上通過掃描所有在該區域的machine_desc類型的結構體,看是否能找到滿足傳入參數類型的機器類型。
【2】
mdesc->boot_params定義的由loader傳遞過來的參數表的首地址,其爲物理地址。
本來struct tag *tags = (struct tag *)&init_tags爲默認的tag參數表地址,但如果mdesc->boot_params有定義,則表示使用由loader傳遞過來的參數表地址,tag重新賦值:tags = phys_to_virt(mdesc->boot_params);
【3】
parse_tags()函數是從首地址開始,掃描所有有效地tag,並執行相應的操作。
static void __init parse_tags(struct tag *t)
{
for (; t->hdr.tag != ATAG_NONE; t = tag_next(t))
if (!parse_tag(t))
printk(KERN_WARNING
"Ignoring unrecognised tag 0x%08x/n",
t->hdr.tag);
}
//看來所有的tag都以鏈表的形式串聯了起來,最後一個tag的類型要爲ATAG_NONE;
//對找到的每個tag,調用函數parse_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++)【5】
if (tag->hdr.tag == t->tag) {
t->parse(tag);
break;
}
return t < &__tagtable_end;
}
//該函數查找存儲在__tagtable_begin中的所有類型的tag表,當找到有效地tag後,調用針對該tag的處理函數;
【4】
結構體tag的定義:
對於arm構架,其定義爲:
File: include/asm-arm
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;
};
其中:
struct tag_header {
u32 size;
u32 tag;
};
【5】
關於tagtable
在arch/arm/kernel/setup.c中,有很多諸如以下的定義:
__tagtable(ATAG_REVISION, parse_tag_revision);
__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
__tagtable(ATAG_SERIAL, parse_tag_serialnr);
......
宏__tagtable的定義如下:
#define __tagtable(tag, fn) /
static struct tagtable __tagtable_##fn __tag = { tag, fn }
#define __tag __attribute_used__ __attribute__((__section__(".taglist.init")))
因此,以上的__tagtable()的調用,相當於向.taglist.init段中插入各個tag的描述。
在arch/arm/kernel/vmlinux.lds.S#L38中,有如下:
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
因此,在如上【5】中,相當於在__tagtable_begin中掃描各個有效地tagtable項。
附:在/include/asm-arm/setup.h中,定義了所有的tag類型如下:
struct tag_header {
__u32 size;
__u32 tag;
};
#define ATAG_NONE 0x00000000
#define ATAG_CORE 0x54410001
#define ATAG_MEM 0x54410002
#define ATAG_VIDEOTEXT 0x54410003
#define ATAG_RAMDISK 0x54410004
#define ATAG_INITRD 0x54410005
#define ATAG_INITRD2 0x54420005
#define ATAG_SERIAL 0x54410006
#define ATAG_REVISION 0x54410007
#define ATAG_VIDEOLFB 0x54410008
#define ATAG_CMDLINE 0x54410009
#define ATAG_ACORN 0x41000101
#define ATAG_MEMCLK 0x41000402
【Uboot中內核啓動參數地址的設定以及原理】
<參數地址的確定>
以smdk2410爲例,在smdk2410.c中的函數board_init()函數中,有:
int board_init (void)
{
......
/* adress of boot parameters */
gd->bd->bi_boot_params = 0x30000100;
......
}
此處即爲傳遞參數的地址,改值需要與如上所述的MACHINE_START中的boot_params設置一致。
<啓動參數傳遞的原理>
以arm平臺爲例,在armlinux.c中的函數,有:
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
......
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
......
#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);
#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);
#endif
/* we assume that the kernel is in place */
printf ("/nStarting kernel .../n/n");
......
}
配置定義一般定於:
u-boot/u-boot-1.1.4/include/configs中各個board的文件中