轉自:http://news.migage.com/articles/%E6%96%87%E4%BB%B6%E5%AD%90%E7%B3%BB%E7%BB%9F%28rootfs%29%E6%A0%B9%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E6%8C%82%E8%BD%BD%E6%B5%81%E7%A8%8B03_3547130_csdn.html 文件子系統-(rootfs)根文件系統掛載流程03 Source NO1.linux內核啟動+Android系統啟動過程詳解 https://blog.csdn.net/ahaochina/article/details/72533442 NO2.內核的配置和編譯及代碼分析(一) https://blog.csdn.net/kakasingle/article/details/12851963?utm_source=jiancool NO3.linux的initrd機制和initramfs機制之initrd http://blog.chinaunix.net/uid-20279362-id-4924898.html http://cukdd.blog.chinaunix.net/uid-29431466-id-4834509.html NO4.android用initrd文件系統啟動流程 https://blog.csdn.net/wangcong02345/article/details/51659201 NO5.initrd(ramdisk)的內核處理流程 https://blog.csdn.net/yiyeguzhou100/article/details/78318100 linux文件系統-根文件系統(rootfs)掛載流程 文件系統的制作… rootfs掛載流程分析 默認根目錄已經掛上去了,此處為掛載具體的文件系統。 linux/init 不同類型掛載情況梳理: 1. 在initramfs或ramdisk的initramfs 注冊階段: start_kernel ->vfs_cache_init -->mnt_init --->init_rootfs ---->ini_mount_tree.//注冊類型為rootfs的fs(文件系統),掛載並設置rootfs為vfs?用戶?根目錄。 掛載階段: -rest_init --kernel_init --->do_basic_setup --->do_initcall(按優先級調用內核初始化啟動函數) ---->rootfs_initcall(populate_rootfs)(注冊populate_rootfs函數) ----->populate_rootfs(解壓initramfs到rootfs,initramfs必須包含init文件), ------>sys_access(kernel_init檢測init是否存在,如果有就不會調用prepare_namespace,即不會掛載其他文件系統了) --回到kernel_init --->run_init_process(ramdisk_execute_command);//即執行rootfs下的init 2. 沒有Ramdisk,假如使用mtd. sys_access(kernel_init檢測init是否存在,不存在這調用prepare_namespace). prepare_namespace -->mount_block_root(root_device_name, root_mountflags) //直接掛載mtd設備 --->回到kernel_init ---->執行try_to_run_init_process("/sbin/init") 3. 在ramdisk為initrd時 sys_access(kernel_init檢測init是否存在,不存在這調用prepare_namespace). ---->prepare_namespace ----->initrd_load ------->rd_load_image(加載initrd.image) ------->如果ROOT_DEV != Root_RAM0,則調用handle_init通過linuxrc啟動用戶態。(即initrd=0Xxxx,但是root=/dev/metdblock2矛盾導致) ------->如果ROOT_DEV = Root_RAM0,rd_load_image加載initrd.image后,通過mount_root掛載Root_RAM0 --------->**mount_root** //將/dev/ram0設備中的跟文件系統掛載到/root目錄,進入該目錄並將/root設置為當前目錄。 image-initrd &cpio-initrd 處理流程描述 - initrd的解釋是initialized RAM disk,就是啟動的時候由uboot來初始化內存,當做disk來使用。 - uboot啟動的時候,uboot會將存儲介質中(如Flash)的initrd文件加載到內存,內核啟動時會在訪問掛載的根文件系統前先訪問該內存中的initrd文件系統。 - 在uboot配置了initrd的情況下,文件系統啟動被分成兩個階段: *第一階段先執行initrd文件系統中的"某個可執行文件"(linuxrc或init,下面會講到具體會執行哪一個),完成加載驅動模塊等任務。 *第二階段是掛載真正的根文件系統中,然后執行/sbin/init進程 image-initrd (1) boot loader(一般大家常用的是grub,關於它的介紹可以到網上搜索)把 initrd.img 初始化成一個設備 /dev/intrd,接着boot loader 把內核以及/dev/initrd的內容加載到內存特定位置。 (2) 內核判斷initrd的文件格式,如果不是cpio格式,將其作為image-initrd處理,保存在rootfs下的/initrd.image文件中,隨后內核把 /dev/initrd 設備的內容解壓縮並拷貝到/dev/ram0設備中,也就是讀入了一個內存盤中。 (3) 內核以可讀寫的方式把 /dev/ram0 設備掛載為原始的根文件系統。 (4) 如果 /dev/ram0 被指定為真正的根文件系統,那么內核不會執行(5)、(6)、(7)的操作,因為這下操作是為了幫內核加載最終的根文件系統做的工作。 (root=/dev/ram的設置,是告訴內核我們最終要掛載的文見系統實際就是被拷貝的內存里的這個文件系統,不要讓內核再去費力去掛載其他的文件系統了) (5) 執行 initrd 上的 /linuxrc 文件,linuxrc 通常是一個腳本文件,負責加載內核訪問根文件系統必須的驅動,以及加載根文件系統。 (6) /linuxrc 執行完畢,真正的根文件系統被掛載,執行權交給內核。 (7) 如果真正的根文件系統存在/initrd 目錄,那么/dev/ram0將從/移動到 /initrd;否則 /dev/ram0 將被卸載。 (8) 在真正的根文件系統上進行正常啟動過程 ,執行 /sbin/init,對於image-initrd格式的鏡像,它執行的是linuxrc文件。 cpio-initrd (1) boot loader 把內核以及 initrd 文件加載到內存的特定位置 (2) 內核判斷initrd的文件格式,如果是cpio格式。 (3) 將initrd的內容釋放到/rootfs中。(rootfs本身也是一個基於內存的文件系統。這樣就省掉了ramdisk的掛載、卸載等) (4) 執行initrd中的/init文件,執行到這一點,內核的工作全部結束,完全交給/init文件處理。也就是其實到了最后一步,內核就已經完成了自己所有的工作,直接移交給initrd 的/init 兩種格式鏡像比較 1. cpio-initrd的制作方法比image-initrd簡單。 2. cpio-initrd的內核處理流程相比image-initrd更簡單,因為: a. 根據上面的流程對比可知,cpio-initrd格式的鏡像是釋放到rootfs中的,不需要額外的文件系統支持, 而image-initrd格式的鏡像先是被掛載成虛擬文件系統,而后被卸載,基於具體的文件系統 b. image-initrd內核在執行完/linuxrc進程后,還要返回執行內核進行一些收尾工作, 並且要負責執行真正的根文件系統的/sbin/init。 initrd initrd鏡像的制作 cpio-initrd格式鏡像制作: 進入到要制作的文件系統的根目錄; bash# find . | cpio -c -o > ../initrd.img bash# gzip ../initrd.img image-initrd格式鏡像制作: 進入到要制作的文件系統的根目錄; bash# dd if=/dev/zero of=../initrd.img bs=512k count=5 bash# mkfs.ext2 -F -m0 ../initrd.img bash# mount -t ext2 -o loop ../initrd.img /mnt bash# cp -r * /mnt bash# umount /mnt bash# gzip -9 ../initrd.img 對於image-initrd格式鏡像的制作,往往采用制作工具,如genext2fs 在這里插入圖片描述 ->rest_init() //加載initranfs文件系統包 -->kernel_init(void unused) --kernel_init_freeable(); //初始化設備驅動,加載靜態內核模塊,啟動所有直接編譯進內核的模塊。 ---do_basic_setup(); //啟動所有在__initcall_start和__initcall_end段的函數,靜態編譯進內核的modules也會將其入口放置在這段區間。 ----do_initcalls(); -----do_initcall_level(level); ------do_one_initcall(fn) |//注冊文件系統相關的初始化函數。 |--------rootfs_initcall(populate_rootfs); //加載不同類型的文件系統 ----------- populate_rootfs //(解壓initramfs系統包到rootfs中 -------------- unpack_to_rootfs rest_init 負責加載initrd文件,擴展VFS樹,創建基本的文件系統目錄拓撲; initrd是一個臨時文件系統,由bootload負責加載到內存中,里面包含了基本的可執行程序和驅動程序。在linux初始化的初級階段,它提供了一個基本的運行環境。當成功加載磁盤文件系統后,系統將切換到磁盤文件系統並卸載initrd tatic noinline void __ref rest_init(void) { struct task_struct *tsk; int pid; rcu_scheduler_starting(); /* * 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. */ pid = kernel_thread(kernel_init, NULL, CLONE_FS); /* * Pin init on the boot CPU. Task migration is not properly working * until sched_init_smp() has been run. It will set the allowed * CPUs for init to the non isolated CPUs. */ rcu_read_lock(); tsk = find_task_by_pid_ns(pid, &init_pid_ns); set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id())); rcu_read_unlock(); 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(); /* * Enable might_sleep() and smp_processor_id() checks. * They cannot be enabled earlier because with CONFIG_PRREMPT=y * kernel_thread() would trigger might_sleep() splats. With * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled * already, but it's stuck on the kthreadd_done completion. */ system_state = SYSTEM_SCHEDULING; complete(&kthreadd_done); /* * The boot idle thread must execute schedule() * at least once to get things moving: */ schedule_preempt_disabled(); /* Call into cpu_idle with preempt disabled */ cpu_startup_entry(CPUHP_ONLINE); } kernel_init static int __ref kernel_init(void *unused) { int ret; /*重點函數*/ kernel_init_freeable(); /* need to finish all async __init code before freeing the memory */ async_synchronize_full(); ftrace_free_init_mem(); free_initmem(); mark_readonly(); system_state = SYSTEM_RUNNING; numa_default_policy(); rcu_end_inkernel_boot(); //Initramfs從這里啟動init pr_emerg("run init\n"); //所以如果uboot傳過來的命令行參數有rdinit=xxx,則會執行 if (ramdisk_execute_command) { ret = run_init_process(ramdisk_execute_command); if (!ret) return 0; pr_err("Failed to execute %s (error %d)\n", ramdisk_execute_command, ret); } /* * 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. */ //從initrd、nfs和磁盤下啟動init //所以如果uboot傳過來的命令行參數有init=xxx,則會執行 if (execute_command) { ret = run_init_process(execute_command); if (!ret) return 0; panic("Requested init %s failed (error %d).", execute_command, ret); } //就會執行/sbin/init, /etc/init,, /bin/init,/bin/sh //如果uboot傳過來的命令行參數沒有init=xxx或者rdinit=xxx,則會執行該進程,一去不復返,后面的就不會執行了 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; panic("No working init found. Try passing init= option to kernel. " "See Linux Documentation/admin-guide/init.rst for guidance."); } static int __init init_setup(char *str) { unsigned int i; //execute_command獲取了init=xxx的值 execute_command = str; /* * In case LILO is going to boot us with default command line, * it prepends "auto" before the whole cmdline which makes * the shell think it should execute a script with such name. * So we ignore all arguments entered _before_ init=... [MJ] */ for (i = 1; i < MAX_INIT_ARGS; i++) argv_init[i] = NULL; return 1; } __setup("init=", init_setup); static int __init rdinit_setup(char *str) { unsigned int i; //ramdisk_execute_command獲取了rdinit= xxx 的值 ramdisk_execute_command = str; /* See "auto" comment in init_setup */ for (i = 1; i < MAX_INIT_ARGS; i++) argv_init[i] = NULL; return 1; } __setup("rdinit=", rdinit_setup); kernel_init_freeable 此處完成根目錄的設置 static noinline void __init kernel_init_freeable(void) { /* * 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]); cad_pid = task_pid(current); smp_prepare_cpus(setup_max_cpus); workqueue_init(); init_mm_internals(); do_pre_smp_initcalls(); lockup_detector_init(); smp_init(); sched_init_smp(); page_alloc_init_late(); /* Initialize page ext after all struct pages are initialized. */ page_ext_init(); do_basic_setup();//初始化設備驅動,加載靜態內核模塊;釋放Initramfs到rootfs test_executor_init(); /* 在rootfs上打開/ dev / console */ 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 */ //ramdisk_execute_command:在kernel解析引導參數的時候使用 //如果用戶指定了init文件路徑,即使用了“init=”,,一般不會出現該賦值。 if (!ramdisk_execute_command) //默認為是/init,即文件系統是initramfs或cpio-initrd ramdisk_execute_command = "/init"; //檢查此時跟文件系統中是否存在文件init,反之調用prepare_namespace()加載initfd、nfs或磁盤文件系統, //即磁盤的文件系統掛載至rootfs/root目錄,並設置系統current對應的根目錄項為磁盤根目錄項、系統current根文件系統為磁盤文件系統。 if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) { ramdisk_execute_command = NULL; //對於initramfs和cpio-initrd的情況,都會將文件系統(其實是一個VFS)解壓到根文件系統。如果不存在/init文件,則執行函數prepare_namespace() 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(); } do_basic_setup(); //初始化設備驅動,加載靜態內核模塊;釋放Initramfs到rootfs 調用所有模塊的初始化函數,包括initramfs的初始化函數populate_rootfs, 在這里執行了 populate_rootfs,即檢測initrd的類型並且將其釋放到目錄中 函數populate_rootfs 通過rootfs_initcall(populate_rootfs)導出. static void __init do_basic_setup(void) { cpuset_init_smp(); shmem_init(); driver_init(); init_irq_proc(); do_ctors(); usermodehelper_enable(); // 內核模塊的初始化函數都在這里執行 do_initcalls(); } void __init driver_init(void) { /* These are the core pieces */ devtmpfs_init();// 初始化devtmpfs devices_init(); // 初始化sysfs buses_init(); // sys/bus sys/system classes_init(); // sys/class firmware_init();// sys/firmware 設備樹放在這里 hypervisor_init(); /* These are also core pieces, but must come after the * core core pieces. */ platform_bus_init();// 初始化platform_bus cpu_dev_init(); // 初始化驅動模型中的devices/system/cpu子系統 memory_dev_init(); container_dev_init(); of_core_init(); } do_initcalls(); initcalls機制 do_initcalls()將按順序從由__initcall_start開始,到__initcall_end結束的section中以函數指針的形式取出這些編譯到內核的驅動模塊中初始化函數起始地址,來依次完成相應的初始化。而這些初始化函數由__define_initcall(level,fn)指示編譯器在編譯的時候,將這些初始化函數的起始地址值按照一定的順序放在這個section中 initcall簡介 linux在代碼段中定義了一個特殊的段initcall,該段中存放的都是函數指針;linux初始化階段調用do_initcalls()依次執行該段的函數。關於該段的詳細信息可以參見vmlinux.lds.S鏈接腳本 https://www.cnblogs.com/downey-blog/p/10486653.html https://blog.csdn.net/mcsbary/article/details/90644101 1. 分析 __define_initcall(level,fn) 宏定義 kernel4.14/include/linux/init.h //__define_initcall(fn, n),n是一個數字或者是數字+s,這個數字代表這個fn執行的優先級,數字越小,優先級越高,帶s的fn優先級低於不帶s的fn優先級。 #define early_initcall(fn) __define_initcall(fn, early) #define pure_initcall(fn) __define_initcall(fn, 0) #define core_initcall(fn) __define_initcall(fn, 1) #define core_initcall_sync(fn) __define_initcall(fn, 1s) #define postcore_initcall(fn) __define_initcall(fn, 2) #define postcore_initcall_sync(fn) __define_initcall(fn, 2s) #define arch_initcall(fn) __define_initcall(fn, 3) #define arch_initcall_sync(fn) __define_initcall(fn, 3s) #define subsys_initcall(fn) __define_initcall(fn, 4) #define subsys_initcall_sync(fn) __define_initcall(fn, 4s) #define fs_initcall(fn) __define_initcall(fn, 5) #define fs_initcall_sync(fn) __define_initcall(fn, 5s) #define rootfs_initcall(fn) __define_initcall(fn, rootfs) #define device_initcall(fn) __define_initcall(fn, 6) #define device_initcall_sync(fn) __define_initcall(fn, 6s) #define late_initcall(fn) __define_initcall(fn, 7) #define late_initcall_sync(fn) __define_initcall(fn, 7s) #define __define_initcall(fn, id) \ static initcall_t __initcall_name(fn, id) __used \ __attribute__((__section__(".initcall" #id ".init"))) = fn; //其中initcall_是一個函數指針類型:typedef int (*initcall_t)(void); //#define __initcall_name(fn, id) __initcall_##fn##id (其中##表示替換連接) //而屬性__attribute__((__section__(".initcall" #id ".init"))) 則表示把對象放在一個這個由括號中的名稱所指代的section中,表示編譯時將目標符號放置在括號指定的段中 // 而#在宏定義中的作用是將目標字符串化,##在宏定義中的作用是符號連接,將多個符號連接成一個符號,並不將其字符串化。 1) 聲明一個名稱為__initcall_##fn##id的函數指針(其中##表示替換連接,); 2) 將這個函數指針初始化為fn; 3) 編譯的時候需要把這個函數指針變量放置到名稱為 ".initcall" #id ".init" 的section中(比如id=rootfs,代表這個section的名稱是 ".initcallrootfs.init") 4) #define rootfs_initcall(fn) __define_initcall(fn, rootfs) .聲明一個函數指針__initcall_fn_rootfs = fn; 5) rootfs_initcall(populate_rootfs) 2.do_initcalls()的執行 //定義一個靜態的initcall_levels數組,這是一個指針數組,數組的每個元素都是一個指針。 //do_initcalls()循環調用do_initcall_level(level),level就是initcall的優先級數字,由for循環的終止條件ARRAY_SIZE(initcall_levels) - 1可知,總共會調用do_initcall_level(0)~do_initcall_level(7),一共七次。 //do_initcall_level(level)中則會遍歷initcall_levels[level]中的每個函數指針,initcall_levels[level]實際上是對應的__initcall##level##_start指針變量,然后依次取出__initcall##level##_start指向地址存儲的每個函數指針,並調用do_one_initcall(*fn),實際上就是執行當前函數。 //__initcall##level##start所存儲的函數指針就是開發者用xxx_initcall()宏添加的函數,對應".initcall##level##.init"段 //do_one_initcall(*fn)的執行:判斷initcall_debug的值,如果為真,則調用do_one_initcall_debug(fn);如果為假,則直接調用fn。事實上,調用do_one_initcall_debug(fn)只是在調用fn的基礎上添加一些額外的打印信息,可以直接看成是調用fn extern initcall_t __initcall_start[]; extern initcall_t __initcall0_start[]; extern initcall_t __initcall1_start[]; extern initcall_t __initcall2_start[]; extern initcall_t __initcall3_start[]; extern initcall_t __initcall4_start[]; extern initcall_t __initcall5_start[]; extern initcall_t __initcall6_start[]; extern initcall_t __initcall7_start[]; extern initcall_t __initcall_end[]; //定義一個靜態的initcall_levels數組,這是一個指針數組,數組的每個元素都是一個指針。 static initcall_t *initcall_levels[] __initdata = { __initcall0_start, __initcall1_start, __initcall2_start, __initcall3_start, __initcall4_start, __initcall5_start, __initcall6_start, __initcall7_start, __initcall_end, }; /* Keep these in sync with initcalls in include/linux/init.h */ static char *initcall_level_names[] __initdata = { "early", "core", "postcore", "arch", "subsys", "fs", "device", "late", }; do_initcall_level static void __init do_initcall_level(int level) { initcall_t *fn; strcpy(initcall_command_line, saved_command_line); //bootloader 通過setup_initrd_tag函數把initrd_start設置到內核 tag中,內核通過parse_tag解析 parse_args(initcall_level_names[level], initcall_command_line, __start___param, __stop___param - __start___param, level, level, NULL, &repair_env_string); //fn為函數指針,fn++相當於函數指針+1,相當於:內存地址+sizeof(fn),sizeof(fn)根據平台不同而不同,一般來說,32位機上是4字節,64位機則是8字節 for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) do_one_initcall(*fn); } do_initcalls // 內核模塊的初始化函數都在這里執行 // 根據調用級別,從level0-leveln分別執行內核模塊的初始化函數 static void __init do_initcalls(void) { int level; for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) do_initcall_level(level); } 3.vmlinux.lds.h的調用 kernel/kernel4.14/include/asm-generic/vmlinux.lds.h //在這里首先定義了__initcall_start,將其關聯到".initcallearly.init"段, //然后對每個level定義了INIT_CALLS_LEVEL(level),將INIT_CALLS_LEVEL(level)展開之后的結果是定義__initcall##level##_start,並將__initcall##level##_start關聯到".initcall##level##.init"段和".initcall##level##s.init"段。 //__initcall##level##_start和".initcall##level##.init"段的對應就比較清晰了,所以,從initcall_levels[level]部分一個個取出函數指針並執行函數就是執行xxx_init_call()定義的函數 #define INIT_CALLS_LEVEL(level) \ VMLINUX_SYMBOL(__initcall##level##_start) = .; \ KEEP(*(.initcall##level##.init)) \ KEEP(*(.initcall##level##s.init)) \ #define INIT_CALLS \ VMLINUX_SYMBOL(__initcall_start) = .; \ KEEP(*(.initcallearly.init)) \ INIT_CALLS_LEVEL(0) \ INIT_CALLS_LEVEL(1) \ INIT_CALLS_LEVEL(2) \ INIT_CALLS_LEVEL(3) \ INIT_CALLS_LEVEL(4) \ INIT_CALLS_LEVEL(5) \ INIT_CALLS_LEVEL(rootfs) \ INIT_CALLS_LEVEL(6) \ INIT_CALLS_LEVEL(7) \ VMLINUX_SYMBOL(__initcall_end) = .; 4.vmlinux.lds.S 之 INIT_CALLS kernel/kernel4.14/arch/arm64/kernel/vmlinux.lds.S SECTIONS{ .init.data : { INIT_DATA INIT_SETUP(16) INIT_CALLS CON_INITCALL SECURITY_INITCALL INIT_RAM_FS *(.init.rodata.* .init.bss) /* from the EFI stub */ KUNIT_TEST_MODULES } } 用戶使用不同優先級的initcall宏可以很方便的在linux代碼中注冊函數指針;將這些函數指針存儲在相應的initcall段中;最終,由do_initcalls()按照優先級依次執行段中的函數. 5.initcall機制舉例 #define core_initcall(fn) __define_initcall(fn, 1) // core_initcall(beagle_init) //core_initcall(beagle_init)宏展開為__define_initcall(beagle_init, 1),所以beagle_init()這個函數被放置在".initcall1.init"段處 //在內核啟動時,系統會調用到do_initcall()函數。 根據指針數組initcall_levels[1]找到__initcall1_start指針,在vmlinux.lds.h可以查到:__initcall1_start對應".initcall1.init"段的起始地址,依次取出段中的每個函數指針,並執行函數 //而initcall_levels[level]指向當前".initcall##level##s.init"段,initcall_levels[level+1]指向".initcall##(level+1)##s.init"段,兩個段之間的內存就是存放所有添加的函數指針. 1) __define_initcall(level,fn)的作用就是指示編譯器把一些初始化函數的指針(即:函數起始地址)按照順序放置一個名為 .initcall.init 的section中,這個section又被分成了若干個子section,它們按順序排列。在內核初始化階段,這些放置到這個section中的函數指針將供do_initcalls() 按順序依次調用,來完成相應初始化。 2) 函數指針放置到的子section由宏定義的level確定,對應level較小的子section位於較前面。而位於同一個子section內的函數指針順序不定,由編譯器按照編譯的順序隨機指定。 3) 因此,如果你希望某個初始化函數在內核初始化階段就被調用,那么你 就應該使用宏__define_initcall(level,fn) 或 其幾個衍生宏 把這個函數fn的對應的指針放置到按照初始化的順序放置到相關的 section 中。如果某個初始化函數fn_B需要依賴於另外一個初始化函數fn_A的完成,那么你應該把fn_B放在比fn_A對應的level值較大的子section中, 這樣,do_initcalls()將在fn_A之后調用fn_B 4) 如果你希望某個初始化函數在內核初始化階段就被調用,那么你就應該使用宏__define_initcall(level,fn) 或 其7個衍生宏來把這個初始化函數fn的起始地址按照初始化的順序放置到相關的section 中。 內核初始化時的do_initcalls()將從這個section中按順序找到這些函數來執行。 populate_rootfs rootfs_initcall(populate_rootfs) populate_rootfs主要完成Initrd的檢測工作,檢查出是CPIO Initrd還是Initramfs還是Image-Initrd 一種是跟kernel融為一體的initramfs.在編譯kernel的時候,通過鏈接腳本將其存放在__initramfs_start至__initramfs_end的區域,直接調用unpack_to_rootfs將其釋放到根目錄。如果不是屬於這種形式的,也就是__initramfs_start和__initramfs_end的值相等,長度為零。不會做任何處理 a. 檢測是否存在initram格式的文件系統,存在則直接釋放到原始的rootfs “/“目錄。 b. 檢測是cpio-initrd還是image-initrd,並分別處理。 c. 若是cpio-init則直接釋放到”/ ”目錄,反之將image-initrd保存在initrd.image。 d. 完成c步驟並釋放Initrd所占用的內存空間。 static int __init populate_rootfs(void) { char *err; //判斷是否加載default_rootfs if (do_skip_initramfs) { if (initrd_start) free_initrd(); return default_rootfs(); } //第一檢測是否initramfs文件系統 //若是的,將位於__initramfs_end - __initramfs_start的initramfs段釋放到“/”目錄下 err = unpack_to_rootfs(__initramfs_start, __initramfs_size); if (err) panic("%s", err); /* Failed to decompress INTERNAL initramfs */ //第二檢測是cpio-initrd還是image-initrd無論這兩種格式,uboot都會把它加載到內存里的initrd_start地址處 //IS_ENABLED(CONFIG_INITRAMFS_FORCE) :存在參數配置y或m,則返回1,反之返回0 if (initrd_start && !IS_ENABLED(CONFIG_INITRAMFS_FORCE)) { //必須要配制CONFIG_BLK_DEV_RAM才會支持image-initrd #ifdef CONFIG_BLK_DEV_RAM int fd; printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n"); //判斷加載的是不是initramfs CPIO文件,若是直接將解壓到“/”目錄下,解壓成功返回0。 err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start); if (!err) { //解壓成功,釋放image中initrd地址處的對應內存 free_initrd(); goto done;//然后直接跳轉到done標號: } else { clean_rootfs(); unpack_to_rootfs(__initramfs_start, __initramfs_size); } //如果執行到這里,就說明是image-initrd,將其內容保存到文件/initrd.image中。 printk(KERN_INFO "rootfs image is not initramfs (%s)" "; looks like an initrd\n", err); //在根文件系統中創建文件/initrd.image fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700); if (fd >= 0) { //將intird_start到initrd_end內容保存到/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); //關閉文件並釋放image中initrd對應內存 sys_close(fd); free_initrd(); } done: /* empty statement */; #else //不存在image-initrd 說明此時是cpio-initrd文件 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 } flush_delayed_fput(); /* * 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); unpack_to_rootfs(char *buf, unsigned long len) 如果initramfs不存在,__initramfs_start和__initramfs_end的值相等即參數 len=0,unpack_to_rootfs不會做任何處理. 它通過__initramfs_start指針和__initramfs_end指針訪問XXX.cpio.gz文件, 調用函數unpack_to_rootfs函數把源文件解壓到rootfs中 static char * __init unpack_to_rootfs(char *buf, unsigned long len) { long written; decompress_fn decompress; const char *compress_name; static __initdata char msg_buf[64]; header_buf = kmalloc(110, GFP_KERNEL); symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL); name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL); if (!header_buf || !symlink_buf || !name_buf) panic("can't allocate buffers"); state = Start; this_header = 0; message = NULL; while (!message && len) { loff_t saved_offset = this_header; if (*buf == '0' && !(this_header & 3)) { state = Start; written = write_buffer(buf, len); buf += written; len -= written; continue; } if (!*buf) { buf++; len--; this_header++; continue; } this_header = 0; //根據buf的第1、2個字節的magic來判斷decompress類型。比如這里對應gzip,所以返回值decompress及對應gunzip() decompress = decompress_method(buf, len, &compress_name); pr_debug("Detected %s compressed data\n", compress_name); if (decompress) { int res = decompress(buf, len, NULL, flush_buffer, NULL, &my_inptr, error); if (res) error("decompressor failed"); } else if (compress_name) { if (!message) { snprintf(msg_buf, sizeof msg_buf, "compression method %s not configured", compress_name); message = msg_buf; } } else error("junk in compressed archive"); if (state != Reset) error("junk in compressed archive"); this_header = saved_offset + my_inptr; buf += my_inptr; len -= my_inptr; } dir_utime(); kfree(name_buf); kfree(symlink_buf); kfree(header_buf); return message; } root_dev_setup root用於制定根文件系統所在的存儲設備,並把設備名稱保存到靜態變量saved_root_name中 __setup(“root=”, root_dev_setup);機制 static char __initdata saved_root_name[64]; static int __init root_dev_setup(char *line) { strlcpy(saved_root_name, line, sizeof(saved_root_name)); return 1; } __setup(“root=”, root_dev_setup); fs_names_setup rootfstype指定跟文件系統的類型,把跟文件系統的類型保存在靜態變量root_fs_names。 static char * __initdata root_fs_names; static int __init fs_names_setup(char *str) { root_fs_names = str; return 1; } __setup(“rootfstype=”, fs_names_setup); //由於之前已經切換到新的跟文件系統的根目錄中去,所以out標簽下這幾步主要是將新的跟文件系統的根目錄 替換rootfs,使其成為linux的vfs的根目錄。 //不使用initrd情況下的流程,跟文件系統設備已經掛載上,且替代了rootfs成為vfs的根, 剩下的任務就是留給根文件系統中的/sbin/init程序。 prepare_namespace(); 如果沒有使用到 cpio 類型的 initrd,內核會執行 prepare_namespace() 函數路徑:kernel/kernel4.14/init/do_mounts.c ** 掛載實際根文件系統** a. 對於image-initrd,有兩中掛載設備,一種是root=/dev/mtdblockxx 一種是root=/dev/ram設備 首先用戶可以用root=來指定根文件系統。它的值保存在saved_root_name中。如果用戶指定了以mtd開始的字串做為它的根文件系統。就會直接去掛載。這個文件是mtdblock的設備文件,否則將設備結點文件轉換為ROOT_DEV即設備節點號。然后,轉向initrd_load()執行initrd預處理后,再將具體的根文件系統掛載。函數末尾,會調用sys_mount()來移動當前文件系統掛載點到”/”目錄下,然后將根目錄切換到當前目錄,根文件系統的掛載點就成為了我們在用戶空間所看到的”/”. initrd_load創建Root_RAM0類型的/dev/ram0設備節點(就是ramdisk設備節點) 調用rd_load_image將文件/initrd.image加載進/dev/ram0, 之后刪除文件initrd.image。 隨后調用handle_initrd將ramdisk節點/dev/ram0 mount到/root目錄, 進入/root目錄,將當前目錄mount為根目錄, 最后切換當前目錄為程序執行所參考的根目錄位置。 待分析知識 _setup 提取 save_root_name //prepare_namespace() 在 do_mounts.c 中定義,它主要負責掛載根文件系統和 ramdisk類型的initrd. > Root_RAM0定義在kernel4.14/include/linux/root_dev.h void __init prepare_namespace(void) { int is_floppy; //對於將根文件系統存放到USB或者SCSI設備上的情況,Kernel需要等待這些耗費時間比較久的設備驅動加載完畢,所以這里存在一個Delay。 if (root_delay) { printk(KERN_INFO "Waiting %d sec before mounting root device...\n", root_delay); ssleep(root_delay); } /********************************/ //首先會輪訓檢測塊設備,若檢測到則創建一個設備節點,然后分配一個設備號 //等待根文件系統所在的設備檢測函數的完成 wait_for_device_probe(); //掛接md設備,安裝md設備驅動 md_run_setup(); dm_run_setup(); /********************************/ //_setup提取save_root_name待分析 //save_root_name存放root=指定設備文件,作為當前系統的跟文件系統設備名。如:“root=/dev/ram0”使用initrd作為跟文件系統,由_setup宏提取。 if (saved_root_name[0]) { //此時已經存在參數值 root_device_name = saved_root_name; /**************************************************************/ //如果存儲設備為閃存分區(設備名稱以“mtd”開頭,)或是在閃存分區基礎上封裝的ubi設備,那么調用mount_block_root就會直接把根文件系統掛載到rootfs文件系統的目錄/root下。 if (!strncmp(root_device_name, "mtd", 3) || !strncmp(root_device_name, "ubi", 3)) { //這里相當於將saved_root_nam指定的設備/dev/mtdblock2進行加載。如下面傳遞給內核的command line: CONFIG_CMDLINE="console=ttyS0,115200 mem=108M rdinit=/linuxrc root=/dev/mtdblock2" mount_block_root(root_device_name, root_mountflags); goto out; } /***********************************************************************************/ //ROOT_DEV實現待分析 //否則將/dev/ram轉換為根文件系統設備號保存全局變量ROOT_DEV中。 ROOT_DEV = name_to_dev_t(root_device_name); //如果跟文件系統設備名的前5個字符是‘/dev/‘ if (strncmp(root_device_name, "/dev/", 5) == 0) //將跟文件系統設備名指向/dev/之后的設備名字 root_device_name += 5; } //經initrd_load()執行initrd預處理后,再將具體的根文件系統掛載。 //若指定了內核參數root=/dev/ram,則return false,繼續執行進行掛載,目錄切換等。 //若沒指定root=/dev/ram則在initrd_load()內部轉進handinit進行處理,然后返回true,隨后直接到out. //加載initrd 並初始化內存盤文件系統 if (initrd_load()) goto out; /*********************************************************************************/ //root_wait待分析 /* wait for any asynchronous scanning to complete */ if ((ROOT_DEV ==0 ) && root_wait) { 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(5); async_synchronize_full(); } /***********************************************************************************/ //如果跟文件系統設備號的主設備號等於軟盤的主設備號FLOPPY_MAJOR(=2),那么is_floppy=1 is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR; //如果跟設備是軟盤,並且rd_doload不等於0,並且rd_load_disk操作成功,rd_doload初始設置為0, 可用load_ramdisk=修改,ra_load_disk函數檢查/dev/root文件系統類型,如果是romfs或cramfs 或ext2或minix文件系統,將initrd.image直接讀取到/dev/ram設備中。如果/dev/root是壓縮文件,則將壓縮文件解壓縮后寫到/dev/ram設備中,如果不是上述四種文件系統之一,則rd_load_image返回0,成功返回1. if (is_floppy && rd_doload && rd_load_disk(0)) ROOT_DEV = Root_RAM0; //將Root_RAM0即/dev/ram0設置成跟文件系統設備 //將實際的Root_RAM0根文件系統掛載到/root目錄,進入該目錄並將/root設置為當前目錄。 mount_root(); //之前切換到新的根文件系統的根目錄中去,下面是用新根文件系統的根目錄替換rootfs,使其成為LinuxVFS的根目錄。 out: //在driver_init處初始化,這里掛載devtmpfs文件系統 devtmpfs_mount("dev"); //將掛載點從當前目錄(實際當前的目錄在mount_root中或者在mount_block_root中指定)移到根目錄。對於上面的command line的話,當前的目錄就是/dev/mtdblock2,或是/dev/ram0。 //將掛載點從當前目錄/dev/ram0,移動到”/”目錄下 sys_mount(".", "/", NULL, MS_MOVE, NULL); //將當前目錄 ’.‘作為系統的根目錄,這樣就將/dev/ram0實際掛接到“/”目錄下,並且把“/”目錄作為系統的根目錄,它的虛擬文件系統掛接結構(struct vfsmount)指針mnt作為所有進程描述結構的tsk->fs->rootmnt,它的目錄條目結構(struct dentry)指針dentry作為進程描述結構的tsk->fs->root //移動當前文件系統掛載點到”/”目錄下,然后將根目錄切換到當前目錄。這樣,根文件系統的掛載點就成為了我們在用戶空間所看到的”/ //將當前目錄當作系統的根目錄,至此虛擬系統根目錄文件系統切換到了實際的根目錄文件系統 sys_chroot("."); } //切換到了新的根文件系統的根目錄中去,所以這兩步的作用是用新的根文件系統的根目替換rootfs,使其成為linuxVFS的根目錄。 initrd_load a. 調用create_dev創建一個/dev/ram設備節點 b. 調用rd_load_image將initrd.image釋放到/dev/ram0,並判斷用戶有沒有指定最終的根文件設備名。 c. 如果沒有指定的不是Root_ram0,則轉入handle_initrd函數進行處理。 詳細代碼分析如下: bool __init initrd_load(void) { //通過Kernel的參數“noinitrd“來配置mount_initrd的值,默認為1,是否要加載 initrd 的標志,當內核啟動參數中包含 noinitrd 字符串時,mount_initrd 會被設為0。 if (mount_initrd) { //為把initrd釋放到內存盤中,需要創建ROOT_RAM設備節點,並將/initrd/.image釋放到這個節點中。 create_dev("/dev/ram", Root_RAM0); //1.調用rd_load_image將initrd.image釋放到/dev/ram0, //2.將initrd.image釋放到節點/dev/ram0,如果根文件設備號不是ROOT_RAM0(用戶指定不是/dev/ram0,轉入handle_initrd), //2.1換句話說,就是給內核指定的參數不是/dev/ram,例如上面指定的/dev/mtdblock2設備節點肯定就不是Root_RAM0。 if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) { sys_unlink("/initrd.image"); //函數handle_initrd主要執行Initrd中的linuxrc文件,並且將真實的根目錄設置為當前目錄. handle_initrd(); return true; } } sys_unlink("/initrd.image"); return false; } rd_load_image a. 分別打開(initrd_load在rootfs中的創建的/dev/ram0設備節點) 和 (ramdisk鏡像(“initrd.image”))。 b. nblocks = identify_ramdisk_image(in_fd, rd_image_start, &decompressor);? c. 調用crd_load將initrd.image內容加載到/dev/ram0設備節點中。 int __init rd_load_image(char *from) { int res = 0; int in_fd, out_fd; unsigned long rd_blocks, devblocks; int nblocks, i, disk; char *buf = NULL; unsigned short rotate = 0; decompress_fn decompressor = NULL; #if !defined(CONFIG_S390) char rotator[4] = { '|' , '/' , '-' , '\\' }; #endif //打開rootfs /dev/ram0設備節點 out_fd = sys_open("/dev/ram", O_RDWR, 0); if (out_fd < 0) goto out; //打開rootfs中的ramdisk鏡像(“initrd.image”) in_fd = sys_open(from, O_RDONLY, 0); if (in_fd < 0) goto noclose_input; nblocks = identify_ramdisk_image(in_fd, rd_image_start, &decompressor); if (nblocks < 0) goto done; if (nblocks == 0) { //加載initrd.image 到 /dev/ram0 if (crd_load(in_fd, out_fd, decompressor) == 0) goto successful_load; goto done; } /* * NOTE NOTE: nblocks is not actually blocks but * the number of kibibytes of data to load into a ramdisk. */ if (sys_ioctl(out_fd, BLKGETSIZE, (unsigned long)&rd_blocks) < 0) rd_blocks = 0; else rd_blocks >>= 1; if (nblocks > rd_blocks) { printk("RAMDISK: image too big! (%dKiB/%ldKiB)\n", nblocks, rd_blocks); goto done; } /* * OK, time to copy in the data */ if (sys_ioctl(in_fd, BLKGETSIZE, (unsigned long)&devblocks) < 0) devblocks = 0; else devblocks >>= 1; if (strcmp(from, "/initrd.image") == 0) devblocks = nblocks; if (devblocks == 0) { printk(KERN_ERR "RAMDISK: could not determine device size\n"); goto done; } buf = kmalloc(BLOCK_SIZE, GFP_KERNEL); if (!buf) { printk(KERN_ERR "RAMDISK: could not allocate buffer\n"); goto done; } printk(KERN_NOTICE "RAMDISK: Loading %dKiB [%ld disk%s] into ram disk... ", nblocks, ((nblocks-1)/devblocks)+1, nblocks>devblocks ? "s" : ""); for (i = 0, disk = 1; i < nblocks; i++) { if (i && (i % devblocks == 0)) { printk("done disk #%d.\n", disk++); rotate = 0; if (sys_close(in_fd)) { printk("Error closing the disk.\n"); goto noclose_input; } change_floppy("disk #%d", disk); in_fd = sys_open(from, O_RDONLY, 0); if (in_fd < 0) { printk("Error opening disk.\n"); goto noclose_input; } printk("Loading disk #%d... ", disk); } sys_read(in_fd, buf, BLOCK_SIZE); sys_write(out_fd, buf, BLOCK_SIZE); #if !defined(CONFIG_S390) if (!(i % 16)) { pr_cont("%c\b", rotator[rotate & 0x3]); rotate++; } #endif } printk("done.\n"); successful_load: res = 1; done: sys_close(in_fd); noclose_input: sys_close(out_fd); out: kfree(buf); sys_unlink("/dev/ram"); return res; } handle_initrd() 如果ROOT_DEV != Root_RAM0,則調用handle_init通過linuxrc啟動用戶態。(即initrd=0Xxxx,但是root=/dev/metdblock2矛盾導致) static void __init handle_initrd(void) { struct subprocess_info *info; static char *argv[] = { "linuxrc", NULL, }; extern char *envp_init[]; int error; //real_root_dev為一個全局變量,用來保存真實文件系統的設備號 real_root_dev = new_encode_dev(ROOT_DEV); //在 rootfs 根目錄創建真正的根文件系設備節點:/dev/root 是,其設備號是 ROOT_DEV 的值,因此這個節點對應是/dev/ram的INITRD create_dev("/dev/root.old", Root_RAM0); //調用mount_block_root將此設備真實文件系統加載到rootfs的/root下 mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);、 //在根目錄'/'下創建’/old‘設備節點 sys_mkdir("/old", 0700); //切換到/old目錄下 sys_chdir("/old"); /* try loading default modules from initrd */ load_default_modules(); /* * 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; info = call_usermodehelper_setup("/linuxrc", argv, envp_init, GFP_KERNEL, init_linuxrc, NULL, NULL); if (!info) return; call_usermodehelper_exec(info, UMH_WAIT_PROC); current->flags &= ~PF_FREEZER_SKIP; /* move initrd to rootfs' /old */ sys_mount("..", ".", NULL, MS_MOVE, NULL); /* switch root and cwd back to / of rootfs */ sys_chroot(".."); //如果real_root_dev直接配置為Root_RAM0,也即直接使用直接使用initrd作為realfs,改變當前目錄到initrd中,並直接返回 //如果real_root_dev在 linuxrc中重新設成Root_RAM0,則initrd就是最終真實的文件系統,改變當前目錄到initrd中,不作后續處理直接返回。 if (new_decode_dev(real_root_dev) == Root_RAM0) { sys_chdir("/old"); return; } //切換到根目錄’/‘ sys_chdir("/"); //如果執行完Linuxrc后,沒有設置成則Root_RAM0,則需要重新掛載上面linuxrc文件執行時指定的跟文件系統。 ROOT_DEV = new_decode_dev(real_root_dev); //調用mount_root將lfs掛載到VFS的/root目錄下,並將當前的目錄配置為VFS的/root 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"); } } mount_root() void __init mount_root(void) { #ifdef CONFIG_ROOT_NFS if (ROOT_DEV == Root_NFS) { //如果網絡文件系統掛載成功,則nfs作為根文件系統 if (mount_nfs_root()) return; printk(KERN_ERR "VFS: Unable to mount root fs via NFS, trying floppy.\n"); ROOT_DEV = Root_FD0; } #endif #ifdef CONFIG_BLK_DEV_FD if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) { /* rd_doload is 2 for a dual initrd/ramload setup */ if (rd_doload==2) { if (rd_load_disk(1)) { ROOT_DEV = Root_RAM1; root_device_name = NULL; } } else change_floppy("root floppy"); } #endif #ifdef CONFIG_BLOCK { //掛載磁盤文件系統為根文件系統 //在rootfs中創建/dev/root設備文件,一般就是指內核啟動參數指定的包含根文件系統的設備,在rootfs 中,這個設備文件被命名為 /dev/root int err = create_dev("/dev/root", ROOT_DEV); if (err < 0) pr_emerg("Failed to create /dev/root: %d\n", err); //把跟文件系統掛載到目錄/root mount_block_root("/dev/root", root_mountflags); } #endif } mount_block_root do_mount_root() 來掛載根文件系統 void __init mount_block_root(char *name, int flags) { struct page *page = alloc_page(GFP_KERNEL); char *fs_names = page_address(page); char *p; #ifdef CONFIG_BLOCK char b[BDEVNAME_SIZE]; #else const char *b = name; #endif // get_fs_names(fs_names); retry: //針對指定文件系統類型,調用do_mount_root嘗試掛載,如果指定的文件系統類型和存儲設備上的文件系統類型一致,那么掛載成功。 for (p = fs_names; *p; p += strlen(p)+1) { // int err = do_mount_root(name, p, flags, root_mount_data); switch (err) { case 0: goto out; case -EACCES: case -EINVAL: continue; } /* * Allow the user to distinguish between failed sys_open * and bad superblock on root device. * and give them a list of the available devices */ #ifdef CONFIG_BLOCK __bdevname(ROOT_DEV, b); #endif 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"); printk_all_partitions(); #ifdef CONFIG_DEBUG_BLOCK_EXT_DEVT printk("DEBUG_BLOCK_EXT_DEVT is enabled, you need to specify " "explicit textual name for \"root=\" boot option.\n"); #endif panic("VFS: Unable to mount root fs on %s", b); } if (!(flags & SB_RDONLY)) { flags |= SB_RDONLY; goto retry; } printk("List of all partitions:\n"); printk_all_partitions(); printk("No filesystem could mount root, tried: "); for (p = fs_names; *p; p += strlen(p)+1) printk(" %s", p); printk("\n"); #ifdef CONFIG_BLOCK __bdevname(ROOT_DEV, b); #endif panic("VFS: Unable to mount root fs on %s", b); out: put_page(page); } do_mount_root 主要為掛載根文件系統 把包含根文件系統的設備掛載到rootfs中的/root目錄上。 static int __init do_mount_root(char *name, char *fs, int flags, void *data) { struct super_block *s; //嘗試把參數name指定的設備文件掛載到/root下,並cd到新的根文件系統的根目錄中去。 int err = sys_mount(name, "/root", fs, flags, data); if (err) return err; //設置系統current->fs->pwd為當前目錄/root,即把掛載的當前目錄設置為/root。 sys_chdir("/root"); s = current->fs->pwd.dentry->d_sb; ROOT_DEV = s->s_dev; printk(KERN_INFO "VFS: Mounted root (%s filesystem)%s on device %u:%u.\n", s->s_type->name, sb_rdonly(s) ? " readonly" : "", MAJOR(ROOT_DEV), MINOR(ROOT_DEV)); return 0; } sys_mount sys_chdir devtmpfs_mount