轉載自:http://www.linuxidc.com/Linux/2011-10/45448.htm
在介紹根文件系統掛載之前先介紹一些基礎知識
initramfs
當linux內核啓動後,會找到並執行第一個用戶程序,一般是init。這個程序存在於文件系統當中,文件系統存在於設備上,但不知道init存在哪個設備上,於是有了內核命令列選項root=,用來指定root文件系統存在於哪個設備上。
然後由於後來的設備類型越來越來多,比如可能在scsi,sata,flash這些設備,還有的存在於網絡設備上,不可能把這些設備的驅動編譯進內核,這樣內核就會越來越來大。爲了解決這些問題,出現了基於ram的文件系統,initramfs,這個文件系統可以包含多個目錄和程序init,然後通過這個程序,內核再用這個程序去掛載真正的要文件系統。如果沒有這個程序,內核可以來尋找和掛載一個根分區,接着執行一些/sbin/init的變種。
ramfs
ramf是一個小型的基於內存的文件系統,由於linux中頁的數據被緩存在內存中,然後標識爲可用,爲防止別用,ramfs就是基於這種機制產生的。只是放在ramfs中的目錄和頁的緩存,不在寫回。
rootfs
rootfs是一種特定的ramfs的實例,它一直存在於系統中,不能卸載。大部分其他的文件系統安裝於rootfs之上。
initramfs和rootfs之間的關係
當內核啓動的時候,會先註冊和掛載一個虛擬的根文件系統,也就是rootfs,然後會把做好的initramfs(這個可以自己製作)中的文件解壓到rootfs中。然後系統會掛載真的根文件系統,rootfs隱藏之後。
我的開發板上的u-boot傳送的參數爲noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M。
noinitrd的含義
(僅當內核配置了選項 CONFIG_BLK_DEV_RAM和CONFIG_BLK_DEV_INITRD)現在的內核都可以支持initrd了,引導進程首先裝載內核和一個初始化的ramdisk,然後內核將initrd轉換成普通的ramdisk,也就是讀寫模式的根文件系統設備。然後linuxrc執行,然後裝載真正的根文件系統,之後ramdisk被卸載,最後執行啓動序列,比如/sbin/init。
選項noinitrd告訴內核不執行上面的步驟,即使內核編譯了initrd,而是把initrd的數據寫到 /dev/initrd,只是這是一個一次性的設備。
01void __init vfs_caches_init(unsigned long mempages)
02{
03 unsigned long reserve;
04
05 /* Base hash sizes on available memory, with a reserve equal to
06 150% of current kernel size */
07
08 reserve = min((mempages - nr_free_pages()) * 3/2, mempages - 1);
09 mempages -= reserve;
10
11 names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
12 SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
13
14 dcache_init();
15 inode_init();
16 files_init(mempages);
17 mnt_init();
18 bdev_cache_init();
19 chrdev_init();
20}
第14行爲頁目錄緩存的初始化
第15行索引結點緩存的初始化
第16行文件的初始化
第17行虛擬文件系統掛載的初始化
第18行塊設備緩存初始化。
第19行字符設備初始化
01void __init mnt_init(void)
02{
03 unsigned u;
04 int err;
05
06 init_rwsem(&namespace_sem);
07
08 mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount),
09 0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
10
11 mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC);
12
13 if (!mount_hashtable)
14 panic("Failed to allocate mount hash table\n");
15
16 printk("Mount-cache hash table entries: %lu\n", HASH_SIZE);
17
18 for (u = 0; u < HASH_SIZE; u++)
19 INIT_LIST_HEAD(&mount_hashtable[u]);
20
21 err = sysfs_init();
22 if (err)
23 printk(KERN_WARNING "%s: sysfs_init error: %d\n",
24 __func__, err);
25 fs_kobj = kobject_create_and_add("fs", NULL);
26 if (!fs_kobj)
27 printk(KERN_WARNING "%s: kobj create error\n", __func__);
28 init_rootfs();
29 init_mount_tree();
30}
第6行命明空間信號量的初始化
第8行分配空間
第11行掛載點哈希表分配空間
第18行初始化所有的掛載點哈希表。
第25行生成名爲fs的kobject對象。
第28行初始化rootfs文件系統
第29行初始化mount樹
第一部分 rootfs文件系統的註冊
01int __init init_rootfs(void)
02{
03 int err;
04
05 err = bdi_init(&ramfs_backing_dev_info);
06 if (err)
07 return err;
08
09 err = register_filesystem(&rootfs_fs_type);
10 if (err)
11 bdi_destroy(&ramfs_backing_dev_info);
12
13 return err;
14}
第5行初始化
第9行註冊rootfs文件系統
1static struct file_system_type rootfs_fs_type = {
2 .name = "rootfs",
3 .get_sb = rootfs_get_sb,
4 .kill_sb = kill_litter_super,
5};
第二部分掛載rootfs文件和創建根目錄
01static void __init init_mount_tree(void)
02{
03 struct vfsmount *mnt;
04 struct mnt_namespace *ns;
05 struct path root;
06
07 mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
08 if (IS_ERR(mnt))
09 panic("Can't create rootfs");
10 ns = kmalloc(sizeof(*ns), GFP_KERNEL);
11 if (!ns)
12 panic("Can't allocate initial namespace");
13 atomic_set(&ns->count, 1);
14 INIT_LIST_HEAD(&ns->list);
15 init_waitqueue_head(&ns->poll);
16 ns->event = 0;
17 list_add(&mnt->mnt_list, &ns->list);
18 ns->root = mnt;
19 mnt->mnt_ns = ns;
20
21 init_task.nsproxy->mnt_ns = ns;
22 get_mnt_ns(ns);
23
24 root.mnt = ns->root;
25 root.dentry = ns->root->mnt_root;
26 set_fs_pwd(current->fs, &root);
27 set_fs_root(current->fs, &root);
28}
這個函數的主要作用是是生成/目錄的。
第3行定義一個掛載點
第4行定義一個命名空間
第5行定義一個根路徑
第7行掛載rootfs文件系統,返回掛載點
第10行爲命名空間分配空間
第13行設定命名空間的引用數爲1
第14行初始化命名空間鏈表
第15行初始化等待對列
第18行命名空間的根結點指向掛載點
第19行掛載點指向命名空間
第21行第一個進程的命名空間第向剛纔初始化的。
第24行路徑的掛載點爲命名空間的根結點
第25行路徑的目錄爲命名空間所指向的掛載點的根目錄
第26行設置/目錄爲當前的目錄
第27行設置/目錄爲根目錄
01struct vfsmount * do_kern_mount(const char *fstype, int flags, const char *name, void *data)
02{
03 struct file_system_type *type = get_fs_type(fstype);
04 struct vfsmount *mnt;
05
06 if (!type)
07 return ERR_PTR(-ENODEV);
08 mnt = vfs_kern_mount(type, flags, name, data);
09
10 if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
11 !mnt->mnt_sb->s_subtype)
12 mnt = fs_set_subtype(mnt, fstype);
13 put_filesystem(type);
14 return mnt;
15}
do_kern_mount的參數介紹
fstype 要安裝的文件系統的類型名
flag 安裝的標誌
name 存放文件系統的塊設備的路徑名
data 指向傳遞給文件系統中read_super方法的附加指針
第3行得到文件系統的類型,這裏是rootfs,當然也會有其它的文件系統,比如proc,pipefs等
第8行返回掛載點
第13行增加對文件系統的引用
01struct vfsmount *
02vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
03{
04 struct vfsmount *mnt;
05 char *secdata = NULL;
06 int error;
07
08 if (!type)
09 return ERR_PTR(-ENODEV);
10
11 error = -ENOMEM;
12 mnt = alloc_vfsmnt(name);
13 if (!mnt)
14 goto out;
15
16 if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) {
17 secdata = alloc_secdata();
18 if (!secdata)
19 goto out_mnt;
20
21 error = security_sb_copy_data(data, secdata);
22 if (error)
23 goto out_free_secdata;
24 }
25
26 error = type->get_sb(type, flags, name, data, mnt);
27 if (error < 0)
28 goto out_free_secdata;
29 BUG_ON(!mnt->mnt_sb);
30
31 error = security_sb_kern_mount(mnt->mnt_sb, flags, secdata);
32 if (error)
33 goto out_sb;
34
35 mnt->mnt_mountpoint = mnt->mnt_root;
36 mnt->mnt_parent = mnt;
37 up_write(&mnt->mnt_sb->s_umount);
38 free_secdata(secdata);
39 return mnt;
40out_sb:
41 dput(mnt->mnt_root);
42 deactivate_locked_super(mnt->mnt_sb);
43out_free_secdata:
44 free_secdata(secdata);
45out_mnt:
46 free_vfsmnt(mnt);
47out:
48 return ERR_PTR(error);
49}
第4行定義掛載點
第12行分配一個新的已安裝文件系統的描述符,存放在局部變量mnt中
第26行調用文件系統get_sb回調函數,這裏是rootfs_get_sb,來初始化一個新的超級塊,同時會創建/目錄.後面會單獨介紹
第35行掛載點根目錄指向與文件系統根目錄對應的目錄項對象的地址
第36行掛載點父目錄指向自己
第39行返回局部變量mnt
第三部分解壓initramfs文件系統中的內容到rootfs
01static int __init populate_rootfs(void)
02{
03 char *err = unpack_to_rootfs(__initramfs_start,
04 __initramfs_end - __initramfs_start);
05 if (err)
06 panic(err); /* Failed to decompress INTERNAL initramfs */
07 if (initrd_start) {
08#ifdef CONFIG_BLK_DEV_RAM
09 int fd;
10 printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
11 err = unpack_to_rootfs((char *)initrd_start,
12 initrd_end - initrd_start);
13 if (!err) {
14 free_initrd();
15 return 0;
16 } else {
17 clean_rootfs();
18 unpack_to_rootfs(__initramfs_start,
19 __initramfs_end - __initramfs_start);
20 }
21 printk(KERN_INFO "rootfs image is not initramfs (%s)"
22 "; looks like an initrd\n", err);
23 fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
24 if (fd >= 0) {
25 sys_write(fd, (char *)initrd_start,
26 initrd_end - initrd_start);
27 sys_close(fd);
28 free_initrd();
29 }
30#else
31 err = unpack_to_rootfs((char *)initrd_start,
32 initrd_end - initrd_start);
33 if (err)
34 printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
35 free_initrd();
36#endif
37 }
38 return 0;
39}
第3行解壓initramfs文件到rootfs文件系統中,第一個參數爲開始位置,第二個參數爲長度。
第7行initrd_start值爲0,下面不執行
01static int __init kernel_init(void * unused)
02{
03 ............
04 do_basic_setup();
05 /*
06 * check if there is an early userspace init. If yes, let it do all
07 * the work
08 */
09
10 if (!ramdisk_execute_command)
11 ramdisk_execute_command = "/init";
12
13 if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
14 ramdisk_execute_command = NULL;
15
16 prepare_namespace();
17 }
18
19 /*
20 * Ok, we have completed the initial bootup, and
21 * we're essentially up and running. Get rid of the
22 * initmem segments and start the user-mode stuff..
23 */
24
25 init_post();
26 return 0;
27}
第4行初始化init段裏面的函數
第10行判斷ramdisk_execute_command的值,如果爲空,就給它賦於/init,這個也是啓動是第一個進程。
第13行如果這個init個程序,訪問它,其實也就是initramfs裏面解壓出來有這個程序的話,就不用再執行下面的函數,用這個init進程可以掛載真正的根文件系統。
第16行爲進程0準備命名空間
第25行進行真正根文件系統中的init進程運行
第四部分準備命名空間,掛載flash上的根文件系統
01void __init prepare_namespace(void)
02{
03 int is_floppy;
04 if (root_delay) {
05 printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
06 root_delay);
07 ssleep(root_delay);
08 }
09
10 /*
11 * wait for the known devices to complete their probing
12 *
13 * Note: this is a potential source of long boot delays.
14 * For example, it is not atypical to wait 5 seconds here
15 * for the touchpad of a laptop to initialize.
16 */
17 wait_for_device_probe();
18
19 md_run_setup();
20
21 if (saved_root_name[0]) {
22 root_device_name = saved_root_name;
23 if (!strncmp(root_device_name, "mtd", 3) ||
24 !strncmp(root_device_name, "ubi", 3)) {
25 mount_block_root(root_device_name, root_mountflags);
26 goto out;
27 }
28 ROOT_DEV = name_to_dev_t(root_device_name);
29 if (strncmp(root_device_name, "/dev/", 5) == 0)
30 root_device_name += 5;
31}
32
33 if (initrd_load())
34 goto out;
35
36 /* wait for any asynchronous scanning to complete */
37 if ((ROOT_DEV == 0) && root_wait) {
38 printk(KERN_INFO "Waiting for root device %s...\n",
39 saved_root_name);
40 while (driver_probe_done() != 0 ||
41 (ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
42 msleep(100);
43 async_synchronize_full();
44 }
45
46 is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
47
48 if (is_floppy && rd_doload && rd_load_disk(0))
49 ROOT_DEV = Root_RAM0;
50
51 mount_root();
52out:
53 sys_mount(".", "/", NULL, MS_MOVE, NULL);
54 sys_chroot(".");
55}
第21行檢查saved_root_name是否爲真,這裏是有值的。
第22行把它賦給root_device_name,此時爲/dev/mtdblock3
第23-27行不執行
第25行掛載根文件系統
第28行名字到設備號的轉變,這個設備號是設備的設備號,下面會用到的。我這裏是flash的第三個分區
第29行/dev/mtdblock3的前5個字符與/dev/比較,這裏是相等的。
第30行root_device_name加5,所以此時root_device_name爲mtdblock3.
第37-44行由於設備號不爲0,所以這裏面沒有執行。
第53行移動根文件系統的根目錄爲/
01void __init mount_block_root(char *name, int flags)
02{
03 char *fs_names = __getname();
04 char *p;
05#ifdef CONFIG_BLOCK
06 char b[BDEVNAME_SIZE];
07#else
08 const char *b = name;
09#endif
10
11 get_fs_names(fs_names);
12retry:
13 for (p = fs_names; *p; p += strlen(p)+1) {
14 int err = do_mount_root(name, p, flags, root_mount_data);
15 switch (err) {
16 case 0:
17 goto out;
18 case -EACCES:
19 flags |= MS_RDONLY;
20 goto retry;
21 case -EINVAL:
22 continue;
23 }
24 /*
25 * Allow the user to distinguish between failed sys_open
26 * and bad superblock on root device.
27 * and give them a list of the available devices
28 */
29#ifdef CONFIG_BLOCK
30 __bdevname(ROOT_DEV, b);
31#endif
32 printk("VFS: Cannot open root device \"%s\" or %s\n",
33 root_device_name, b);
34 printk("Please append a correct \"root=\" boot option; here are the available partitions:\n");
35
36 printk_all_partitions();
37#ifdef CONFIG_DEBUG_BLOCK_EXT_DEVT
38 printk("DEBUG_BLOCK_EXT_DEVT is enabled, you need to specify "
39 "explicit textual name for \"root=\" boot option.\n");
40#endif
41 panic("VFS: Unable to mount root fs on %s", b);
42 }
43
44 printk("List of all partitions:\n");
45 printk_all_partitions();
46 printk("No filesystem could mount root, tried: ");
47 for (p = fs_names; *p; p += strlen(p)+1)
48 printk(" %s", p);
49 printk("\n");
50#ifdef CONFIG_BLOCK
51 __bdevname(ROOT_DEV, b);
52#endif
53 panic("VFS: Unable to mount root fs on %s", b);
54out:
55 putname(fs_names);
56}
第3行申請空間
第11行fs_name指向內核裏面編譯文件系統的第一個
第13行循環把這些文件系統掛到根目錄下,我的內核裏面分別有,ext3,ext3,vfat等,這裏用到的是yaffs文件系統
第14行調用do_mount_root函數進行掛載
第15行返回的結果0
第17行增加對文件系統的引用
01static int __init do_mount_root(char *name, char *fs, int flags, void *data)
02{
03 int err = sys_mount(name, "/root", fs, flags, data);
04 if (err)
05 return err;
06
07 sys_chdir("/root");
08 ROOT_DEV = current->fs->pwd.mnt->mnt_sb->s_dev;
09 printk("VFS: Mounted root (%s filesystem)%s on device %u:%u.\n",
10 current->fs->pwd.mnt->mnt_sb->s_type->name,
11 current->fs->pwd.mnt->mnt_sb->s_flags & MS_RDONLY ?
12 " readonly" : "", MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
13 return 0;
14}
第3行掛載文件系統,這裏的name爲/dev/root,fs爲文件的類型,這裏是yaffs2,文件類型當然還是ext3,ext2等。
第7行改變到/root目錄下
第9行分別顯示出掛載出根文件系統和主次設備號,這裏是yaffs文件系統,主:次31:3
01void __init mount_root(void)
02{
03#ifdef CONFIG_ROOT_NFS
04 if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {
05 if (mount_nfs_root())
06 return;
07
08 printk(KERN_ERR "VFS: Unable to mount root fs via NFS, trying floppy.\n");
09 ROOT_DEV = Root_FD0;
10 }
11#endif
12#ifdef CONFIG_BLK_DEV_FD
13 if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {
14 /* rd_doload is 2 for a dual initrd/ramload setup */
15 if (rd_doload==2) {
16 if (rd_load_disk(1)) {
17 ROOT_DEV = Root_RAM1;
18 root_device_name = NULL;
19 }
20 } else
21 change_floppy("root floppy");
22 }
23#endif
24#ifdef CONFIG_BLOCK
25 create_dev("/dev/root", ROOT_DEV);
26 mount_block_root("/dev/root", root_mountflags);
27#endif
28}
這裏只執行了CONFI_BLOCK宏開關
第25行創建設備結點,這裏我在ubutu上測試了一下,/dev/root是的連接是hda1
第26行掛載根文件系統
第五部分運行真正根目錄中的init程序
01static noinline int init_post(void)
02 __releases(kernel_lock)
03{
04 /* need to finish all async __init code before freeing the memory */
05 async_synchronize_full();
06 free_initmem();
07 unlock_kernel();
08 mark_rodata_ro();
09 system_state = SYSTEM_RUNNING;
10 numa_default_policy();
11
12 if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
13 printk(KERN_WARNING "Warning: unable to open an initial console.\n");
14
15 (void) sys_dup(0);
16 (void) sys_dup(0);
17
18 current->signal->flags |= SIGNAL_UNKILLABLE;
19
20 if (ramdisk_execute_command) {
21 run_init_process(ramdisk_execute_command);
22 printk(KERN_WARNING "Failed to execute %s\n",
23 ramdisk_execute_command);
24 }
25
26 /*
27 * We try each of these until one succeeds.
28 *
29 * The Bourne shell can be used instead of init if we are
30 * trying to recover a really broken machine.
31 */
32 if (execute_command) {
33 run_init_process(execute_command);
34 printk(KERN_WARNING "Failed to execute %s. Attempting "
35 "defaults...\n", execute_command);
36 }
37 run_init_process("/sbin/init");
38 run_init_process("/etc/init");
39 run_init_process("/bin/init");
40 run_init_process("/bin/sh");
41
42 panic("No init found. Try passing init= option to kernel.");
43}
第12行打開/dev/console設備文件
第15-16行將文件描述符0複製給文件描述符1和2。
第20-24行ramdisk_execute_command變量中如果指定了要運行的程序,就開始運行這個程序,在u-boot的命令行參數中會指定rdinit=…,這個時候ramdisk_execute_command等於這個參數指定的程序。也可能/init程序存在,哪麼ramdisk_execute_command就等於/init,或者爲空。本程序沒有指定,所以這裏爲空。
第37行執行/sbin/init程序,這個程序存在於根文件系統,如果存在,執行它,系統的控制權交給/sbin/init,不再返回init_post函數