Linux内核初始化步骤(二)

参考了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所传参数的接收,解析的机制和方法,另外需要了解下所解析和操作的内容。

猜你喜欢

转载自blog.csdn.net/qq_40788950/article/details/84555494