在do_bootm_linux函数中,用到函数char *commandline = getenv ("bootargs") 来从环境变量中获取命令参数,下面就来分析一下参数的传递过程:
首先要讲一个重要的结构体:
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem_range mem_range;
struct tag_cmdline cmdline;
struct tag_clock clock;
struct tag_ethernet ethernet;
} u;
};
在这个结构体重集合了传递的绝大多数命令参数。
函数do_bootm_linux中的部分代码:
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
#ifdef CONFIG_CMDLINE_TAG//如果定义了这个宏,将会把bootargs传给内核。
char *commandline = getenv ("bootargs");//获取bootargs环境变量。
#endif
theKernel = (void (*)(int, int, uint))images->ep;//镜像的入口地址处也就是需要执行的函数入口
s = getenv ("machid");
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment/n", machid);
}
show_boot_progress (15);
debug ("## Transferring control to Linux (at address %08lx) .../n",
(ulong) theKernel);
//下面将会初始化传给内核的标签(tag),这些标签里包含了必要的环境变量和参数。
#if defined (CONFIG_SETUP_MEMORY_TAGS) || /
defined (CONFIG_CMDLINE_TAG) || /
defined (CONFIG_INITRD_TAG) || /
defined (CONFIG_SERIAL_TAG) || /
defined (CONFIG_REVISION_TAG)
setup_start_tag (bd);
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);//内存有关的定义
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);//将bootargs传给tag
#endif
#ifdef CONFIG_INITRD_TAG
if (images->rd_start && images->rd_end)//没有ramdisk所以不会执行这个路径
setup_initrd_tag (bd, images->rd_start, images->rd_end);
#endif
setup_end_tag (bd);
#endif
...
}
*********************************************************************************************
首先看下函数setup_start_tag,
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;
//在board_init 中有一句:gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100;这个设置参数存放的位置: #define PHYS_SDRAM_1 CSD0_DDR_BASE_ADDR -->> #define CSD0_DDR_BASE_ADDR MMDC0_ARB_BASE_ADDR -->> #define MMDC0_ARB_BASE_ADDR 0x10000000
//初始化(struct tag *)型的全局变量params为bd->bi_boot_params的地址,之后的setup tags相关函数如下面的setup_memory_tags就把其它tag的数据放在此地址的偏移地址上。
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
...
}
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x54410001
tag方式传参
(1)struct tag,tag是一个数据结构,在uboot和linux kernel中都有定义tag数据机构,而且定义是一样的。
(2)tag_header和tag_xxx。tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理。
(3)tag_start与tag_end。kernel接收到的传参是若干个tag构成的,这些tag由tag_start起始,到tag_end结束。
*********************************************************************************************
RAM 相关参数在 bootm.c 中的函数 setup_memory_tags 中初始化:
static void setup_memory_tags (bd_t *bd)
{
int i;
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { //:#define CONFIG_NR_DRAM_BANKS 1
params->hdr.tag = ATAG_MEM; //#define ATAG_MEM 0x54410002
params->hdr.size = tag_size(tag_mem32);
params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size; //这些变量都是在struct bd_info 中
params = tag_next(params); //初始化内存相关tag
}
}
*********************************************************************************************
调用getenv ("bootargs")函数获得命令行参数并让commandline指向它,然后调用setup_commandline_tag函数将命令行参数放到tag参数例表
static void setup_commandline_tag (bd_t *bd, char *commandline)
{ // commandline就是需要传递的bootargs
char *p;
if (!commandline)
return;
/* eat leading white space */
for (p = commandline; *p == ' '; p++);
/* skip non-existent command lines so the kernel will still
* use its default command line.
*/
if (*p == '\0')
return;
params->hdr.tag = ATAG_CMDLINE; //#define ATAG_CMDLINE 0x54410009
params->hdr.size =
(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline, p);
params = tag_next (params);
}
当内核启动的时候就会去读这些参数。
*********************************************************************************************
以下是内核部分
*********************************************************************************************
执行到setup_end_tag(bd)的时候,uboot向内核传参结束,下面就开始是内核来接受这些参数,并进行解析,以用来接下来的启动过程。
b start_kernel -->> asmlinkage void __init start_kernel(void) -->> setup_arch(&command_line) -->> mdesc = setup_machine_tags(machine_arch_type) -->> parse_tags(tags)//开始解析所有的参数
void __init setup_arch(char **cmdline_p)
{
/*__atags_pointer是uboot传递的参数地址*/
mdesc = setup_machine_fdt(__atags_pointer);
/*由于参数非设备树结构,返回NULL*/
if (!mdesc)
mdesc = setup_machine_tags(machine_arch_type);
...
init_mm.start_code = (unsigned long) _text; // 记录kernel代码段的虚拟启始地址
init_mm.end_code = (unsigned long) _etext; // 记录kernel代码段的虚拟结束地址
init_mm.end_data = (unsigned long) _edata; // 记录kernel数据段的虚拟结束地址
init_mm.brk = (unsigned long) _end; // brk指向kernel全部段的虚拟结束地址
*cmdline_p = boot_command_line; // 将boot_command_line的地址保存到start_kernel局部指针变量cmdline_p中
...
}
nr= machid=MACH_TYPE_MX6Q_SABRESD=3980是uboot传递过来的,内核中的MACH_TYPE_MX6Q_SABRESD
在/include/generated/mach-types.h中定义,这个文件是动态产生的。
/*
* This was automagically generated from arch/arm/tools/mach-types!
* Do NOT edit
*/
#ifndef __ASM_ARM_MACH_TYPE_H
#define __ASM_ARM_MACH_TYPE_H
#ifndef __ASSEMBLY__
/* The type of machine we're running on */
extern unsigned int __machine_arch_type;
#endif
#define MACH_TYPE_MX6Q_SABRESD 3980
#endif
static struct machine_desc * __init setup_machine_tags(unsigned int nr)
{
init_tags.mem.start = PHYS_OFFSET; //#define PHYS_OFFSET PLAT_PHYS_OFFSET -->> define PLAT_PHYS_OFFSET MX6_PHYS_OFFSET -->> #define MX6_PHYS_OFFSET UL(0x10000000) tag在内存中开始的地址
/* 在受支持的机器列表中定位 */
for_each_machine_desc(p)
/*#define for_each_machine_desc(p) \
for (p = __arch_info_begin; p < __arch_info_end; p++)
__arch_info_begin是machine_desc存放的位置,通过属性定义,*/
if (nr == p->nr) {
printk("Machine: %s\n", p->name);
//内核参数打印:Machine: Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board
mdesc = p;
break;
}
if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params) {
tags = phys_to_virt(mdesc->boot_params);
}
//这里根据情况判断tags接收uboot参数的来源,if中说明是uboot传递来的参数,elseif说明是由内核部分写死的
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);
save_atags(tags);
parse_tags(tags);
}
/*全局变量meminfo在这时候还没有被初始化,其用于指示物理内存bank个数的成员nr_banks肯定为0,save_atags将把tags里的内容拷贝给全局变量atags_copy*/
/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
return mdesc;
}
*********************************************************************************************
#define for_each_machine_desc(p) \
for (p = __arch_info_begin; p < __arch_info_end; p++)
__arch_info_begin是machine_desc存放的位置,通过属性定义,
在setup_machine_tags函数一开始首先定义struct tag *型指针变量tags:
struct tag *tags = (struct tag *)&init_tags;
tags就是内核接收uboot参数的。
static struct init_tags {
struct tag_header hdr1;
struct tag_core core;
struct tag_header hdr2;
struct tag_mem32 mem;
struct tag_header hdr3;
} init_tags __initdata = {
{ tag_size(tag_core), ATAG_CORE },
{ 1, PAGE_SIZE, 0xff },
{ tag_size(tag_mem32), ATAG_MEM },
{ MEM_SIZE },
{ 0, ATAG_NONE }
};
__atags_pointer定义是在文件arch/arm/kernel/head-common.s
.long __atags_pointer @ r6
在内核代码源头stext运行前,由arm寄存器R6保存要传递给内核的参数的地址,当stext运行到子程序 __switch_data时,定义变量__atags_pointer保存这个地址,即__atags_pointer保存了uboot要传递给内核的参数的地址,所以这里让tags获取__atags_pointer的转换后的虚拟地址,即可访问。
有的时候,可能不需要从uboot传递参数到内核,也就是说这些参数写死在内核里而不是在uboot里,那么就可以写死,写死是写死在machine_desc变量的boot_params成员,可以把地址值赋给这个成员,即可访问。
*********************************************************************************************
接下来是函数parse_tags(tags):
parse_tags函数的实现,必须要搞懂tags指针变量里面的内容是什么了,tags指针变量实际上指向了多个的struct tags型变量,观察struct tags结构体即可发现,它是一个struct tags_header加一个联合的结构。再看parse_tags的实现,它就是对每一个它指向的struct tags型变量调用函数parse_tag,这个函数实际解析struct tags型变量。
static void __init parse_tags(const struct tag *t)
{
for (; t->hdr.size; t = tag_next(t))
if (!parse_tag(t))
printk(KERN_WARNING
"Ignoring unrecognised tag 0x%08x\n",
t->hdr.tag);
}
在if条件语句中调用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++)
if (tag->hdr.tag == t->tag) {
t->parse(tag);
break;
}
return t < &__tagtable_end;
}
*********************************************************************************************
extern struct tagtable __tagtable_begin, __tagtable_end;”,发现这两个是在链接脚本vmlinux.lds.S中定义的,并且是卡住某一代码段便于给C函数调用。
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
(arch/include/asm/setup.h)
#define __tag __used __attribute__((__section__(".taglist.init")))
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn }
以__tagtable(tag, fn)形式定义的宏,实际是在".taglist.init"段中,创建struct tagtable的静态变量,并赋初值给这个变量,赋的值就是这个宏的两个参数tag和fn.
所以,parse_tag函数对传递进来的参数,利用__tagtable_begin和__tagtable_end,使用所有定义的__tagtable(tag, fn)对其进行遍历,一旦发现__tagtable(tag, fn)的tag和传递进来的参数的参数头的tag(即struct tags_header的tag成员)一样,就用该__tagtable(tag, fn)的fn即解析函数进行解析。
在内核中用__tagtable(tag,fn)定义的函数:
Init.c (arch\arm\mm):__tagtable(ATAG_INITRD, parse_tag_initrd);
Init.c (arch\arm\mm):__tagtable(ATAG_INITRD2, parse_tag_initrd2);
Setup.c (arch\arm\kernel):__tagtable(ATAG_CORE, parse_tag_core);
Setup.c (arch\arm\kernel):__tagtable(ATAG_MEM, parse_tag_mem32);
Setup.c (arch\arm\kernel):__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
Setup.c (arch\arm\kernel):__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);
Setup.c (arch\arm\kernel):__tagtable(ATAG_SERIAL, parse_tag_serialnr);
Setup.c (arch\arm\kernel):__tagtable(ATAG_REVISION, parse_tag_revision);
Setup.c (arch\arm\kernel):__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
下面就挑两个函数:
static int __init parse_tag_mem32(const struct tag *tag)
{
return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
}
__tagtable(ATAG_MEM, parse_tag_mem32);
这个函数是初始化meminfo,让设备了解物理内存的情况,它将调用函数arm_add_memory,参数就是uboot传递的相应参数的mem结构,包括start成员和size成员即物理内存起始地址和内存大小,arm_add_memory这个函数把这两个参数写进meminfo的bank中,并更新benk个数值nr_banks。对于很多小型arm嵌入式应用,一般只有一个物理内存,也就只调用该函数一次。重中之重的meminfo就是在这里初始化的。
第二个函数:
static int __init parse_tag_cmdline(const struct tag *tag)
{
#if defined(CONFIG_CMDLINE_EXTEND)
strlcat(default_command_line, " ", COMMAND_LINE_SIZE);
strlcat(default_command_line, tag->u.cmdline.cmdline,
COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
pr_warning("Ignoring tag cmdline (using the default kernel command line)\n");
#else
strlcpy(default_command_line, tag->u.cmdline.cmdline,
COMMAND_LINE_SIZE);
#endif
return 0;
}
__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
它把uboot传递的“命令行参数”赋给静态全局变量default_command_line.
/*Kernel command line: console=ttymxc1,115200 video=mxcfb0:dev=lcd,AT070-WVGA,if=RGB24,bpp=32 video=mxcfb1:dev=hdmi,1920x1080M@60,if=RGB24,bpp=32 ldb=sin0 audio_codec=wm8960-24M video=mxcfb2:off calibrate=Y fec_mac=1E:ED:19:27:1A:B3 ip=none root=/dev/mmcblk0p1 rootwait*/
*********************************************************************************************
重新回到函数setup_machine_tags中,最后的代码:
在setup_machine_tags函数一开始的地方有这样的定义:
char *from = default_command_line;
/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
此句是将default_command_line复制给boot_command_line,回到setup_arch函数:
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
在setup.c文件开始的地方: cmd_line被定义全局变量,
static char __initdata cmd_line[COMMAND_LINE_SIZE];
所以到函数parse_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);
//boot_command_line的值又给了tmp_cmdline
parse_early_options(tmp_cmdline);
done = 1;
}
接下来追踪到:
void __init parse_early_options(char *cmdline)
{
parse_args("early options", cmdline, NULL, 0, do_early_param);
}
现在要关注的是parse_args函数中的参数:do_early_param:
static int __init do_early_param(char *param, char *val)
{
const struct obs_kernel_param *p;
for (p = __setup_start; p < __setup_end; p++)
...
}
在for循环语句中:__setup_start和 __setup_end,它们的定义是在链接脚本vmlinux.lds.S(include/asm-generic)
VMLINUX_SYMBOL(__setup_start) = .; \
*(.init.setup) \
VMLINUX_SYMBOL(__setup_end) = .;
这个定义和刚刚讲的__tagtable那个类似。
(include/linux/init.h)
#define __setup_param(str, unique_id, fn, early) \
static const char __setup_str_##unique_id[] __initconst \
__aligned(1) = str; \
static struct obs_kernel_param __setup_##unique_id \
__used __section(.init.setup) \
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_##unique_id, fn, early }
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
/* NOTE: fn is as per module_param, not __setup! Emits warning if fn
* returns non-zero. */
#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)
这两个宏都会调用__setup_param,在这个宏里面有个结构体:
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
__setup与early_param的区别就是用early标记。
early_param相关函数:
Main.c (init):early_param("debug", debug_kernel);
Main.c (init):early_param("quiet", quiet_kernel);
Main.c (init):early_param("loglevel", loglevel);
函数的调用过程:parse_early_param-->parse_early_options-->parse_args-->do_early_param
*********************************************************************************************