轉自:https://www.chinahadoop.cn/group/15/thread/1786
這一篇我們來講解linux的initrd機制和initramfs機制之根文件掛載流程:代碼分析,希望大家認真學習!
linux-2.6.30
kernel_init
do_basic_setup(); // 批注1
if (!ramdisk_execute_command) // 批注2
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace(); // 批注3
}
init_post();
批注1:所有直接編譯在kernel中的模塊都是由它啟動的。這里有一個地方涉及到根文件系統的掛載
當配置了CONFIG_BLK_DEV_INITRD,在這里會調用函數populate_rootfs;
如果沒有配置populate_rootfs,則會調用函數default_rootfs
批注2:ramdisk_execute_command值通過“rdinit=”指定,如果未指定,則采用默認的值/init。
批注3:檢查根文件系統中是否存在文件ramdisk_execute_command,如果存在的話則執行init_post(),
否則執行prepare_namespace()掛載根文件系統。
先來說說上面批注1可能的兩個函數 populate_rootfs和default_rootfs
點擊(此處)折疊或打開
- static int __init populate_rootfs(void)
- {
- char *err = unpack_to_rootfs(__initramfs_start, // 批注1
- __initramfs_end - __initramfs_start);
- if (err)
- panic(err); /* Failed to decompress INTERNAL initramfs */
- if (initrd_start) { // 批注2
- #ifdef CONFIG_BLK_DEV_RAM
- int fd;
- printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
- err = unpack_to_rootfs((char *)initrd_start, // 批注3
- initrd_end - initrd_start);
- if (!err) {
- free_initrd();
- return 0;
- } else {
- clean_rootfs();
- unpack_to_rootfs(__initramfs_start,
- __initramfs_end - __initramfs_start);
- }
- 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); // 批注4
- if (fd >= 0) {
- sys_write(fd, (char *)initrd_start, // 批注5
- initrd_end - initrd_start);
- sys_close(fd);
- free_initrd(); // 批注6
- }
- #else
- 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);
- free_initrd();
- #endif
- }
- return 0;
- }
批注1:unpack_to_rootfs顧名思義,就是解壓包到rootfs,其具有兩個功能,一個是檢測
是否是屬於cpio包,另外一個就是解壓cpio包,通過最后一個參數進行控制。1:檢測,0:解壓。
其實,Initramfs也是壓縮過后的CPIO文件。initramfs位於地址__initramfs_start處,
是內核在編譯過程中生成的,initramfs的是作為內核的一部分而存在的,不是 boot loader加載的。
(編譯的時候通過連接腳本arch/arm/kernel/vmlinux.lds將其編譯到__initramfs_start~__initramfs_end,
執行完unpack_to_rootfs后將被拷貝到根目錄)。
批注2:判斷是否加載了initrd,無論哪種格式的initrd,都會被bootloader加載到地址initrd_start處。
批注3:判斷加載的是不是CPIO-Initrd。
批注4:如果不是CPIO-Initrd,則就是Image-Initrd,將其內容保存到文件/initrd.image中。在根文件系統中創建文件/initrd.image。
批注5:將內容保存到文件/initrd.image中
批注6:釋放Initrd所占用的內存空間。
default_rootfs()主要往rootfs中生成兩個目錄/dev和/root以及一個設備文件/dev/console。
下面來繼續看下面的函數:prepare_namespace
點擊(此處)折疊或打開
- void __init prepare_namespace(void)
- {
- int is_floppy;
- if (root_delay) { // 批注1
- printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
- root_delay);
- ssleep(root_delay);
- }
- /*
- * wait for the known devices to complete their probing
- *
- * Note: this is a potential source of long boot delays.
- * For example, it is not atypical to wait 5 seconds here
- * for the touchpad of a laptop to initialize.
- */
- wait_for_device_probe(); // 批注2
- md_run_setup();
- if (saved_root_name[0]) { // 批注3
- root_device_name = saved_root_name;
- if (!strncmp(root_device_name, "mtd", 3) ||
- !strncmp(root_device_name, "ubi", 3)) {
- mount_block_root(root_device_name, root_mountflags); // 批注4
- goto out;
- }
- ROOT_DEV = name_to_dev_t(root_device_name); // 批注5
- if (strncmp(root_device_name, "/dev/", 5) == 0)
- root_device_name += 5;
- }
- if (initrd_load()) // 批注6
- goto out;
- /* wait for any asynchronous scanning to complete */
- if ((ROOT_DEV == 0) && root_wait) { // 批注7
- printk(KERN_INFO "Waiting for root device %s...\n",
- saved_root_name);
- while (driver_probe_done() != 0 ||
- (ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
- msleep(100);
- async_synchronize_full();
- }
- is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
- if (is_floppy && rd_doload && rd_load_disk(0))
- ROOT_DEV = Root_RAM0;
- mount_root();
- out:
- sys_mount(".", "/", NULL, MS_MOVE, NULL); // 批注8
- sys_chroot("."); // 批注9
- }
批注1:對於將根文件系統存放到USB或者SCSI設備上的情況,Kernel需要等待這些耗費時間比較久的設備
驅動加載完畢,所以這里存在一個Delay。
批注2:等待根文件系統所在的設備探測函數的完成。
批注3:參數saved_root_name存放的是uboot參數root=所指定的設備文件。
批注4:將saved_root_nam指定的設備加載。
批注5:參數ROOT_DEV存放設備節點號。
批注6:掛載initrd,見下面的詳解。
批注7:如果指定mount_initrd為true,即沒有指定在函數initrd_load中mount的話,則在這里重新realfs的mount操作。
批注8:將掛載點從當前目錄(實際當前的目錄在mount_root中或者在mount_block_root中指定)移到根目錄。
即是如/dev/mtdblock2。
批注9:將當前目錄當作系統的根目錄,至此虛擬系統根目錄文件系統切換到了實際的根目錄文件系統。
點擊(此處)折疊或打開
- int __init initrd_load(void)
- {
- if (mount_initrd) { // 批注1
- create_dev("/dev/ram", Root_RAM0); // 批注2
- /*
- * 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) { // 批注3
- sys_unlink("/initrd.image");
- handle_initrd(); // 批注4
- return 1;
- }
- }
- sys_unlink("/initrd.image");
- return 0;
- }
批注1:可以通過Kernel的參數“noinitrd“來配置mount_initrd的值,默認為1,很少看到有項目區配置該值,
所以一般情況下,mount_initrd的值應該為1。
批注2:創建一個Root_RAM0的設備節點/dev/ram。
批注3:如果根文件設備號不是Root_RAM0則程序就會執行代碼就進入執行,如指定的/dev/mtdblock4設備節點
肯定就不是Root_RAM0。另外還將文件initrd.image釋放到節點/dev/ram0,也就是對應image-initrd的操作。
批注4:函數handle_initrd主要功能是執行Initrd中的linuxrc文件,並且將realfs的根目錄設置為當前
目錄。其實前面也已經提到了,這些操作只對image-cpio的情況下才會去執行。。
點擊(此處)折疊或打開
- static void __init handle_initrd(void)
- {
- int error;
- int pid;
- real_root_dev = new_encode_dev(ROOT_DEV); // 批注1
- create_dev("/dev/root.old", Root_RAM0); // 批注2
- /* mount initrd on rootfs' /root */
- mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);
- sys_mkdir("/old", 0700); // 批注3
- root_fd = sys_open("/", 0, 0);
- old_fd = sys_open("/old", 0, 0);
- /* move initrd over / and chdir/chroot in initrd root */
- sys_chdir("/root"); // 批注4
- sys_mount(".", "/", NULL, MS_MOVE, NULL);
- sys_chroot(".");
- /*
- * In case that a resume from disk is carried out by linuxrc or one of
- * its children, we need to tell the freezer not to wait for us.
- */
- current->flags |= PF_FREEZER_SKIP;
- pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD); // 批注5
- if (pid > 0)
- while (pid != sys_wait4(-1, NULL, 0, NULL))
- yield();
- current->flags &= ~PF_FREEZER_SKIP;
- /* move initrd to rootfs' /old */
- sys_fchdir(old_fd); // 批注6
- sys_mount("/", ".", NULL, MS_MOVE, NULL);
- /* switch root and cwd back to / of rootfs */
- sys_fchdir(root_fd);
- sys_chroot(".");
- sys_close(old_fd);
- sys_close(root_fd);
- if (new_decode_dev(real_root_dev) == Root_RAM0) { // 批注7
- sys_chdir("/old");
- return;
- }
- ROOT_DEV = new_decode_dev(real_root_dev); // 批注8
- mount_root();
- printk(KERN_NOTICE "Trying to move old root to /initrd ... ");
- error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);
- if (!error)
- printk("okay\n");
- else {
- int fd = sys_open("/dev/root.old", O_RDWR, 0);
- if (error == -ENOENT)
- printk("/initrd does not exist. Ignored.\n");
- else
- printk("failed\n");
- printk(KERN_NOTICE "Unmounting old root\n");
- sys_umount("/old", MNT_DETACH);
- printk(KERN_NOTICE "Trying to free ramdisk memory ... ");
- if (fd < 0) {
- error = fd;
- } else {
- error = sys_ioctl(fd, BLKFLSBUF, 0);
- sys_close(fd);
- }
- printk(!error ? "okay\n" : "failed\n");
- }
- }
批注1:real_root_dev為一個全局變量,用來保存realfs的設備號。
批注2:調用mount_block_root將realfs加載到VFS的/root下。
批注3:提取rootfs的根文件描述符並將其保存到root_fd,資料中提及其用處就是在后續調用
sys_chroot到initrd的文件系統后,處理完init請求后,還能夠再次切回到rootfs。
批注4:sys_chroot到initrd文件系統,前面已經掛載initrd到VFS的root目錄下。
批注5:執行initrd中的linuxrc,並等待執行結束。
批注6:initrd執行結束后,切回到rootfs。
批注7:如果real_root_dev直接配置為Root_RAM0,也即直接使用直接使用initrd作為realfs,
改變當前目錄到initrd中,並直接返回。
批注8:執行完Linuxrc后,realfs已經確定,則調用mount_root將realfs掛載到VFS的/root目錄下,
並將當前的目錄配置為VFS的/root。
再回過頭來再看上面提到的init_post,該函數實際上是在Kernel_init中最后執行的函數
static noinline int init_post(void)
__releases(kernel_lock)
if (ramdisk_execute_command)
if (execute_command)
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
可以看到,在該函數的最后,以此會去搜索文件並執行ramdisk_execute_command、execute_command、/sbin/init、
/etc/init、/bin/init和/bin/sh,如果發現這些文件均不存在的話,則通過panic輸出錯誤命令,並將當前的系統Halt在那里
大概總結一下流程:
內核會先創建一個基於內存的虛擬文件系統rootfs(和用什么機制無關);
根據采用的機制來掛載虛擬
文件系統,完成掛載真正文件系統前的工作;
掛載真正的文件系統;
執行最初的應用程序。