linux引导initrd流程解析

​本文所使用的内核版本: linux-4.19.87
1 内核引导过程
1.1 自解压
bl decompress_kernel /* arch/arm/boot/compressed/head.S +561 /
decompress_kernel() /
arch/arm/boot/compressed/misc.c /
/
putstr(“Uncompressing Linux…”);
ret = do_decompress();
putstr(" done, booting the kernel.\n");
*/
1.2 引导
/arch/arm/kernel/head.S +149
ENTRY(stext)
ldr r13, =__mmap_switched @ address to jump to after
ENDPROC(stext)

arch/arm/kernel/head-common.S +119
__mmap_switched:
b start_kernel
ENDPROC(__mmap_switched)

init/main.c
start_kernel(void)
{
/* 进行一系列初始化 */
}

2 内核在何时解析initrd选项,如何使用的?
2.1 解析引导参数时调用early_initrd()找到initrd在内存中的物理地址和大小

arch/arm/mm/init.c
early_param("initrd", early_initrd);
调用流程如下:
start_kernel()
  -->setup_arch(&command_line) // 传出命令行, 定义在 arch/arm/kernel/setup.c
    -->parse_early_param();
      -->parse_early_options(boot_command_line);
        -->parse_args("early options", boot_command_line, NULL, 0, 0, 0, do_early_param);
        /* 从boot_command_line中获取一组参数"param=val", 逐个调用parse_one()解析 */
          -->parse_one(param, val, "early options", NULL, 0, 0, 0, do_early_param); /* doing为函数指针do_early_param的第三个参数 */
            -->do_early_param(param, val, "early options");    
                /*
                    const struct obs_kernel_param *p; /* {str,setup_func 函数指针,early} */
                    for (p = __setup_start; p < __setup_end; p++) {
                            p->setup_func(val);
                    }
                */

因此early_initrd()函数在start_kernel()–>setup_arch()–>parse_early_param();中调用传递的参数是initrd=0x42000000,0x60000 中的等号后面的内容。
early_initrd(char p)函数中获取initrd物理地址和大小(uboot在引导内核前会将initrd映像文件放在该位置)
填充:
phys_initrd_start = start; /
0x42000000 /
phys_initrd_size = size; /
0x60000 */

2.2 根据initrd的物理地址填充initrd虚拟地址

start_kernel()
  -->setup_arch(&command_line)
    -->arm_memblock_init()
      -->arm_initrd_init() /* 定义在 init/main.c */
        /*
            initrd_start = __phys_to_virt(phys_initrd_start);
            initrd_end = initrd_start + phys_initrd_size;
        */

2.3 准备rootfs根文件

init/main.c
start_kernel()
  -->vfs_caches_init(); /* 定义在 fs/dcache.c */
    -->mnt_init(); /* 定义在 fs/namespace.c */
      -->(1)init_rootfs(); /* 定义在 init/do_mounts.c */
        -->register_filesystem(&rootfs_fs_type); /* 定义在 fs/filesystems.c */
          /* rootfs_fs_type = { .name = "rootfs", .mount = rootfs_mount,};
             static struct file_system_type *file_systems;
             注册的实质是追加rootfs类型的文件系统到file_systems全局指针
           */
      -->(2)init_mount_tree(); /* 定义在 fs/namespace.c */
        -->vfs_kern_mount(type, 0, "rootfs", NULL);
           /* 创建相关数据结构,挂载根文件系统 */
        -->set_fs_pwd(current->fs, &root); /* 设置当前进程(current)的当前目录为rootfs */
        -->set_fs_root(current->fs, &root); /* 设置当前进程(current)的根目录为rootfs */

2.4 解压内存中的initrd文件,并将其填充到上述步骤中的rootfs中
init/initramfs.c中定义 rootfs_initcall(populate_rootfs);
// 在初始化阶段 __initcall__rootfs 阶段解压内存中的initrd, 并根据释放cpio格式的文件到rootfs中。
populate_rootfs()在何处调用的流程如下:
init/main.c

start_kernel()
  -->rest_init()
    -->kernel_thread(kernel_init, NULL, CLONE_FS);
      -->kernel_init()
        -->kernel_init_freeable()
          -->do_basic_setup();
            -->do_initcalls();
              -->do_initcall_level(level);
                -->do_one_initcall(initcall_t fn) /* 调用8个阶段的初始化函数 */

怎么好像没有调用 rootfs_initcall?
include/linux/init.h
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
#define ___define_initcall(fn, id, __sec)
static initcall_t _initcall##fn##id __used
attribute((section(#__sec “.init”))) = fn;
对于populate_rootfs()展开为:
static initcall_t __initcall_populate_rootfsrootfs = populate_rootfs;

populate_rootfs(void)
{
    /* Load the built in initramfs 如果有的话解压并释放跟内核打包到一块的initramfs */
    unpack_to_rootfs(__initramfs_start, __initramfs_size);
    /* 符号 __initramfs_start 定义在 include/asm-generic/vmlinux.lds.h
       符号 __initramfs_size 定义在 usr/initramfs_data.S
       该例子中没有将内核和initramfs打包在一起,因此__initramfs_size=0,不做解压
       即没有进入unpack_to_rootfs()中的while(len)进行解压,实际调试中没有打印调试信息
    */
    printk(KERN_INFO "Unpacking initramfs...\n");
    err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);
    if (err)
        printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
    /*
       解压 initrd_start 位置的initrd, 并将其填充到rootfs
       initrd_end定义在 init/do_mounts_initrd.c +14
    */
}

unpack_to_rootfs()函数就是所谓的释放initrd到rootfs

unpack_to_rootfs(char *buf, unsigned long len)
{
    decompress_fn decompress;
    decompress = decompress_method(buf, len, &compress_name); /* 返回解压函数指针 */
    decompress(buf, len, NULL, flush_buffer, NULL, &my_inptr, error);
}

unpack_to_rootfs()调用decompress()解压initrd, 不断调用flush_buffer()流式的获取到解压结果,使用状态机处理cpio格式的结果
参考这里
init/initramfs.c
注意:不要跟gzip具体解压操作中的状态机混淆
释放操作跟cpio格式密切相关
static __initdata int (*actions[])(void) = {
[Start] = do_start,
[Collect] = do_collect,
[GotHeader] = do_header,
[SkipIt] = do_skip,
[GotName] = do_name,
[CopyFile] = do_copy,
[GotSymlink] = do_symlink,
[Reset] = do_reset,
};
状态机的GotName状态操作函数do_name() 中根据不同类型的文件创建并写入文件。

2.5 执行initrd中 /init

init/main.c
start_kernel()
  -->rest_init()
    -->kernel_thread(kernel_init, NULL, CLONE_FS);
      -->kernel_init()
        -->kernel_init_freeable(); /* 该函数中赋值ramdisk_execute_command=/init */
          -->run_init_process(ramdisk_execute_command); /* 执行initrd中的/init */
            /* {
                  argv_init[0] = init_filename;
                  pr_info("Run %s as init process\n", init_filename);
                  return do_execve(init_filename, argv_init, envp_init);
               } */

附录1:
unpack_to_rootfs()调试信息及gzip格式的initrd具体的解压操作

unpack_to_rootfs(char *buf, unsigned long len)
{
        decompress = decompress_method(buf, len, &compress_name);
        pr_debug("Detected %s compressed data\n", compress_name);
        pr_err("[--debug--]compress_name: %s", compress_name);
        if (decompress) {
            pr_err("[--debug--]if decompress branch");
            int res = decompress(buf, len, NULL, flush_buffer, NULL,
                   &my_inptr, error);
            if (res)
                pr_err("decompressor failed");
        } else if (compress_name) {
            pr_err("[--debug--]else if compress_name branch");
            if (!message) {
                pr_err("[--debug--]if !message branch");
                snprintf(msg_buf, sizeof msg_buf,
                     "compression method %s not configured",
                     compress_name);
                message = msg_buf;
            }
        } else {
            pr_err("[--debug--]else branch not decompress not compress_name ");
            error("junk in compressed archive");
        }
        if (state != Reset){
            pr_err("[--debug--]if branch state != Reset");
            error("junk in compressed archive");
        }
}

加了调试信息,打印如下:
[    1.745849] Unpacking initramfs...
[    1.751335] [--debug--]compress_name: gzip // 第一次进入
[    1.751340] [--debug--]if decompress branch
[    2.002768] [--debug--]compress_name: (null)  // 应该是第二次进入
[    2.009165] [--debug--]else branch not decompress not compress_name
[    2.015992] Initramfs unpacking failed: junk in compressed archive

decompress_method(const unsigned char *inbuf, long len, const char **name)
static const struct compress_format compressed_formats[] __initconst = {
    { {0x1f, 0x8b}, "gzip", gunzip },
    
相当于调用 gunzip(buf, len, NULL, flush_buffer, NULL, &my_inptr, error);                
lib/decompress_inflate.c
STATIC int INIT gunzip(unsigned char *buf, long len,
               long (*fill)(void*, unsigned long),
               long (*flush)(void*, unsigned long),
               unsigned char *out_buf,
               long *pos,
               void (*error)(char *x))
{
    /* 传递下来的参数:fill=NULL, flush=flush_buffer, out_buf=NULL, pos=$my_inptr */
    return __gunzip(buf, len, fill, flush, out_buf, 0, pos, error);
}

/* 压缩解压算法参考这里 */

STATIC int INIT __gunzip(unsigned char *buf, long len,
               long (*fill)(void*, unsigned long),
               long (*flush)(void*, unsigned long),
               unsigned char *out_buf, long out_len,
               long *pos,
               void(*error)(char *x))
{
    /* 该函数中out_buf是malloc分配的,因此使用了内核的cache buffer机制,但是是如何追踪到解压后的文件系统的位置的呢? */
    out_len = 0x8000; /* 32 K */
    out_buf = malloc(out_len);
    rc = zlib_inflateInit2(strm, -MAX_WBITS);
    rc = zlib_inflate(strm, 0); // 解压buf中的gzip文件
    /* 定义在lib/zlib_inflate/inflate.c */
    zlib_inflateEnd(strm);
}

附录2:
挂载其他类型的根文件系统过程
1 cpio格式initrd的旧的处理方式

kernel_init_freeable(void)
  -->prepare_namespace();
    -->(1)initrd_load(); /* 定义在 init/do_mounts_initrd.c */
      -->(1)rd_load_image("/initrd.image") /* 解压并加载 /initrd.image 的内容到 /dev/ram */
            /*
               rd_load_image(char *from)
               {
                   out_fd = ksys_open("/dev/ram", O_RDWR, 0);
                   in_fd = ksys_open(from, O_RDONLY, 0);
                   for (i = 0, disk = 1; i < nblocks; i++) {
                       ksys_read(in_fd, buf, BLOCK_SIZE);
                       ksys_write(out_fd, buf, BLOCK_SIZE);
                   }
               }
            */
      -->(2)handle_initrd();
            /*
               挂载initrd到/root
               加载模块
               执行/linuxrc
               挂载真实根文件系统
               mount_root(void) /* 定义在 init/do_mounts.c */
               mount_block_root("/dev/root", root_mountflags);
               解挂initrd
               释放内存空间
            */
    -->(2)ksys_mount(".", "/", NULL, MS_MOVE, NULL);
    -->(3)ksys_chroot(".");


若真正的根文件系统挂载出错,则报相关信息:
mount_block_root(char *name, int flags)
{
    /* 该函数中打印报错信息
       printk("VFS: Cannot open root device \"%s\" or %s: error %d\n", root_device_name, b, err);
       printk("Please append a correct \"root=\" boot option; here are the available partitions:\n");
       panic("VFS: Unable to mount root fs on %s", b);
       列出可用的根块设备 */
}

2
在解析命令行流程(__setup())中解析出boot cmdline中的"root="的值赋值给saved_root_name
具体调用的root_dev_setup(char *line)函数,该函数定义在 init/do_mounts.c

init/do_mounts.h 文件中根据是否支持RAMDISK(即是否定义了CONFIG_BLK_DEV_RAM宏)定义不同的函数
unsigned long rd_size = CONFIG_BLK_DEV_RAM_SIZE;
解析 boot cmdline 中的 ramdisk_size 字段, 赋值给 rd_size 全局变量
没有ramdisk_size选项initrd也是OK的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值