initramfs的加载过程(从uboot到kernel)

initramfs是一个cpio格式的包,压缩方式可以选择xz,但initramfs并不按照任何文件系统的格式组织数据。initramfs可以理解为initramfs=特定的文件夹(文件),把这些文件夹按照cpio格式打包再压缩就是initramfs(如生成最终文件initramfs.cpio.xz)。
系统使用 initramfs 作为它的根文件系统(或临时根文件系统)甚至不需要将文件系统驱动程序内建到kernel,因为没有块设备要用来做文件的块设备,只是存在内存中的文件罢了。initramfs执行的init程序不返回内核。
有两种方式生成initramfs打包文件:
1, 编译kernel的时候以一个cpio包的形式被包裹到kernel的镜像中,当kernel启动的时候,把initramfs解开,把里面的文件夹(文件)复制到一个tmpfs格式的分区中(roofs的根目录 “/”),那么就可以把这个分区作为根文件系统(或临时作为根文件系统,在initramfs的init脚本最后可以调用switch_root挂载并切换到真正的根文件系统中);
2, 不把initramfs编译进内核镜像中,而是单独组织生成一个cpio格式打包文件,然后可以kernel镜像打包在一起生在一个FIT镜像,然后由uboot代码负责解析出initramfs打包文件,uboot可以通过修改设备树信息(如修改chosen节点)进而将initramfs打包文件所处的物理地址信息传递给kernel,当kernel启动的时候,把initramfs解开,把里面的文件夹(文件)复制到一个tmpfs格式的分区中(roofs的根目录 “/”),那么就可以把这个分区作为根文件系统(或临时作为根文件系统,在initramfs的init脚本最后可以调用switch_root挂载并切换到真正的根文件系统中)。
uboot代码从FIT镜像中解析出initramfs打包文件,调用操作设备树代码(common/fdt_support.c)为chosen节点添加linux,initrd-start,linux,initrd-end属性,即设备initramfs打包文件load到的起始物理地址与结束物理地址。kernel启动阶段从r2寄存器拿到fdt后,通过early_init_dt_scan_chosen->early_init_dt_check_for_initrd(node)函数从设备树chosen节点获取到initramfs的物理地址。

设备树chosen节点:
        chosen {  
                bootargs = "console=ttyAMA0,115200 loglevel=7 panic=3 isolcpus=1
                linux,initrd-start = <0x27a35000>; //uboot代码设置的物理地址
                linux,initrd-end  = <0x27fffae8>;
                rsr = <0x2>;      
                crtm_partition = "primary";

uboot串口打印:
## Loading init Ramdisk from FIT Image at 10000000 ...
   Using 'config@1' configuration
   Trying 'ramdisk@1' ramdisk subimage
     Description:  initramfs.cpio.xz
     Type:           RAMDisk Image
     Compression:  uncompressed
     Data Start:   0x101f0924
     Data Size:    6066252 Bytes = 5.8 MiB
     Architecture: ARM
     OS:           Linux
     Load Address: 0x01000000
     Entry Point:  0x001f08c0
     Hash algo:    crc32
     Hash value:   c4249d83
   Verifying Hash Integrity ... crc32+ OK


kernel加载代码(按调用顺序, 以使用initramfs.cpio.xz为例,关键代码处加了注释):
#ifdef CONFIG_BLK_DEV_INITRD
/**
 * early_init_dt_check_for_initrd - Decode initrd location from flat tree
 * @node: reference to node containing initrd location ('chosen')
 */
void __init early_init_dt_check_for_initrd(unsigned long node)
{
	unsigned long start, end, len;
	__be32 *prop;

	pr_debug("Looking for initrd properties... ");

	prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
	if (!prop)
		return;
	start = of_read_ulong(prop, len/4);

	prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
	if (!prop)
		return;
	end = of_read_ulong(prop, len/4);

	early_init_dt_setup_initrd_arch(start, end);
	pr_debug("initrd_start=0x%lx  initrd_end=0x%lx\n", start, end);
}
#else
inline void early_init_dt_check_for_initrd(unsigned long node)
{
}
#endif /* CONFIG_BLK_DEV_INITRD */
#ifdef CONFIG_OF_FLATTREE
void __init early_init_dt_setup_initrd_arch(unsigned long start, unsigned long end)
{
	phys_initrd_start = start; //获取到了initramfs.cpio.xz文件的物理起始地址与size
	phys_initrd_size = end - start;
}
#endif /* CONFIG_OF_FLATTREE */
void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)
{
	int i;

	for (i = 0; i < mi->nr_banks; i++)
		memblock_add(mi->bank[i].start, mi->bank[i].size);

	/* Register the kernel text, kernel data and initrd with memblock. */
#ifdef CONFIG_XIP_KERNEL
	memblock_reserve(__pa(_sdata), _end - _sdata);
#else
	memblock_reserve(__pa(_stext), _end - _stext);
#endif
#ifdef CONFIG_BLK_DEV_INITRD
	if (phys_initrd_size &&
	    !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
		pr_err("INITRD: 0x%08lx+0x%08lx is not a memory region - disabling initrd\n",
		       phys_initrd_start, phys_initrd_size);
		phys_initrd_start = phys_initrd_size = 0;
	}
	if (phys_initrd_size &&
	    memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {
		pr_err("INITRD: 0x%08lx+0x%08lx overlaps in-use memory region - disabling initrd\n",
		       phys_initrd_start, phys_initrd_size);
		phys_initrd_start = phys_initrd_size = 0;
	}
	if (phys_initrd_size) {
		memblock_reserve(phys_initrd_start, phys_initrd_size);

		/* Now convert initrd to virtual addresses */
		initrd_start = __phys_to_virt(phys_initrd_start);//将initramfs.cpio.xz文件所处物理地址转化为虚拟地址(+PAGE_OFFSET)
		initrd_end = initrd_start + phys_initrd_size;
	}
#endif
static int __init populate_rootfs(void)
{
        char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
        if (err)
                panic(err);     /* Failed to decompress INTERNAL initramfs */
        if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM //使用initramfs时该宏没有定义,使用ramdisk时才定义该宏
                int fd;
                printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
                err = unpack_to_rootfs((char *)initrd_start,
                        initrd_end - initrd_start);
                if (!err) {
                        free_initrd();
                        goto done;
                } else {
                        clean_rootfs();
                        unpack_to_rootfs(__initramfs_start, __initramfs_size);
                }
                printk(KERN_INFO "rootfs image is not initramfs (%s)"
                                "; looks like an initrd\n", err);
                fd = sys_open("/initrd.image",
                              O_WRONLY|O_CREAT, 0700);
                if (fd >= 0) {
                        sys_write(fd, (char *)initrd_start,
                                        initrd_end - initrd_start);
                        sys_close(fd);
                        free_initrd();
                }
        done:
#else
                printk(KERN_INFO "Unpacking initramfs...\n");
                err = unpack_to_rootfs((char *)initrd_start,
                        initrd_end - initrd_start); //将initramfs.cpio.xz解包解压到roofs的根目录("/")
                if (err)
                        printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
                free_initrd();
#endif
                /*
                 * Try loading default modules from initramfs.  This gives
                 * us a chance to load before device_initcalls.
                 */
                load_default_modules();
        }
        return 0;
}
rootfs_initcall(populate_rootfs);
static noinline void __init kernel_init_freeable(void)
{
        struct stat console_stat;
        /*
         * Wait until kthreadd is all set-up.
         */
        wait_for_completion(&kthreadd_done);

        /* Now the scheduler is fully set up and can do blocking allocations */
        gfp_allowed_mask = __GFP_BITS_MASK;

        /*
         * init can allocate pages on any node
         */
        set_mems_allowed(node_states[N_MEMORY]);
        /*
         * init can run on any cpu.
         */
        set_cpus_allowed_ptr(current, cpu_all_mask);

        cad_pid = task_pid(current);

        smp_prepare_cpus(setup_max_cpus);

        do_pre_smp_initcalls();
        lockup_detector_init();

        smp_init();
        sched_init_smp();

        do_basic_setup();//上述populate_rootfs是在该函数中调用的(do_initcalls)

        /* Use /dev/console to infer if the rootfs is setup properly */
        if (sys_newlstat((char __user *) "/dev/console", (struct stat __user *) &console_stat)
                        || !S_ISCHR(console_stat.st_mode)) {
                panic("/dev/console is missing or not a character device!\nPlease ensure your rootfs is properly configured\n");
        }

        /* Open the /dev/console on the rootfs, this should never fail */
        if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
                pr_err("Warning: unable to open an initial console.\n");

        (void) sys_dup(0);
        (void) sys_dup(0);
        /*
         * check if there is an early userspace init.  If yes, let it do all
         * the work
         */

        if (!ramdisk_execute_command) { //因为没有使用ramdisk,所以该值为null
                ramdisk_execute_command = "/init"; //将其赋值,实际上"/init"就是刚才在populate_rootfs对initramfs.cpio.xz解包解压到"/"后的得到的init
        }
        //因为在populate_rootfs中已将将initramfs.cpio.xz解包解压到了rootfs的根目录,所以这里判断"/init"是不是可以访问的,答案是肯定的,因为该文件已经存在了
        if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
                ramdisk_execute_command = NULL;
                prepare_namespace(); //所以在使用initramfs时,这里不会执行。但使用ramdisk时,会执行到这里,因为ramdisk在"/"还是一个image文件,还没有挂载,所以在"/"之下还不存在init文件
        }

         /*
         * Ok, we have completed the initial bootup, and
         * we're essentially up and running. Get rid of the
         * initmem segments and start the user-mode stuff..
         */

        /* rootfs is available now, try loading default modules */
        load_default_modules();
}











static int __ref kernel_init(void *unused)
{
	kernel_init_freeable();
	/* need to finish all async __init code before freeing the memory */
	async_synchronize_full();
	free_initmem();
	mark_rodata_ro();
	system_state = SYSTEM_RUNNING;
	numa_default_policy();

	flush_delayed_fput();

	if (ramdisk_execute_command) { //因为已经设置了该变量,所以下面就开始执行rootfs根下init脚本了,他继承了kernel_thread(kernel_init,...)内核线程的PID=1,开始转向用户空间执行
		if (!run_init_process(ramdisk_execute_command))
			return 0;
		pr_err("Failed to execute %s\n", ramdisk_execute_command);
	}

	/*
	 * We try each of these until one succeeds.
	 *
	 * The Bourne shell can be used instead of init if we are
	 * trying to recover a really broken machine.
	 */
	if (execute_command) {
		if (!run_init_process(execute_command))
			return 0;
		pr_err("Failed to execute %s.  Attempting defaults...\n",
			execute_command);
	}
	if (!run_init_process("/sbin/init") ||
	    !run_init_process("/etc/init") ||
	    !run_init_process("/bin/init") ||
	    !run_init_process("/bin/sh"))
		return 0;

	panic("No init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/init.txt for guidance.");
}
下面看看init脚本,可以在最后挂载真正的根文件系统(如果initramfs不是作为最终根文件系统的话):
exec /sbin/switch_root $ROOTFS_MNT /sbin/init # ROOTFS_MNT = /mnt
在执行 switch_root之前,在init脚本中可以从远程server通过wget下载真正的根文件系统,并解压到/mnt目录下,然后在最后执行switch_root。
switch_root会做这些工作: switch_root moves already mounted /proc, /dev and /sys to newroot and makes newroot the new root filesystem and starts init process(就是 /lib/systemd/systemd,然后systemd就可启动各种APP的service了,直至systemd达到多用户target,至此启动完毕).
root@tian:~# ls -all /sbin/init
lrwxrwxrwx    1 root root            20 Jan  1  1970 /sbin/init -> /lib/systemd/systemd
  • 2
    点赞
  • 20
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
评论

打赏作者

yiyeguzhou100

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值