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)

  DRAM2G,起始物理地址是0x00000000

  外接emmc,通过emmc启动,emmc被划分为多个分区,用于存储不同的映像文件,0x0~0x4000用于存储MiniLoaderAll.bin0x4000~0x6000用于存储uboot.img0x6000~0x8000用于存储trust.img0x8000~0x40000用于存储boot.imgrootfs.img存储于0x40000开始的地方;

启动流程:

  开发板上电,首先运行BootROM里面的代码,这部分代码是瑞芯微出厂固化在BootROM里面的,Boot code会判断当前的启动介质是emmc,然后从emmcMiniLoaderAll.bin加载到SRAM,然后跳转到MiniLoaderAll.bin

  MiniLoaderAll.bin会初始化cpu核心板,初始化DDR,然后从emmc0x4000地址处将uboot.img拷贝到DDR0x00a00000地址处,0x00a00000uboot.img的链接地址,也是加载地址;

  Uboot.img会将FTI映像从emmc0x8000加载到DDRFIT映像里面包含kernel映像、DTB映像、ramdiskuboot.img会执行解压kernel的操作,准备设备树DTB,然后跳转到kernel的入口地址0x00280000运行;

  kernel启动后,会挂载根文件系统,将系统运行起来。

1

二、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各映像的基本信息,比如kernelDTBramdisk的加载地址和启动地址等,这个文件类似于设备树,有专门的操作接口;

  ubootFIT描述文件获取到各映像的入口地址和加载地址,将这些信息设置到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方式挂载根文件系统为例;

  首先ubootbootargs环境变量要设置成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》

 

posted @ 2026-09-13 22:02  小小的番茄  阅读(21)  评论(0)    收藏  举报