您的位置:首页 > 运维架构 > Linux

Linux 根文件系统的挂载分析

2012-06-20 11:08 330 查看
转载自: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函数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: