Linux 内核启动流程

转载自 http://wenku.baidu.com/link?url=KpOdULJu1CxP1swqRs_Szoyg5r_8rje4N08o4QtB5L9QlPjWesTYlrTPgkxPOriFtsmsqCyg-gWMVOkLjgYN640zsk7vGlN24tO5tkJcj8K

有修改


ARM Linux 内核在Linux-3.x 内核有了很大的变化,对一些新的平台的支持取消了传统的设备文件而用设备树取代,这里以飞思卡尔的imx6sx-sdb设备树识别为例说明Linux 是如何识别设备树的。这里采用的linux内核版本为3.14.28。
1、 设备树文件
参考文件 arch/arm/boot/dts/imx6sx-sdb.dts,在线可以看 http://lxr.oss.org.cn/source/arch/arm/boot/dts/imx6sx-sdb.dts?v=3.17
编译设备树文件,内核顶层目录下执行如下命令可以编译设备树文件:
$ make dtbs ARCH=arm
(可能因为我使用yocto进行编译的,所以我执行时候报错)

编译后生成文件为 imx6sx-sdb.dtb, 文件是使用大端字节序存储,可用下面命令看其二进制内容:
$ hexdump imx6sx-sdb.dtb -C

在uboot 引导内核之前,会将设备树文件加载到内存中,以备Linux 内核使用,这里就不详细说明了
2、 Linux 内核启动
Linux 内核启动分几个阶段:
1) Linux 内核自解压
2) Linux 内核初始化----汇编
3) Linux 内核初始化----C
这里从第三阶段开始说明,分析这个阶段,主要是查看函数start_kernel,在start_kernel 中有几个函数这里重点分析:
2.1 setup_arch
arch/arm/kernel/setup.c

void __init setup_arch(char **cmdline_p)
{
	const struct machine_desc *mdesc;
	…
	mdesc = setup_machine_fdt(__atags_pointer);
	if (!mdesc)
	mdesc = setup_machine_tags(__atags__pointer, __machine_arch_type);
	…
}

arch/arm/kernel/devtree.c

/**
 * setup_machine_fdt - Machine setup when an dtb was passed to the kernel
 * @dt_phys: physical address of dt blob
 *
 * If a dtb was passed to the kernel in r2, then use it to choose the
 * correct machine_desc and to setup the system.
 */
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
	const struct machine_desc *mdesc, *mdesc_best = NULL;

#ifdef CONFIG_ARCH_MULTIPLATFORM
	DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
	MACHINE_END

	mdesc_best = &__mach_desc_GENERIC_DT;
#endif

	if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys)))
		return NULL;

	mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

	if (!mdesc) {
		const char *prop;
		long size;
		unsigned long dt_root;

		early_print("\nError: unrecognized/unsupported "
			    "device tree compatible list:\n[ ");

		dt_root = of_get_flat_dt_root();
		prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
		while (size > 0) {
			early_print("'%s' ", prop);
			size -= strlen(prop) + 1;
			prop += strlen(prop) + 1;
		}
		early_print("]\n\n");

		dump_machine_table(); /* does not return */
	}

	/* Change machine number to match the mdesc we're using */
	__machine_arch_type = mdesc->nr;

	return mdesc;
}

setup_machine_fdt 函数是用来识别设备树,看注释,bootloader 如果将一个设备树文件加载到内存中,其通过r2 寄存器将设备树的物理地址传递到Linux 内核中,Linux 内核来选择正确的机器且对其进行设置

函数中需要重点分析的函数有: of_flat_dt_match_machine 和 of_get_flat_dt_root

drivers/of/fdt.c

const void * __init of_flat_dt_match_machine(const void *default_match
		const void * (*get_next_compat)(const char * const **))
{
	const void *data = NULL;
	const void *best_data = default_match;
	const char *const *compat;
	unsigned long dt_root;
	unsigned int best_score = ~1, score = 0;

	//找到设备树中的根节点(开始结点)
	dt_root = of_get_flat_dt_root();
	/*
	* get_next_compat = arch_get_next_mach
	* 内核中可以同时支持多个平台,这个函数用来获得下一个平台的
	* dt_compat 属性,结合上边函数克制data 内容
	*/
	while((data = get_next_compat(&compat))) {
		/*
		* of_flat_dt_match 用来匹配设备树中内容和内核所支持平台是否匹配
		*/
		score = of_flat_dt_match(dt_root, compat);
		if (score > 0 && score < best_score) {
			best_data = data;
			best_score = score;
		}
	}
	if (!best_data) {
		const char *prop;
		long size;
		/*
		* 设备树中内容和内核所支持平台没有匹配则打印出错提示
		*/
		pr_err("\n unrecognized device tree list: \n");
		prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
		if (prop) {
			while (size > 0) {
				printk("'%s'", prop);
				size -= strlen(prop) + 1;
				prop += strlen(prop) + 1;
			}
			printk("]\n\n");
			return NULL;
		}
	}
}

函数中会调用函数 of_get_flat_dt_root 和 get_next_compat , of_get_flat_dt_root 用来从设备树文件中找到根节点, get_next_compat 按照上下文这个函数等于 arch_get_next_mach , 下边会重点分析这两个函数。


补充:设备树相关宏和结构体:
结点相关宏:

include/linux/of_fdt.h

OF_DT_HEADER 0xd00dfeed 标记
OF_DT_BEGIN_NODE 0x1 开始结点
OF_DT_END_NODE 0x2 结束结点
OF_DT_PROP 0x3 Property: name off, size, *content资源
OF_DT_NOP 0x4 NOP
OF_DT_END 0x9 结束
OF_DT_VERSION 0x10 版本

include/linux/of_fdt.h

struct boot_param_header {
	__be32 magic; // OF_DT_HEADER 幻数
	__be32 totalsize; // 设备树总体大小
	__be32 off_dt_struct; // structure 偏移
	__be32 off_dt_strings; // strings 偏移
	__be32 off_mem_rsvmap; // 内存预留映射表偏移
	__be32 version; // 格式版本
	__be32 last_comp_version; // 最后兼容版本
	__be32 boot_cpuid_phys; // 我们要启动的CPU 的ID
	__be32 dt_strings_size; // 设备树strings 块大小
	__be32 dt_struct_size; // 设备树structure 块大小
};

drivers/of/fdt.c    看3.17的内核实现又不一样了

unsigned long __inti of_get_flat_dt_root(void)
{
	unsigned long p = ((unsigned long)initial_boot_params) +
		be32_to_cpu(initial_boot_params->off_dt_struct);
	/*
	* 结合上述内容p = 设备树起始地址 + 0x0038
	* 设备树文件按16 进制显示:
	* 0000030 0000 0000 0000 0000 0000 0100 0000 0000
	* 0x38 = 0x00000001
	*/
	/*
	* 跳过所有无效数据
	*/
	while(be32_to_cpup((__be32 *)p) == OF_DT_NOP)
		p += 4;
	/*
	* p = OF_DT_BEGIN_NODE 表示找到开始节点,也就是设备树有效数据的开始
	*/
	BUG_ON(be32_to_cpup((__be32 *)p) != OF_DT_BEGIN_NODE);
	p += 4;
	/*
	* 数据对齐
	*/
	return ALIGN(p + strlen(char *p) + 1, 4);
}

arch/arm/kernel/devtree.c
static const void * __init arch_get_next_mach(const char *const **match)
{
	static const struct machine_desc *mdesc = __arch_info_begin;
	if (m > __arch_info_end)
		return NULL;
	mdesc++;
	*match = m->dt_compat;
	return m;
}

内核中又一个段叫.arch.info.init,内核在编译的时候会将所有支持的平台的信息链接到这个段中即machine_desc,其中 __arch_info_begin 表示这个段的开始 __arch_info_end 表示这个段的结束。
连接脚本参考arch/arm/kernel/vmlinux.lds 中有如下内容:

.init.arch.info: {
	__arch_info_begin = .;
	*(.arch.info.init)
	__arch_info_end = .;
}

machine_desc 注册参考arch/arm/mach-imx/mach-imx6sx.c 中

static void __init imx6sx_init_machine(void)
{
	struct device *parent;

	mxc_arch_reset_init_dt();

	parent = imx_soc_device_init();
	if (parent == NULL)
		pr_warn("failed to initialize soc device\n");

	of_platform_populate(NULL, of_default_bus_match_table,
					imx6sx_auxdata_lookup, parent);

	imx6sx_enet_init();
	imx_anatop_init();
	imx6sx_pm_init();
	imx6sx_qos_init();
}

static const char *imx6sx_dt_compat[] __initdata = {
	"fsl,imx6sx",
	NULL,
};

DT_MACHINE_START(IMX6SX, "Freescale i.MX6 SoloX (Device Tree)")
	.map_io		= imx6sx_map_io,
	.init_irq	= imx6sx_init_irq,
	.init_machine	= imx6sx_init_machine,
	.init_late	= imx6sx_init_late,
	.dt_compat	= imx6sx_dt_compat,
	.restart	= mxc_restart,
MACHINE_END

DT_MACHINE_START 的含义就是定义结构体 machine_desc 并初始化且标识链接到.arch.info.init 段中,定义如下:
arch/arm/include/asm/mach/arch.h

#define DT_MACHINE_START(_name, _namestr)		\
static const struct machine_desc __mach_desc_##_name	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= ~0,				\
	.name		= _namestr,

上述内容主要是通过读取设备树信息,检测当前使用内核是否支持本平台,如果支持则做相应的初始化,这里主要分析平台识别过程。
2.2 rest_init
reset_init 函数中创建了几个内核线程,其中kernel_init 使我们要重点分析的。其中 kernel_init ----> kernel_init_freeable -----> do_basic_setup ----> do_initcalls
init\main.c

static void __init do_initcalls(void)
{
	int level;

	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
		do_initcall_level(level);
}

init\main.c

static initcall_t *initcall_levels[] __initdata = {
	__initcall0_start,
	__initcall1_start,
	__initcall2_start,
	__initcall3_start,
	__initcall4_start,
	__initcall5_start,
	__initcall6_start,
	__initcall7_start,
	__initcall_end,
};

在Linux 内核连接文件arch/arm/kernel/vmlinux.lds 文件中有如下内容:

__initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;

链接文件定义了若干个段,在内核编译时不同的函数会连接到不容的段中,比如Linux内核会创建一个函数指针指向初始化函数且链接到initcall 段中,initcall 段中的0~7 数字表示优先级, 也就是将来这些初始化函数执行的先后。inicall 段中的函数在Linux 内核启动过程中也就是 do_initcalls 执行的时候被调用。do_initcalls 函数中 for循环的作用就是按照先后执行所有连接到initcall 段中的函数。那么这和设备树有什么关系呢,接下来我们再分析一个函数。

arch/arm/kernel/setup.c

static int __init customize_machine(void)
{
	/*
	 * customizes platform devices, or adds new ones
	 * On DT based machines, we fall back to populating the
	 * machine from the device tree, if no callback is provided,
	 * otherwise we would always need an init_machine callback.
	 */
	if (machine_desc->init_machine)
		machine_desc->init_machine();
#ifdef CONFIG_OF
	else
		of_platform_populate(NULL, of_default_bus_match_table,
					NULL, NULL);
#endif
	return 0;
}
arch_initcall(customize_machine);

arch_initcall 的作用是定义函数指针变量指向customize_machine 并将此函数指针连接到initcall 段。
customize_machine 会在系统启动被调用

查看 customize_machine 中实际上是调用了 machine_desc->init_machine 函数也就是前文注册的 machine_desc 中的:

	.init_machine	= imx6sx_init_machine,

设备树的转换主要通过 of_platform_populate ;
drivers/of/platform.c

/**
 * of_platform_populate() - Populate platform_devices from device tree data
 * @root: parent of the first level to probe or NULL for the root of the tree
 * @matches: match table, NULL to use the default
 * @lookup: auxdata table for matching id and platform_data with device nodes
 * @parent: parent to hook devices from, NULL for toplevel
 *
 * Similar to of_platform_bus_probe(), this function walks the device tree
 * and creates devices from nodes.  It differs in that it follows the modern
 * convention of requiring all device nodes to have a 'compatible' property,
 * and it is suitable for creating devices which are children of the root
 * node (of_platform_bus_probe will only create children of the root which
 * are selected by the @matches argument).
 *
 * New board support should be using this function instead of
 * of_platform_bus_probe().
 *
 * Returns 0 on success, < 0 on failure.
 */
int of_platform_populate(struct device_node *root,
			const struct of_device_id *matches,
			const struct of_dev_auxdata *lookup,
			struct device *parent)
{
	struct device_node *child;
	int rc = 0;
	//判断是否是根节点
	root = root ? of_node_get(root) : of_find_node_by_path("/");
	if (!root)
		return -EINVAL;
	//遍历结点
	for_each_child_of_node(root, child) {
		//转换为platform_devie 并遍历本节点的子节点
		rc = of_platform_bus_create(child, matches, lookup, parent, true);
		if (rc)
			break;
	}

	of_node_put(root);
	return rc;
}
EXPORT_SYMBOL_GPL(of_platform_populate);

看注释, of_platform_populate 会将相应结点转换为 platform_deivce ,而且下文代码中的 of_platform_bus_create 是一个递归调用逐次遍历各级子节点完成相应的转换,这里就不一一分析,先要了解整个转换过程可以跟进去看看。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页