参考了http://www.360doc.com/content/16/0626/14/19351147_570866376.shtml和
https://blog.csdn.net/qing_ping/article/details/17351541的内容
setup_arch()是start_kernel阶段的函数,它是体系结构相关的,具体编译哪个体系的setup_arch()函数,由顶层Makefile中的ARCH变量决定:它首先通过检测出来的处理器类型进行处理器内核的初始化,然后通过bootmem_init()函数根据系统定义的meminfo结构进行内存结构的初始化,最后调用paging_init()开启MMU,创建内核页表,映射所有的物理内存和IO空间。
__init setup_arch(char **cmdline_p)
{
/*内核通过machine——desc结构体来控制系统体系结构相关部分的初始化。
machine_desc结构体通过MACHINE_START宏来初始化,在代码中,通过
调用setup_machine_fdt来获取。*/
struct machine_desc *mdesc;
/*首先从arm寄存器里面取得cpu ID,然后调用lookup_processor_type来取得proc_info_list
这个结构体。取得proc_info_list以后,将里面的内容一个个赋值给相应的全局变量,然后将
cpu的信息打印出来。然后它会从arm寄存器里面获得cache的信息,并将cache的信息打印出来*/
setup_processor();
/*__atags_pointer,定义在汇编文件arch/arm/kernel/head-common.S中,由arm寄存器r2保存要
传递给内核的参数的地址,定义变量__atags_pointer保存这个地址,即__atags_pointer保存了
uboot要传递给内核参数的地址*/
mdesc = setup_machine_fdt(__atags_pointer);
if (!mdesc)
mdesc = setup_machine_tags(machine_arch_type);
machine_desc = mdesc;
machine_name = mdesc->name;
/*分配可以用于DMA的区域,这部分区域不通过MMU进行映射*/
#ifdef CONFIG_ZONE_DMA
if (mdesc->dma_zone_size) {
extern unsigned long arm_dma_zone_size;
arm_dma_zone_size = mdesc->dma_zone_size;
}
#endif
/*通过struct machine_desc中的soft_reboot数据来设置重启类型*/
if (mdesc->restart_mode)
reboot_setup(&mdesc->restart_mode);
/*通过链接脚本中得到的Linux代码位置数据来初始化一个mm_struct结构体init_mm中的
部分数据,每一个任务都有一个mm_struct结构以管理内存空间,init_mm是内核自身的
mm_struct*/
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
/* populate cmd_line too for later use, preserving boot_command_line */
/*同时填充cmd_line以备后用,保护boot_command_line数据*/
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
/*处理在struct obs_kernel_param中定义的early启动参数(主要是内存配置部分的参数)
其中就分析了mem=size@start参数初始化了struct meminfo meminfo;
同时如果有vmalloc=size参数也会初始化vmalloc_min;
这里需要注意的是内核的cmdline中参数按照其被需要的先后,分为early和非early的。
include/linux/init.h;
struct obs_kernel_param{
const char *str;//在cmdline中相应参数名
int(*setup_func)(char*);//对于此参数的专用处理函数
int early;//是否为早期需要处理的函数
};
两种不同的参数在内核中用了不同的宏来定义:
early:#define early_param(star,fn) \
__setup_param(str,fn,fn,1)
非early:#define __setup(str,fn) \
__setup_param(str,fn,fn,0)
*/
parse_early_param();
sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL);
sanity_check_meminfo();
arm_memblock_init(&meminfo, mdesc);
paging_init(mdesc);
request_standard_resources(mdesc);
if (mdesc->restart)
arm_pm_restart = mdesc->restart;
unflatten_device_tree();
#ifdef CONFIG_SMP
if (is_smp())
smp_init_cpus();
#endif
/*用于内核崩溃时的保留内核。此功能通过内核command line 参数中的“crashkernel=”保留下
来的内存用于主内存崩溃时获取内核信息的导出*/
reserve_crashkernel();
/*初始化arm内部的TCM(紧耦合内存)*/
tcm_init();
#ifdef CONFIG_MULTI_IRQ_HANDLER
handle_arch_irq = mdesc->handle_irq;
#endif
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
if (mdesc->init_early)
mdesc->init_early();
}
这个函数的主要作用是uboot向内核传递参数,下面我们就来看看这个过程。先看看uboot给内核传递的的参数是什么样的东西,在arch/arm/include/asm/setup.h文件中的struct tag 结构体:
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;
};
uboot传递给内核的参数,都是一个个对设备参数的描述,用于内核进行相应的初始化,下面一步步看内核是怎么接收uboot参数的,在setup_machine_tags函数中首先定义struct tag*型指针变量tags:
struct tag *tags = (struct tag *)&init_tags;
tags就是内核uboot参数的东西!
init_tags是个全局静态变量,在/arch/arm/kernel/setup.c文件中定义如下:
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 }
};
在定义结构体init_tags的同时声明了静态全局变量init_tags,继续往下看:
/*
* locate machine in the list of supported machines.
*/
for_each_machine_desc(p)
if (nr == p->nr) {
printk("Machine: %s\n", p->name);
mdesc = p;
break;
}
对应于启动过程中打印出的这条语句:
[ 0.000000] Machine: DaVinci DA850/OMAP-L138/AM18x EVM
再往下看:
if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->atag_offset)
tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);
这里根据情况判断tags接收uboot参数的来源,if中说明来源是uboot传递,else if 说明是由内核部分的代码(即代码写死,不是从uboot);
先看看这个__atags_pointer是什么:
__atasg_pointer,定义在汇编文件arch/arm/kernel/head-common.S的__switch_data子程序,在内核代码源头.stext运行前,由arm寄存器r2保存要传递给内核的参数的指针,当stext运行到子程序__switch_data时,定义变量__atags_pointer保存这个地址,即__atags_pointer保存了uboot要传递给内核的参数的地址,所以这里让tags获取__atags_pointer转换后的虚拟地址,即可访问。
再接着看内核代码如何使用这些参数,如下:
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
#endif
if (tags->hdr.tag != ATAG_CORE) {
#if defined(CONFIG_OF)
/*
* If CONFIG_OF is set, then assume this is a reasonably
* modern system that should pass boot parameters
*/
early_print("Warning: Neither atags nor dtb found\n");
#endif
tags = (struct tag *)&init_tags;
}
结合本文最前面的struct tags结构体,它的第一个成员如下:
struct tag_header hdr;
struct tag_header hdr的成员tag,如果不等于宏ATAG_CORE的值,说明是旧式的参数,需要转换成新格式的参数,所以调用convert_to_tag_list;如果转换后依然是旧格式的,那么就设法使用这个参数了,改为使用默认参数,就是本文开始时描述的哪个init_tags静态全局变量。
因此这部分内容基本不需要看,因为一个正常的uboot是不会传递旧格式的参数。
后面的fixup部分,用于内核固定写死的meminfo,而不是由uboot传递参数配置memeinfo,很少有使用fixup成员写死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_tags:
观察parse_tags函数的实现,先要搞懂tags指针变量里面的内容是什么,tags指针变量实际指向了多个的struct tags型变量,观察struct tags结构体即可发现,它是一个struct tags_header加一个联合的结构,再看parse_tags的实现,它就是对每个它指向的struct tags型变量调用函数parse_tag,这个函数实际解析struct tags型变量,下面是它的实现:
/*
* Scan the tag table for this tag, and call its parse function.
* The tag table is built by the linker from all the __tagtable
* declarations.
*/
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;”,这是在链接脚本/arch/arm/kernel/vmlinux.lds中定义的,并且是卡住某一代码段便于给C函数调用:
首先看这两个变量在哪里定义,卡住了哪部分内容:
可见是卡住了“.taglist.init”段的全部内容,下面再来分析这个段里面包含了哪些东西,下面的内容定义在arch/arm/include/asm/setup.h文件中:
#define __tag __used __attribute__((__section__(".taglist.init")))
#define __tagtable(tag, fn) \
static const struct tagtable __tagtable_##fn __tag = { tag, fn }
第一行的意思是:宏__tag,定义为__used attribute((section(".taglist.init")));
第二三行的意思是:定义宏 __tagtable(tag, fn)为static const struct tagtable _tagtable##fn _tag = { tag, fn },意思是:在".taglist.init"段中,创建struct tagtagble的静态变量__tagtable##fn(fn是什么由参数指定,后面的__tag起变量描述符的作用,它真正指定了这个静态变量是在“.taglist.inti"段中链接),并赋初值,赋的值就是宏函数定义时的参数tag和fn.
说白了,就是以__tagtable(tag,fn)形式定义的宏,实际是在”.taglist.init”段中,创建struct tagtable的静态变量,并赋初值给这个变量,赋的值就是这个宏的两个参数tag和fn。
在arch/arm/kernel/setup.c、arch/arm/mm/init.c文件中,定义了很多__tagtable(XXX,XXX)这样的宏,这些宏就是解析uboot传递给内核的参数用的(当然不仅它们解析,后面还有别的代码解析),现在回到parse_tag函数,应该很好理解了,它对传递进来的参数,利用__tagtable_begin和__tagtable_end,使用所有的__tagtable(tag,fn)对其进行遍历,一旦发现__tagtable(tag,fn)的tag和传递进来的参数的参数头tag(即struct tags_header的tag成员)一样,就用该__tagtable(tag,fn)的fn即解析函数进行解析,我们的omap138(davinci)设备的实现一共定义了9个这样的__tagtable(tag,fn),列举如下:
下面描述两个比较重要的函数:
parse_tag_mem32:
这个是初始化meminfo即让内核了解设备的物理内存情况的,它将调用函数arm_add_memory,参数就是uboot传递的相应参数的mem结构,包括start成员和size成员即物理内存起始地址和内存大小,arm_add_memory把两个参数写进meminfo的bank中,并更新bank个数值nr_banks.
static int __init parse_tag_mem32(const struct tag *tag)
{
return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
}
parse_tag_cmdline
它把uboot传递的“命令行参数”赋给静态全局变量default_command_line,这个变量的作用可以从下面的分析中看到,先看看这里的davinci设备的情况:
console=ttyS1,115200n8 mem=32M@0xc0000000 mem=192M@0xc4000000 eth=02:02:30:30:97:81 da850-panel=TL070A da850-emac=mii root=ubi0_0 ubi.mtd=4,2048 rootfstype=ubifs
再来看看这个函数的内部所调用的函数:
strlcpy(default_command_line, tag->u.cmdline.cmdline,
COMMAND_LINE_SIZE);
现在就明白这个default_command_line的作用了,这里只是将从uboot传来的cmdline拷贝到这个变量中去,后面还有其他代码进一步解析它。
总之,对于uboot向内核传递参数,需要理解的 一个是内核对uboot所传参数的接收,解析的机制和方法,另外需要了解下所解析和操作的内容。