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);

浙公网安备 33010602011771号