Linux 内核启动流程
轉(zhuǎn)載自 http://wenku.baidu.com/link?url=KpOdULJu1CxP1swqRs_Szoyg5r_8rje4N08o4QtB5L9QlPjWesTYlrTPgkxPOriFtsmsqCyg-gWMVOkLjgYN640zsk7vGlN24tO5tkJcj8K
有修改
ARM Linux 內(nèi)核在Linux-3.x 內(nèi)核有了很大的變化,對一些新的平臺的支持取消了傳統(tǒng)的設(shè)備文件而用設(shè)備樹取代,這里以飛思卡爾的imx6sx-sdb設(shè)備樹識別為例說明Linux 是如何識別設(shè)備樹的。這里采用的linux內(nèi)核版本為3.14.28。
1、 設(shè)備樹文件
參考文件 arch/arm/boot/dts/imx6sx-sdb.dts,在線可以看 http://lxr.oss.org.cn/source/arch/arm/boot/dts/imx6sx-sdb.dts?v=3.17
編譯設(shè)備樹文件,內(nèi)核頂層目錄下執(zhí)行如下命令可以編譯設(shè)備樹文件:
$ make dtbs ARCH=arm
(可能因?yàn)槲沂褂脃octo進(jìn)行編譯的,所以我執(zhí)行時(shí)候報(bào)錯(cuò))
編譯后生成文件為 imx6sx-sdb.dtb, 文件是使用大端字節(jié)序存儲,可用下面命令看其二進(jìn)制內(nèi)容:
$ hexdump imx6sx-sdb.dtb -C
在uboot 引導(dǎo)內(nèi)核之前,會將設(shè)備樹文件加載到內(nèi)存中,以備Linux 內(nèi)核使用,這里就不詳細(xì)說明了
2、 Linux 內(nèi)核啟動
Linux 內(nèi)核啟動分幾個(gè)階段:
1) Linux 內(nèi)核自解壓
2) Linux 內(nèi)核初始化----匯編
3) Linux 內(nèi)核初始化----C
這里從第三階段開始說明,分析這個(gè)階段,主要是查看函數(shù)start_kernel,在start_kernel 中有幾個(gè)函數(shù)這里重點(diǎn)分析:
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_MULTIPLATFORMDT_MACHINE_START(GENERIC_DT, "Generic DT based system")MACHINE_ENDmdesc_best = &__mach_desc_GENERIC_DT;
#endifif (!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 函數(shù)是用來識別設(shè)備樹,看注釋,bootloader 如果將一個(gè)設(shè)備樹文件加載到內(nèi)存中,其通過r2 寄存器將設(shè)備樹的物理地址傳遞到Linux 內(nèi)核中,Linux 內(nèi)核來選擇正確的機(jī)器且對其進(jìn)行設(shè)置
函數(shù)中需要重點(diǎn)分析的函數(shù)有: 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_matchconst 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;//找到設(shè)備樹中的根節(jié)點(diǎn)(開始結(jié)點(diǎn))dt_root = of_get_flat_dt_root();/** get_next_compat = arch_get_next_mach* 內(nèi)核中可以同時(shí)支持多個(gè)平臺,這個(gè)函數(shù)用來獲得下一個(gè)平臺的* dt_compat 屬性,結(jié)合上邊函數(shù)克制data 內(nèi)容*/while((data = get_next_compat(&compat))) {/** of_flat_dt_match 用來匹配設(shè)備樹中內(nèi)容和內(nèi)核所支持平臺是否匹配*/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;/** 設(shè)備樹中內(nèi)容和內(nèi)核所支持平臺沒有匹配則打印出錯(cuò)提示*/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;}}
}
函數(shù)中會調(diào)用函數(shù) of_get_flat_dt_root 和 get_next_compat , of_get_flat_dt_root 用來從設(shè)備樹文件中找到根節(jié)點(diǎn), get_next_compat 按照上下文這個(gè)函數(shù)等于 arch_get_next_mach , 下邊會重點(diǎn)分析這兩個(gè)函數(shù)。
補(bǔ)充:設(shè)備樹相關(guān)宏和結(jié)構(gòu)體:
結(jié)點(diǎn)相關(guān)宏:
include/linux/of_fdt.h
OF_DT_HEADER 0xd00dfeed 標(biāo)記
OF_DT_BEGIN_NODE 0x1 開始結(jié)點(diǎn)
OF_DT_END_NODE 0x2 結(jié)束結(jié)點(diǎn)
OF_DT_PROP 0x3 Property: name off, size, *content資源
OF_DT_NOP 0x4 NOP
OF_DT_END 0x9 結(jié)束
OF_DT_VERSION 0x10 版本
include/linux/of_fdt.h
struct boot_param_header {__be32 magic; // OF_DT_HEADER 幻數(shù)__be32 totalsize; // 設(shè)備樹總體大小__be32 off_dt_struct; // structure 偏移__be32 off_dt_strings; // strings 偏移__be32 off_mem_rsvmap; // 內(nèi)存預(yù)留映射表偏移__be32 version; // 格式版本__be32 last_comp_version; // 最后兼容版本__be32 boot_cpuid_phys; // 我們要啟動的CPU 的ID__be32 dt_strings_size; // 設(shè)備樹strings 塊大小__be32 dt_struct_size; // 設(shè)備樹structure 塊大小
};
drivers/of/fdt.c?? ?看3.17的內(nèi)核實(shí)現(xiàn)又不一樣了
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);/** 結(jié)合上述內(nèi)容p = 設(shè)備樹起始地址 + 0x0038* 設(shè)備樹文件按16 進(jìn)制顯示:* 0000030 0000 0000 0000 0000 0000 0100 0000 0000* 0x38 = 0x00000001*//** 跳過所有無效數(shù)據(jù)*/while(be32_to_cpup((__be32 *)p) == OF_DT_NOP)p += 4;/** p = OF_DT_BEGIN_NODE 表示找到開始節(jié)點(diǎn),也就是設(shè)備樹有效數(shù)據(jù)的開始*/BUG_ON(be32_to_cpup((__be32 *)p) != OF_DT_BEGIN_NODE);p += 4;/** 數(shù)據(jù)對齊*/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;
}
內(nèi)核中又一個(gè)段叫.arch.info.init,內(nèi)核在編譯的時(shí)候會將所有支持的平臺的信息鏈接到這個(gè)段中即machine_desc,其中 __arch_info_begin 表示這個(gè)段的開始 __arch_info_end 表示這個(gè)段的結(jié)束。
連接腳本參考arch/arm/kernel/vmlinux.lds 中有如下內(nèi)容:
.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 的含義就是定義結(jié)構(gòu)體 machine_desc 并初始化且標(biāo)識鏈接到.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,
上述內(nèi)容主要是通過讀取設(shè)備樹信息,檢測當(dāng)前使用內(nèi)核是否支持本平臺,如果支持則做相應(yīng)的初始化,這里主要分析平臺識別過程。
2.2 rest_init
reset_init 函數(shù)中創(chuàng)建了幾個(gè)內(nèi)核線程,其中kernel_init 使我們要重點(diǎn)分析的。其中 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 內(nèi)核連接文件arch/arm/kernel/vmlinux.lds 文件中有如下內(nèi)容:
__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 = .;
鏈接文件定義了若干個(gè)段,在內(nèi)核編譯時(shí)不同的函數(shù)會連接到不容的段中,比如Linux內(nèi)核會創(chuàng)建一個(gè)函數(shù)指針指向初始化函數(shù)且鏈接到initcall 段中,initcall 段中的0~7 數(shù)字表示優(yōu)先級, 也就是將來這些初始化函數(shù)執(zhí)行的先后。inicall 段中的函數(shù)在Linux 內(nèi)核啟動過程中也就是 do_initcalls 執(zhí)行的時(shí)候被調(diào)用。do_initcalls 函數(shù)中 for循環(huán)的作用就是按照先后執(zhí)行所有連接到initcall 段中的函數(shù)。那么這和設(shè)備樹有什么關(guān)系呢,接下來我們再分析一個(gè)函數(shù)。
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_OFelseof_platform_populate(NULL, of_default_bus_match_table,NULL, NULL);
#endifreturn 0;
}
arch_initcall(customize_machine);
arch_initcall 的作用是定義函數(shù)指針變量指向customize_machine 并將此函數(shù)指針連接到initcall 段。
customize_machine 會在系統(tǒng)啟動被調(diào)用
查看 customize_machine 中實(shí)際上是調(diào)用了 machine_desc->init_machine 函數(shù)也就是前文注冊的 machine_desc 中的:
.init_machine = imx6sx_init_machine,
設(shè)備樹的轉(zhuǎn)換主要通過 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;//判斷是否是根節(jié)點(diǎn)root = root ? of_node_get(root) : of_find_node_by_path("/");if (!root)return -EINVAL;//遍歷結(jié)點(diǎn)for_each_child_of_node(root, child) {//轉(zhuǎn)換為platform_devie 并遍歷本節(jié)點(diǎn)的子節(jié)點(diǎn)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 會將相應(yīng)結(jié)點(diǎn)轉(zhuǎn)換為 platform_deivce ,而且下文代碼中的 of_platform_bus_create 是一個(gè)遞歸調(diào)用逐次遍歷各級子節(jié)點(diǎn)完成相應(yīng)的轉(zhuǎn)換,這里就不一一分析,先要了解整個(gè)轉(zhuǎn)換過程可以跟進(jìn)去看看。
總結(jié)
以上是生活随笔為你收集整理的Linux 内核启动流程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Select函数实现原理分析
- 下一篇: qemu模拟A9/A15运行Linux4