RK3568系统启动流程分析
一、RK3568启动存储空间布局
二、uboot启动kernel的代码流程分析
三、kernel挂载根文件系统流程分析
一、RK3568启动存储空间布局
BootRom大小是20KB,物理地址是0xFFFF0000
有两个SRAM:
PMU_SRAM(8KB),物理地址是0xFDCD0000;
SYSTEM_SRAM (64KB),物理地址是0xFDCC0000;
remap[1:0]=2'b00:可以通过0xFFFF0000访问BootRom
remap[1:0]=2'b11:可以通过0xFFFF0000访问PMU_SRAM(8KB)
remap[1:0]=2'b10:可以通过0xFF000000访问SYSTEM_SRAM (64KB)
DRAM是2G,起始物理地址是0x00000000;
外接emmc,通过emmc启动,emmc被划分为多个分区,用于存储不同的映像文件,0x0~0x4000用于存储MiniLoaderAll.bin,0x4000~0x6000用于存储uboot.img,0x6000~0x8000用于存储trust.img,0x8000~0x40000用于存储boot.img,rootfs.img存储于0x40000开始的地方;
启动流程:
开发板上电,首先运行BootROM里面的代码,这部分代码是瑞芯微出厂固化在BootROM里面的,Boot code会判断当前的启动介质是emmc,然后从emmc将MiniLoaderAll.bin加载到SRAM,然后跳转到MiniLoaderAll.bin;
MiniLoaderAll.bin会初始化cpu核心板,初始化DDR,然后从emmc的0x4000地址处将uboot.img拷贝到DDR的0x00a00000地址处,0x00a00000是uboot.img的链接地址,也是加载地址;
Uboot.img会将FTI映像从emmc的0x8000加载到DDR,FIT映像里面包含kernel映像、DTB映像、ramdisk,uboot.img会执行解压kernel的操作,准备设备树DTB,然后跳转到kernel的入口地址0x00280000运行;
kernel启动后,会挂载根文件系统,将系统运行起来。

二、uboot启动kernel的代码流程分析
uboot启动到主循环main_loop,该函数运行过读秒逻辑后,会运行bootcmd环境变量里面的命令序列,bootcmd环境变量是用户自定义的启动kernel的命令,
启动kernel命令对应的执行函数是do_boot_fit
main_loop
autoboot_command --- 执行bootcmd环境变量里面的命令序列,相当于run bootcmd
run_command_list
parse_string_outer
parse_stream_outer
do_boot_fit
do_boot_fit启动kernel的代码流程:
首先,获取启动介质rockchip_get_bootdev,这里是mmc 0;获取fit映像所在的分区信息,然后将整个fit映像从emmc读到内存;这个FTI映像文件在uboot里面有专门的FIT描述文件来描述,里面描述了FIT各映像的基本信息,比如kernel、DTB、ramdisk的加载地址和启动地址等,这个文件类似于设备树,有专门的操作接口;
uboot从FIT描述文件获取到各映像的入口地址和加载地址,将这些信息设置到FIT描述文件;
接着调用do_bootm_states函数,开始状态机启动流程,填充 images 结构体,这个images结构体是专门描述FIT各映像信息的,比如加载地址、入口、大小、OS 类型等;接着将kernel解压到kernel的加载地址,将设备树DTB映像拷贝到DTB的加载地址,将ramdisk映像拷贝到ramdisk的加载地址;
接着对设备树内容进行修正,比如获取DRAM物理内存信息,写入 /memory 节点、更新电源管理PSCI、设置调试串口、修正bootargs、在bootargs里面添加linux,initrd-start 和 linux,initrd-end 属性等等;
最后,调用汇编函数armv8_switch_to_el2跳转到内核入口地址images->ep,将设备树地址images->ft_addr通过x0寄存器传递给内核,至此,内核开始运行。
/*
* do_boot_fit
*
*/
/*
* argc == 1:
* FIT image is loaded from storage(eg. CONFIG_BOOTCOMMAND).
*
* argc == 2:
* FIT image is already in ram, the booflow is:
* CLI cmd "bootm <fit_addr>" => do_bootm() =>
* board_do_bootm() => boot_fit <fit_addr>
* 获取启动介质rockchip_get_bootdev,这里是mmc 0: Bootdev(scan): mmc 0
* 获取fit映像所在的分区信息,然后将整个fit映像从emmc读到内存
* 获取fit镜像中设备树FTD的加载地址load、设备树FDT在fit镜像中的offset、fit镜像中FDT的大小
* 设置设备树FDT的入口地址和加载地址、向uboot声明将FDT的加载地址预留起来
* 设置kernel的入口地址和加载地址
* 设置ramdisk的入口地址和加载地址
* 调用do_bootm_states函数,开始状态机启动流程:
* BOOTM_STATE_START阶段,填充 images 结构体,并初始化 lmb(内存块管理器)等
* BOOTM_STATE_FINDOS阶段,从FIT映像中解析 OS 镜像的头部信息(加载地址、入口、大小、OS 类型等),并填充images相关字段
* BOOTM_STATE_FINDOTHER阶段,查找其他组件,如 ramdisk、设备树(FDT)等,并填充 images 中相应字段(rd_start、ft_addr 等)
* BOOTM_STATE_LOADOS阶段,把内核解压加载到load地址
* 获取kernel的启动函数:do_bootm_linux
* 调用启动函数:do_bootm_linux:
* 将设备树的内存保留
* 调用cpu架构定制的函数对设备树内容进行修正,比如获取DRAM物理内存信息,写入 /memory 节点、更新电源管理PSCI、设置调试串口、修正bootargs、
* 在bootargs里面添加linux,initrd-start 和 linux,initrd-end 属性等等
* 调用汇编函数armv8_switch_to_el2跳转到内核入口地址images->ep,将设备树地址images->ft_addr通过x0寄存器传递给内核,至此,内核开始运行
*/
static int do_boot_fit(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
/*
* 从启动设备(如 eMMC、SD 卡)中加载整个 FIT(Flattened Image Tree)映像到内存
*/
void *fit;
fit = do_boot_fit_storage(&size);
/* 获取启动介质,这里是mmc 0: Bootdev(scan): mmc 0 */
struct blk_desc *dev_desc;
dev_desc = rockchip_get_bootdev();
/*
* 获取fit映像所在的分区信息
* 从emmc读取整个镜像到fit
* FIT映像文件在存储设备中的布局 = [FDT头部(含结构)] + [内核数据] + [设备树数据] + [ramdisk数据]
*/
void *fit;
disk_partition_t part;
fit = fit_get_blob(dev_desc, &part, false);
/* 获取Boot分区信息,这个分区保存kernel和dtb的映像 */
disk_partition_t part;
char *part_name = PART_BOOT;
part_get_info_by_name(dev_desc, part_name, &part);
/* 计算读取 FDT 头部所需的块数并分配内存 */
int blk_num;
blk_num = DIV_ROUND_UP(sizeof(struct fdt_header), dev_desc->blksz); //fdt头部长度除以块大小,等于需要的块数量
fdt = memalign(ARCH_DMA_MINALIGN, blk_num * dev_desc->blksz); //申请内存
/* 从分区起始扇区读取头部 */
blk_dread(dev_desc, part.start, blk_num, fdt);
/* 获取 FIT 总大小并重新分配完整内存 */
blk_num = DIV_ROUND_UP(fdt_totalsize(fdt), dev_desc->blksz);
fit = memalign(ARCH_DMA_MINALIGN, blk_num * dev_desc->blksz);
/* 读取完整 FIT 映像 */
blk_dread(dev_desc, part.start, blk_num, fit);
/*
* 获取整个fit镜像的大小
* 具体流程待研究!!!
*/
*size = fit_image_get_bootables_size(fit);
fit_image_get_param(fit, FIT_FDT_PROP, &load, &offset, &size)
ret = fdt_image_get_offset_size(fit, prop_name, offset, size);
return fdt_image_get_load(fit, prop_name, load);
off[0] = offset + FIT_ALIGN(size);
fit_image_get_param(fit, FIT_KERNEL_PROP, &load, &offset, &size)
off[1] = offset + FIT_ALIGN(size);
fit_image_get_param(fit, FIT_RAMDISK_PROP, &load, &offset, &size)
off[2] = offset + FIT_ALIGN(size);
max_off = max(off[0], off[1]);
max_off = max(max_off, off[2]);
return FIT_ALIGN(fdt_totalsize(fit)) + max_off;
/*
* 分配保存镜像的内存
*/
blk_num = DIV_ROUND_UP(*size, dev_desc->blksz);
fit = sysmem_alloc(MEM_FIT, blk_num * dev_desc->blksz);
/* 从emmc读取fit映像 */
blk_dread(dev_desc, part.start, blk_num, fit);
/*
* 检查fit镜像合法性
*/
fdt_check_header(fit);
/*
* fixup entry/load and alloc sysmem
* 设置FDT、kernel、ramdisk的入口地址和加载地址
*/
fit_image_pre_process(fit);
ret = fit_image_fixup_alloc(fit, FIT_FDT_PROP, "fdt_addr_r", MEM_FDT);
/* 获取uboot环境变量保存的fdt_addr_r值 */
addr = env_get_ulong(addr_name, 16, 0);
/* 获取fit镜像中设备树FTD的加载地址load、设备树FDT在fit镜像中的offset、fit镜像中FDT的大小 */
ret = fit_image_get_param(fit, prop_name, &load, &offset, &size);
/*
* 设置设备树FDT的入口地址和加载地址
*/
ret = fix_image_set_addr(fit, prop_name, load, addr);
/* do not fix if verified-boot,只有在 old 是占位符 且 不要求验证的情况下,才真正执行地址修复 */
if (!fit_image_addr_is_placeholder(old) || fit_sig_require_conf(fit, gd_fdt_blob()))
return 0;
/* 获取fdt_addr_r配置节点的偏移量 */
int noffset;
noffset = fit_default_conf_get_node(fit, prop_name);
/* 获取默认配置节点的偏移量 */
conf_noffset = fit_conf_get_node(fit, NULL); /* NULL for default conf */
/*
* 该函数在给定的配置节点(conf_noffset)下,获取名为 prop_name 的属性,该属性值应为一个字
* 符串(表示镜像单元名),然后根据该单元名在 /images 下查找对应的镜像节点,返回其偏移量。
*/
return fit_conf_get_prop_node(fit, conf_noffset, prop_name);
fit_conf_get_prop_node_index(..., index=0)
fdt_stringlist_get() //获取 "kernel" 属性的字符串值(如 "kernel-1")
fit_image_get_node(fit, "kernel-1")
int noffset, images_noffset;
images_noffset = fdt_path_offset("/images")
noffset = fdt_subnode_offset(..., "kernel-1")
/* 返回配置节点的偏移 */
return noffset;
/* 为指定的镜像节点(由 noffset 标识)设置 entry 属性(入口地址)为 entry 值 */
fit_image_set_entry(fit, noffset, new);
/* 获取节点中名为 name 的属性(例如 "entry")的原始数据指针和长度 */
const fdt32_t *cell;
cell = fdt_getprop(fit, noffset, name, &len);
/* 写入新值 */
cell_len = len >> 2;
/* Use load64 to avoid compiling warning for 32-bit target */
while (cell_len--)
*(fdt32_t *)cell = cpu_to_uimage(addr >> (32 * cell_len));
cell++;
/* 设置加载地址的值 */
fit_image_set_load(fit, noffset, new);
fit_image_set_address(fit, noffset, FIT_LOAD_PROP, load);
const fdt32_t *cell;
cell = fdt_getprop(fit, noffset, name, &len);
cell_len = len >> 2;
while (cell_len--)
*(fdt32_t *)cell = cpu_to_uimage(addr >> (32 * cell_len));
cell++;
/* 向uboot声明将FDT的加载地址预留起来 */
sysmem_alloc_base(mem, (phys_addr_t)addr, ALIGN(size, RK_BLK_SIZE))
/*
* 设置kernel的入口地址和加载地址
*/
ret = fit_image_fixup_alloc(fit, FIT_KERNEL_PROP, "kernel_addr_r", MEM_KERNEL);
/*
* 设置ramdisk的入口地址和加载地址
*/
fit_image_fixup_alloc(fit, FIT_RAMDISK_PROP, "ramdisk_addr_r", MEM_RAMDISK);
/*
* 把镜像的首地址格式化到bootm_args
*/
char *bootm_args[1];
snprintf(fit_addr, sizeof(fit_addr), "0x%lx", (ulong)fit);
bootm_args[0] = fit_addr;
/*
* do_bootm_states
*
*/
ret = do_bootm_states(NULL, 0, ARRAY_SIZE(bootm_args), bootm_args,
BOOTM_STATE_START |
BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
BOOTM_STATE_LOADOS |
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
BOOTM_STATE_RAMDISK |
#endif
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO, &images, 1);
/* uboot定制取消哪些阶段 */
unmask = env_get_ulong("bootm_states_unmask", 16, 0);
if (unmask)
states &= ~unmask;
images->state |= states;
/*
* BOOTM_STATE_START
* 填充 images 结构体,并初始化 lmb(内存块管理器)等
*/
if (states & BOOTM_STATE_START)
ret = bootm_start(cmdtp, flag, argc, argv);
memset((void *)&images, 0, sizeof(images));
boot_start_lmb(&images);
images.state = BOOTM_STATE_START;
return bootm_board_start();
/*
* BOOTM_STATE_FINDOS
* bootm_find_os 会从镜像(如 FIT 或 legacy 格式)中解析 OS 镜像的头部信息(加载地址、入口、大小、OS 类型等),并填充 images->os 字段
*/
if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(cmdtp, flag, argc, argv);
const void *os_hdr;
/*
* get kernel image header, start address and length
* argv保存的是fit首地址
*/
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv, &images, &images.os.image_start, &images.os.image_len);
/* img_addr就是fit映像的首地址 */
ulong img_addr;
img_addr = genimg_get_kernel_addr_fit(argc < 1 ? NULL : argv[0], &fit_uname_config, &fit_uname_kernel);
kernel_addr = simple_strtoul(img_addr, NULL, 16);
return kernel_addr;
buf = map_sysmem(img_addr, 0);
switch (genimg_get_format(buf))
case IMAGE_FORMAT_FIT:
os_noffset = fit_image_load(images, img_addr,&fit_uname_kernel, &fit_uname_config,IH_ARCH_DEFAULT, IH_TYPE_KERNEL,BOOTSTAGE_ID_FIT_KERNEL_START,
FIT_LOAD_IGNORED, os_data, os_len);
fit = map_sysmem(addr, 0);
prop_name = fit_get_image_type_property(image_type);
/* get FIT component image node offset */
noffset = fit_image_get_node(fit, fit_uname);
/*
* 打印选择的kernel映像信息
Description: unavailable
Type: Kernel Image
Compression: uncompressed
fit_image_get_data enter.
Data Start: 0x7926c440
Data Size: 39369216 Bytes = 37.5 MiB
Architecture: AArch64
OS: Linux
Load Address: 0x00280000
Entry Point: 0x00280000
Hash algo: sha256
Hash value: c002d53d1a63bae13e344ce5372e8ab8c25da5455e98aebfacdd5981699817d5
Verifying Hash Integrity ... fit_image_get_data enter.
*/
ret = fit_image_select(fit, noffset, images->verify);
/*
* 从FIT的文件列表读取cpu架构信息
*/
fit_image_get_arch(fit, noffset, &os_arch);
images->os.arch = os_arch;
/* get image data address and length */
fit_image_get_data(fit, noffset, &buf, &size);
/* 板子定制的处理函数 */
board_fit_image_post_process((void *)fit, noffset, &load, (ulong **)&buf, &size, NULL);
/* 解压映像 */
fit_decomp_image(fit, node, load_addr, src_addr, src_len, spec);
/* 内核设备树已准备就绪,但是尚未设置内核设备树,用全局设备树 gd->fdt_blob 覆盖 FIT 中的设备树 */
*src_addr = (void *)gd->fdt_blob;
*src_len = (size_t)fdt_totalsize(gd->fdt_blob);
/*
* 将FIT映像中bootargs节点的内容追加到uboot的bootargs
*/
old = env_get("bootargs");
strcat(new, (char *)(*src_addr));
env_set("bootargs", new); //重新设置uboot的bootargs环境变量
/* buf是映像的加载地址,也就是运行时被加载到的地址 */
void *vbuf = (void *)buf;
data = map_to_sysmem(vbuf);
*datap = data;
*lenp = len;
/*
* 填充images,bootm_headers_t *images
*/
images->fit_hdr_os = map_sysmem(img_addr, 0);
images->fit_uname_os = fit_uname_kernel;
images->fit_uname_cfg = fit_uname_config;
images->fit_noffset_os = os_noffset;
/* end of boot_get_kernel */
/*
* 继续填充images结构体的相关成员
* 这些值都是从FIT的文件列表取出来的,FIT的文件列表被组织成设备树节点的结构
* 通过fdt_getprop、fdt_path_offset、fdt_subnode_offset这类函数来操作
*/
fit_image_get_type(images.fit_hdr_os,images.fit_noffset_os,&images.os.type);
fit_image_get_comp(images.fit_hdr_os,images.fit_noffset_os,&images.os.comp);
fit_image_get_os(images.fit_hdr_os, images.fit_noffset_os,&images.os.os);
fit_image_get_arch(images.fit_hdr_os,images.fit_noffset_os,&images.os.arch);
images.os.end = fit_get_end(images.fit_hdr_os);
fit_image_get_load(images.fit_hdr_os, images.fit_noffset_os,&images.os.load);
/* 获取kernel的启动函数地址,也就是entry的地址,Entry Point: 0x00280000 */
ret = fit_image_get_entry(images.fit_hdr_os,images.fit_noffset_os, &images.ep);
images.os.start = map_to_sysmem(os_hdr);
/*
* BOOTM_STATE_FINDOTHER
* 查找其他组件,如 ramdisk、设备树(FDT)等,并填充 images 中相应字段(rd_start、ft_addr 等)
*/
if (!ret && (states & BOOTM_STATE_FINDOTHER))
ret = bootm_find_other(cmdtp, flag, argc, argv);
/* Load the OS */
if (!ret && (states & BOOTM_STATE_LOADOS))
ulong load_end;
iflag = bootm_disable_interrupts();
/*
* 把内核加载到load地址
*
*/
ret = bootm_load_os(images, &load_end, 0);
image_info_t os = images->os;
ulong load = os.load;
/* 加载地址 */
load_buf = map_sysmem(load, 0);
/* kernel映像所在 */
image_buf = map_sysmem(os.image_start, image_len);
/* 把内核解压到加载地址 */
bootm_decomp_image(os.comp, load, os.image_start, os.type,load_buf, image_buf, image_len,CONFIG_SYS_BOOTM_LEN, load_end);
/* 刷cache,保证kernel映像写到加载地址 */
flush_cache(load, ALIGN(*load_end - load, ARCH_DMA_MINALIGN));
/* update image len as decompressed kernel size for late use */
images->os.image_len = *load_end - load;
lmb_reserve(&images->lmb, images->os.load, (load_end - images->os.load));
boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
ret = boot_ramdisk_high(&images->lmb, images->rd_start,rd_len, &images->initrd_start, &images->initrd_end);
ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,&images->ft_len);
/* 获取kernel的启动函数:do_bootm_linux */
boot_fn = bootm_os_get_boot_func(images->os.os);
/*
* 开始启动内核
*/
if (!ret && (states & BOOTM_STATE_OS_GO))
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO, images, boot_fn);
arch_preboot_os(state, images);
/* 进行地址对齐操作 */
u8 *data = (void *)images->ep;
orig_images_ep = images->ep;
if (data[10] == 0x00)
if (round_down(images->ep, SZ_2M) != images->ep)
images->ep = round_down(images->ep, SZ_2M);
if (IS_ALIGNED(images->ep, SZ_2M))
images->ep += 0x80000;
/* 调用启动函数:do_bootm_linux */
boot_fn(state, argc, argv, images);
boot_prep_linux(images);
image_setup_linux(images);
/* 将设备树的内存保留 */
boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);
ret = boot_get_cmdline(lmb, &images->cmdline_start,&images->cmdline_end);
cmdline = (char *)(ulong)lmb_alloc_base(lmb, CONFIG_SYS_BARGSIZE, 0xf,env_get_bootm_mapsize() + env_get_bootm_low());
s = env_get("bootargs");
strcpy(cmdline, s);
*cmd_start = (ulong) & cmdline[0];
*cmd_end = *cmd_start + strlen(cmdline);
ret = boot_relocate_fdt(lmb, of_flat_tree, &of_size);
ret = image_setup_libfdt(images, *of_flat_tree, of_size, lmb);
/*
* cpu架构定制的函数对设备树内容进行修正
*/
arch_fixup_fdt(blob);
/* 板级定制的函数对设备树内容进行修正 */
ret = board_fdt_fixup(blob);
/* 打印保留内存,Show "/reserved-memory" */
boot_mem_rsv_regions(NULL, blob);
/* 获取DRAM物理内存信息,写入 /memory 节点 */
ret = fdt_fixup_memory_banks(blob, start, size, CONFIG_NR_DRAM_BANKS);
/* 更新spin table */
ret = spin_table_update_dt(blob);
/* 更新电源管理PSCI */
ret = psci_update_dt(blob);
/* 设置调试串口 */
fdt_root(blob);
/* 修正bootargs */
fdt_chosen(blob);
/* 修正设备树网卡节点 */
fdt_fixup_ethernet(blob);
/* 重新调整设备树占用的内存大小,这里直接修正保留内存的设备树节点 */
ret = fdt_shrink_to_minimum(blob, 0);
/* 在bootargs里面添加linux,initrd-start 和 linux,initrd-end 属性,也就是initramfs占用的内存区 */
fdt_initrd(blob, *initrd_start, *initrd_end);
boot_jump_linux(images, flag);
/* 声明跳转内核的函数 */
void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,void *res2);
/* 内核启动的入口地址 */
kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,void *res2))images->ep;
/* 启动内核前的清理动作,比如关闭中断这些 */
announce_and_cleanup(images, fake);
/* 电源管理相关的设置 */
armv8_setup_psci();
/* 刷cache */
do_nonsec_virt_switch();
/*
* 调用汇编函数开始启动kernel
* images->ft_addr设备树地址,通过x0寄存器传递给kernel
* images->ep内核的入口函数地址
*/
armv8_switch_to_el2((u64)images->ft_addr, 0, 0, es_flag, images->ep, ES_TO_AARCH64);
/* 使用跳转指令br,跳转到x4的值,也就是images->ep */
br x4
三、kernel挂载根文件系统流程分析
以NFS方式挂载根文件系统为例;
首先uboot的bootargs环境变量要设置成NFS方式挂载:
setenv bootargs "root=/dev/nfs root=/dev/nfs nfsroot=${serverip}:/home/topeet/ubuntu20desktop,v3 rw ip=${ipaddr}:${serverip}:${gateway}:255.255.255.0::eth1:off init=/linuxrc"
uboot跳转到kernel之后,kernel先运行了一段汇编代码,然后调用start_kernel进入C语言阶段;该阶段首先会对内核的各个子系统进行初始化,比如内存管理、调度管理、中断子系统等,加载各类驱动;然后调用rest_init函数,该函数里面会创建kernel_init线程;该线程会调用mount_nfs_root函数,去NFS服务器目录挂载根文件系统,代码流程如下所示;
内核启动 │ ├─ start_kernel() [init/main.c] │ └─ parse_args() ← 解析 root=, nfsroot=, ip= 等 │ ├─ name_to_dev_t("/dev/nfs") → ROOT_DEV = Root_NFS │ ├─ nfs_root_setup() [fs/nfs/nfsroot.c] │ └─ ip_auto_config() [net/ipv4/ipconfig.c] │ ├─ kernel_init() [init/main.c] │ └─ prepare_namespace() [init/do_mounts.c] │ ├─ mount_root() │ │ └─ mount_nfs_root() │ │ └─ do_mount_root("nfs", ...) │ │ └─ sys_mount() [VFS 系统调用] │ │ └─ nfs_do_root_mount() [fs/nfs/super.c] │ │ └─ vfs_kern_mount() │ │ └─ nfs3_try_mount() → RPC 连接 NFS 服务器 │ │ │ └─ init_mount_tree() ← 将挂载点转为根目录 │ └─ 执行 init 程序 (/linuxrc)
根文件系统挂载完成之后,kernel_init函数判断execute_command变量定义了init程序init=/linuxrc,于是就在根文件系统中查找 /linuxrc 并执行它;
kernel_init if (ramdisk_execute_command) ret = run_init_process(ramdisk_execute_command); if (execute_command) ret = run_init_process(execute_command); if (CONFIG_DEFAULT_INIT[0] != '\0') ret = run_init_process(CONFIG_DEFAULT_INIT); if (!try_to_run_init_process("/sbin/init") || !try_to_run_init_process("/etc/init") || !try_to_run_init_process("/bin/init") || !try_to_run_init_process("/bin/sh")) return 0; panic("No working init found. Try passing init= option to kernel. See Linux Documentation/admin-guide/init.rst for guidance.");
init=/linuxrc进程开始运行,内核转到用户态运行;
然后linuxrc会解析/etc/inittab文件,inittab文件中是一些条目,这些条目指明了在某种状态下会执行哪些操作,比如初始化阶段sysinit做哪些工作,系统关闭shutdown时做哪些工作;linuxrc解析完inittab文件后,会执行sysinit指定的操作,即执行脚本文件/etc/init.d/rcS,之后就会进入到控制台,完成系统启动。
参考文档:
《【北京迅为】itop-rk3568开发板官方android11移植教程.pdf》
《【北京迅为】itop-3568 开发板文件系统构建手册v1.1.pdf》
浙公网安备 33010602011771号