Linux 根文件系統的掛載分析

轉載自: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函數



發佈了35 篇原創文章 · 獲贊 15 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章