initrd 文件系统

  目前正在处理initrd 升级;记录一下这个过程并总结。

内核代码执行流程:

  根据上篇转载文章; head.S执行过程中保存了bootloader传递的启动参数、启动模式以及FDT地址等,创建了内核空间的页表,最后为init进程初始化好了堆栈,并跳转到start_kernel执行;

此次主要看的是start_kernel的rest_init的主要流程.

static noinline void __init_refok rest_init(void)
{
    int pid;

    rcu_scheduler_starting();
    smpboot_thread_init();
    /*
     * We need to spawn init first so that it obtains pid 1, however
     * the init task will end up wanting to create kthreads, which, if
     * we schedule it before we create kthreadd, will OOPS.
     */
    kernel_thread(kernel_init, NULL, CLONE_FS);
    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();
    complete(&kthreadd_done);

    /*
     * The boot idle thread must execute schedule()
     * at least once to get things moving:
     */
    init_idle_bootup_task(current);
    schedule_preempt_disabled();
    /* Call into cpu_idle with preempt disabled */
    cpu_startup_entry(CPUHP_ONLINE);
}

- -rcu_scheduler_starting

  主要设置rcu_scheduler_active = RCU_SCHEDULER_INIT,标记调度器被激活。从rcu_scheduler_active 变量的注释可以看到:
rcu_scheduler_active最初的值为RCU_SCHEDULER_INACTIVE,在第一个task创建后转换为RCU_SCHEDULER_INIT(本函数),此时RCU做一些硬件初始化(?), 当初始化完毕后会将状态转换为RCU_SCHEDULER_RUNNING

- -kernel_thread(kernel_init, NULL, CLONE_FS);

  kernel_thread会调用do_fork来创建kernel_init进程也就是1号进程,此时kernel_init进程并未调度执行,直到schedule_preempt_disabled被执行。

- -pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

  创建kthreadd内核线程,它的任务就是管理和调度其他内核线程kernel_thread,进程号为2

- -complete(&kthreadd_done);

  表示kthreadd已经创建完毕,唤醒阻塞的kernel_init进程,但是要等到schedule()调用时才能执行

- -schedule_preempt_disabled

    sched_preempt_enable_no_resched();
    schedule();
    preempt_disable();

 

  调用schedule()函数切换当前进程,调用该函数1号进程(kernel_init)将被执行

|- -cpu_startup_entry(CPUHP_ONLINE)

调用cpu_idle(),当前cpu执行的0号进程进入idle函数的循环,在该循环中会周期性地检查

 

kernel_init

|--    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) {
|-------ret = run_init_process(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) {
        ret = run_init_process(execute_command);
    }
    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;

|- -kernel_init_freeable

 

static noinline void __init kernel_init_freeable(void)
{
    /*
     * Wait until kthreadd is all set-up.等待2号进程kthreadd创建完毕
     */
    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);//cpu唤醒其它cpu的准备工作,如设置其它cpu的启动地址

    do_pre_smp_initcalls();
  /*  遍历执行由early_initcall声明的函数。
从do_pre_smp_initcalls的代码可以看出,遍历执行位于__initcall_start和__initcall0_start之间的初始化函数。通过vmlinux.lds可以看到,
这部分位于.initcallearly.init段:*/

    lockup_detector_init();//watchdog死锁检测初始化
  /*1.idle_threads_init
     为每个非boot cpu都各fork一个idle task,将获得的task_struct记录到per_cpu变量idle_threads中
     2.cpuhp_threads_init
     为每个core都创建一个"cpuhp/%u"内核线程,结果记录在per_cpu变量cpuhp_state.thread中,然后启动当前cpu的"cpuhp/%u"线程:“cpuhp/0”
     线程处理函数为smpboot_thread_fn - percpu hotplug thread loop function     
     3.bringup_nonboot_cpus
     bringup未启动的cpu core
     */
    smp_init();
   
    sched_init_smp();

    page_alloc_init_late();
    /*
    do_basic_setup
        |--cpuset_init_smp();
        |--driver_init(); 
        |--init_irq_proc();       
        |--do_ctors(); 
        |--usermodehelper_enable();  
        |--do_initcalls();
               |....
               |--rootfs_initcall(populate_rootfs)
               |....
 do_basic_setup最主要的是会执行init段的函数,其中会调用到rootfs_initcall,它会将initrd释放到rootfs的/目录
    */
    do_basic_setup();
/* 通过filp_open("/dev/console", O_RDWR, 0)得到 struct file *file,然后又使用init_dup(file)执行了3次,一共得到了3个文件描述符。 init_dup(file)实际执行了如下操作: 设置current->files->fdt->fd[fd]为file,这里fd分别为0,1,2,就是所谓的:标准输入、标准输出、标准错误 */ /* 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_execute_command = "/init"; if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) { ramdisk_execute_command = NULL; prepare_namespace();//ramdisk_execute_command默认为/init, init_eaccess返回非0,因此会通过prepare_namespace执行挂载 } /* * 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 the public keys * and default modules */ integrity_load_keys(); load_default_modules(); }

 

run_init_process(ramdisk_execute_command);

  默认是 /init 进程

cpio initrd解包

  kernel_init->do_basic_setup->do_initcalls会遍历执行所有的init函数,这其中会执行populate_rootfs函数,populate_rootfs会将initrd释放到rootfs的“/”目录,目前来介绍start_kernel的cpio initrd解包的主要流程.

rest_init
    \--kernel_thread(kernel_init, NULL, CLONE_FS);
            |--kernel_init
            |      |--kernel_init_freeable
            |      |      |--do_basic_setup
            |      |      |      |--populate_rootfs 
            |     |      |         ---------------------------
            \--if (ramdisk_execute_command)
                    run_init_process(ramdisk_execute_command)                  

 

 

static int __init populate_rootfs(void)
{
    //char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
    char *err = NULL;
    printk(KERN_EMERG "Trying to unpack rootfs image as initramfs...\n");
    printk(KERN_EMERG "before %X %X %X %X %ld\n", __initramfs_start[0], __initramfs_start[1], __initramfs_start[2], __initramfs_start[3], __initramfs_size);

#ifdef CONFIG_BLK_DEV_INITRD_CRYPT // decrypt initrd
    decrypt_rootfs((u8*)__initramfs_start,
             __initramfs_size);
#endif

    printk(KERN_EMERG "after %X %X %X %X %ld\n", __initramfs_start[0], __initramfs_start[1], __initramfs_start[2], __initramfs_start[3], __initramfs_size);
/*加载initramfs, initramfs位于地址__initramfs_start处,是内核在编译过程中生成的,initramfs的是作为内核的一部分而存在的,
不是 boot loader加载的。前面提到了现在initramfs没有任何实质内容。
    */
    err = unpack_to_rootfs(__initramfs_start , __initramfs_size);
    if (err)
        panic("--------%s", err); /* Failed to decompress INTERNAL initramfs */
    if (initrd_start) {//判断是否加载了initrd。无论哪种格式的initrd,都会被boot loader加载到地址initrd_start处。
#ifdef CONFIG_BLK_DEV_RAM
        int fd;

        extern unsigned int initramfs_size;
        printk(KERN_INFO "Trying to unpack rootfs image as initramfs %ld %d...\n",initrd_end - initrd_start,initramfs_size);
#ifdef CONFIG_BLK_DEV_INITRD_CRYPT // decrypt initrd
        if((int)(initrd_end - initrd_start)>initramfs_size && initramfs_size!=0){
            initrd_end=initrd_start+initramfs_size;
        }
        decrypt_rootfs((u8*)initrd_start,
            initrd_end - initrd_start); //如果initrd被加密了 先解密
#endif
         /* 把它解压到前面挂载的根文件系统上,然后释放initrd占用的内存。
判断加载的是不是cpio-initrd。实际上 unpack_to_rootfs有两个功能一个是释放cpio包,另一个就是判断是不是cpio包, 
这是通过最后一个参数来区分的, 0:释放 1:查看。
 */
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start);
        if (!err) {//如果是cpio格式的initrd,
             /* 如果是cpio格式的initrd,把它解压到前面挂载的根文件系统上,然后释放initrd占用的内存。*/
            free_initrd();
            goto done;
        } else {
            clean_rootfs();
            unpack_to_rootfs(__initramfs_start, __initramfs_size);
        }
        
        /* 如果执行到这里,说明这是旧的块设备格式的initrd。
                         * 那么首先在前面挂载的根目录上创建一个initrd.image文件,
                         * 再把initrd_start到initrd_end的内容写入到/initrd.image中,
                         * 最后释放initrd占用的内存空间(它的副本已经保存到/initrd.image中了。)。
                         */
        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) {
            //如果不是cpio-initrd,则认为是一个image-initrd,将其内容保存到/initrd.image中。在后面的image-initrd的处理代码中会读取/initrd.image
            ssize_t written = xwrite(fd, (char *)initrd_start,
                        initrd_end - initrd_start);

            if (written != initrd_end - initrd_start)
                pr_err("/initrd.image: incomplete write (%zd != %ld)\n",
                       written, 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);//如果是cpio-initrd则将其内容释放出来到rootfs中。如果是initrd-image 也会释放到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);

  将initrd释放到rootfs,实际是将initrd下的目录和文件及链接在rootfs的根目录/下再创建一遍,之后把cpio initrd的文件内容拷贝过去

populate_rootfs
    |--unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start)
           |--while (!message && len)
                  decompress(buf, len, NULL, flush_buffer, NULL,...)//only once
                  write_buffer(buf, len)    

  本项目采用cpio initrd,调用unpack_to_rootfs对cpio initrd进行解包。unpack_to_rootfs最重要的是会调用write_buffer函数,它通过一个大循环对cpio initrd中打包进去的每一个文件进行处理。while循环首次会将cpio initrd的压缩文件进行解压缩。之后将通过write_buffer对解压缩后的cpio initrd进行处理。
write_buffer函数维护了一个状态机,不同的状态具有不同的处理函数

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,
};
  • Start:文件处理开始
  • Collect:通过读取cpio initrd中每个文件的cpio头来收集文件信息
  • GotHeader:对每个文件的cpio头进行解析,并保存到全局变量
  • GotName:通过上步获取的cpio头信息获取文件访问属性,对文件进行不同处理,如对于普通文件则在当前进程的当前目录(current为init_task,current->fs->pwd为rootfs的/目录)下创建此文件
  • CopyFile:将initrd中文件的内容拷贝到新创建的文件
  • Reset:重置,以准备下一个文件的处理

经过如上的步骤就完成了cpio initrd释放到rootfs的“/”目录,之后就可以通过/init来访问为init程序,并为之创建进程,也就是1号进程了。

  由于init是一个普通文件因此会调用sys_open(collected, openflags, mode)

SO:我们要写init这个可执行脚本或者进程, 里面执行一些操作然后执行switch_root

 

但是实际上对于:一个inird为loopback设备!!

  1.782997][ 4] Trying to unpack rootfs image as initramfs...
[    1.788757][ 4] before FFFFFFD2 FFFFFFC7 3B 1C 134
[    1.793573][ 4] Trying to decrypt rootfs ...
[    1.797865][ 4] after 1F FFFFFF8B 8 0 134
[    1.801915][ 4] Trying to unpack rootfs image as initramfs 9154349 0...
[    1.808538][ 4] Trying to decrypt rootfs ...
[    1.859486][ 4] rootfs image is not initramfs (no cpio magic); looks like an initrd
[    1.869202][ 1] [free_reserved_area] [6008]Freeing initrd memory: 8940K

其解压的时候 会报错表示为no cpio , 此时会继续将 initrd 解压到/initrd.image  然后 后面执行 prepare_namespace  继续 解压 initrd.imgage

老式的initrd的处理

prepare_namespace()用于处理老式的initrd。

initrd_load()执行以下步骤:

  • 调用create_dev()建立设备文件节点/dev/ram,其实这也是一个ramfs文件系统。

  • 调用rd_load_image()把/initrd.image加载到/dev/ram中。

  • 调用handle_initrd()把把块设备文件/dev/ram挂载到/root。

    /*
     * check if there is an early userspace init.  If yes, let it do all
     * the work
     */

    if (!ramdisk_execute_command)
        ramdisk_execute_command = "/init";

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
        prepare_namespace();
    }
int __init initrd_load(void)
{
    if (mount_initrd) {
        create_dev("/dev/ram", Root_RAM0);
        /*
         * Load the initrd data into /dev/ram0. Execute it as initrd
         * unless /dev/ram0 is supposed to be our actual root device,
         * in that case the ram disk is just set up here, and gets
         * mounted in the normal path.
         */
        if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {
            sys_unlink("/initrd.image");
            handle_initrd();
            return 1;
        }
    }
    sys_unlink("/initrd.image");
    return 0;
}

重新解压 ;按照 loopback方式解压!!!

 3.375785][ 6] RAMDISK: gzip image found at block 0

 

linux 使用程序udev来在/dev目录下建立各种设备文件。然而,系统引导时,udev并没有执行。那么,系统初始化时用到的/dev/console /dev/ram0从哪里来的呢?
见main.c init()函数中:

if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)

 

/dev/console来历:

 

[ ! -c /dev/console ] && mknod /dev/console c 5 1

 


/dev/ram 来历:

[init>prepare_namespace>initrd_load]
       Root_RAM0 = MKDEV(RAMDISK_MAJOR, 0),     create_dev(
"/dev/ram", Root_RAM0);



 

posted @ 2022-06-21 12:07  codestacklinuxer  阅读(81)  评论(0)    收藏  举报