Linux启动分析之文件系统的挂载
2016-12-21 11:26
549 查看
基于内核版本:Linux-3.0.35
这几天在优化开机时间,发现其中挂载文件系统的时耗费了大量时间,所以才想着对内核挂载文件系统的过程跟踪分析,同时也在网上学到了很多,在这里整理记录下。
一、rootfs的诞生
Linux一切皆文件的提出:在Linux中,普通文件、目录、字符设备、块设备、套接字等都以文件被对待;他们具体的类型及其操作不同,但需要向上层提供统一的操作接口。
虚拟文件系统VFS就是Linux内核中的一个软件层,向上给用户空间程序提供文件系统操作接口;向下允许不同的文件系统共存。所以,所有实际文件系统都必须实现VFS的结构封装。
矛盾的提出:
Linux系统中任何文件系统的挂载必须满足两个条件:挂载点和文件系统。
直接挂载nfs或flash文件系统有如下两个问题必须解决:
1.谁来提供挂载点?我们可以想象自己创建一个超级块(包含目录项和i节点),这时挂载点不是就有了吗;很可惜,linux引入VFS(一切皆文件,所有类型文件系统必须提供一个VFS的软件层、以向上层提供统一接口)后该问题不能这么解决,因为挂载点必须关联到文件系统、也就是说挂载点必须属于某个文件系统。
2.怎样访问到nfs或flash上的文件系统?我们可以说直接访问设备驱动读取其上边的文件系统(设备上的文件系统是挂载在自己的根目录),不就可以了吗;别忘了还是Linux的VFS,设备访问也不例外。因为访问设备还是需要通过文件系统来访问它的挂载点,不能直接访问(要满足Linux的VFS架构,一切皆文件)。
所以,一句话:rootfs之所以存在,是因为需要在VFS机制下给系统提供最原始的挂载点。
如此矛盾,需要我们引入一种特殊文件系统:
1.它是系统自己创建并加载的第一个文件系统;该文件系统的挂载点就是它自己的根目录项。
2.该文件系统不能存在于nfs或flash上,因为如此将会陷入之前的矛盾。
rootfs的诞生:
上述问题需要我们创建具有如下三个特点的特殊文件系统:
1.它是系统自己创建并加载的第一个文件系统;
2.该文件系统的挂载点就是它自己的根目录项对象;
3.该文件系统仅仅存在于内存中。
由以上分析可以看出,rootfs是Linux的VFS(一切皆文件,所有类型文件系统必须提供一个VFS的软件层、以向上层提供统一接口)存在的基石;二者关系密切。如果没有VFS机制,rootfs也就没有存在的必要;同样,如果没有rootfs、VFS机制也就不能实现。
这就是两者之间的真正关系,之前看网上什么说法都有:有的只说关系密切,没有指明具体关系;有的干脆误人子弟,说VFS就是rootfs。
其实,VFS是一种机制、是Linux下每一种文件系统(包括刚才说的rootfs,还有常见的ext3、yaffs等)都必须按照这个机制去实现的一种规范;而rootfs仅仅是符合VFS规范的而且又具有如上3个特点的一个文件系统。
VFS是Linux文件系统实现必须遵循的一种机制,rootfs是一种具体实现的文件系统、Linux下所有文件系统的实现都必须符合VFS的机制(符合VFS的接口);这就是二者的真正关系。
二.注册/创建、安装/挂载rootfs,并调用set_fs_root设置系统current的根文件系统为rootfs
过程:
第一步:建立rootfs文件系统;
第二步:调用其get_sb函数(对于rootfs这种内存/伪文件系统是get_sb_nodev,实际文件系统比如ext2等是get_sb_bdev)、建立超级块(包含目录项和i节点);
第三步:挂载该文件系统(该文件系统的挂载点指向该文件系统超级块的根目录项);
第四步:将系统current的根文件系统和根目录设置为rootfs和其根目录。
kernel/init/main.c
下边分两步:
1.向内核注册rootfs虚拟文件系统init_rootfs
kernel/fs/ramfs/inode.c
kernel/fs/namespace.c
以下着重分析do_kern_mount函数,它实现了rootfs在自己根目录上的挂载:
kernel/fs/namespace.c
struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
{
struct file_system_type *type = get_fs_type(fstype);
struct vfsmount *mnt;
if (!type)
return ERR_PTR(-ENODEV);
mnt = vfs_kern_mount(type, flags, name, data);
if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
!mnt->mnt_sb->s_subtype)
mnt = fs_set_subtype(mnt, fstype);
put_filesystem(type);
return mnt;
}
kernel/fs/namespace.c
至此,rootfs文件系统建立、并且挂载于自己超级块(包括目录项dentry和i节点inod)对应的目录项,设置了系统current根目录和根文件系统、pwd的目录和文件系统。
================================================ 我 是 分 割 线 ===============================================================
第一点就提出了rootfs只是给Linux系统提供一个原始的挂载点,目的就是为了挂载我们自己需要文件系统,例如:ext2 ,ext3,yaffs等,这些文件系统一般都存在于flash中,所以这里称之为磁盘文件系统以便区别rootfs虚拟文件系统。
在挂载磁盘文件系统之前,内核代码会优先判定是否存在initramfs,这是一个存在于内存的根文件包(bin,dev,sur,lib等等),如果initramfs存在,释放Initramfs到rootfs;如果Initramfs中有init,这种情况比较特殊、rootfs就是最后系统使用的根文件系统。
而且此时,不需要在单独烧录根文件系统的img;此时,根文件系统就是内核uImage的一部分。当然,缺陷就是该文件系统运行时的介质是ramdisk即内存盘、它不再与磁盘对应;因此,此时修改根目录下的文件将不被得到保存。它的内核配置项为:CONFIG_INITRAMFS_SOURCE。实际项目中会经常碰到。
make menuconfig->General setup->Initial RAM filesystem and RAM disk(initramfs/initrd) support
底下的Initramfs source file(s)填写根文件系统的路径,如:../out/target/product/tclm6/root;不填的话,将导致initrd或磁盘文件系统的挂载(因为下边将会看到,内核将找不到“/init”)。
对应内核源码:
kernel/init/main.c
下边从uboot启动内核参数的角度来简单说明:
以下三种情况都是将文件系统挂载到rootfs的/root目录,并将系统current的根目录切换为/root、系统current的根文件系统切换为磁盘文件系统。
kernel/init/do_mounts.c
下边分两步解释mount_root()和sys_chroot(".")调用:
1.将nfs或磁盘文件系统挂载至rootfs的/root目录(以磁盘为例)
2.将当前目录/root设置为系统current根目录,磁盘文件系统设置为系统current根文件系统
fs/open.c
这几天在优化开机时间,发现其中挂载文件系统的时耗费了大量时间,所以才想着对内核挂载文件系统的过程跟踪分析,同时也在网上学到了很多,在这里整理记录下。
一、rootfs的诞生
Linux一切皆文件的提出:在Linux中,普通文件、目录、字符设备、块设备、套接字等都以文件被对待;他们具体的类型及其操作不同,但需要向上层提供统一的操作接口。
虚拟文件系统VFS就是Linux内核中的一个软件层,向上给用户空间程序提供文件系统操作接口;向下允许不同的文件系统共存。所以,所有实际文件系统都必须实现VFS的结构封装。
矛盾的提出:
Linux系统中任何文件系统的挂载必须满足两个条件:挂载点和文件系统。
直接挂载nfs或flash文件系统有如下两个问题必须解决:
1.谁来提供挂载点?我们可以想象自己创建一个超级块(包含目录项和i节点),这时挂载点不是就有了吗;很可惜,linux引入VFS(一切皆文件,所有类型文件系统必须提供一个VFS的软件层、以向上层提供统一接口)后该问题不能这么解决,因为挂载点必须关联到文件系统、也就是说挂载点必须属于某个文件系统。
2.怎样访问到nfs或flash上的文件系统?我们可以说直接访问设备驱动读取其上边的文件系统(设备上的文件系统是挂载在自己的根目录),不就可以了吗;别忘了还是Linux的VFS,设备访问也不例外。因为访问设备还是需要通过文件系统来访问它的挂载点,不能直接访问(要满足Linux的VFS架构,一切皆文件)。
所以,一句话:rootfs之所以存在,是因为需要在VFS机制下给系统提供最原始的挂载点。
如此矛盾,需要我们引入一种特殊文件系统:
1.它是系统自己创建并加载的第一个文件系统;该文件系统的挂载点就是它自己的根目录项。
2.该文件系统不能存在于nfs或flash上,因为如此将会陷入之前的矛盾。
rootfs的诞生:
上述问题需要我们创建具有如下三个特点的特殊文件系统:
1.它是系统自己创建并加载的第一个文件系统;
2.该文件系统的挂载点就是它自己的根目录项对象;
3.该文件系统仅仅存在于内存中。
由以上分析可以看出,rootfs是Linux的VFS(一切皆文件,所有类型文件系统必须提供一个VFS的软件层、以向上层提供统一接口)存在的基石;二者关系密切。如果没有VFS机制,rootfs也就没有存在的必要;同样,如果没有rootfs、VFS机制也就不能实现。
这就是两者之间的真正关系,之前看网上什么说法都有:有的只说关系密切,没有指明具体关系;有的干脆误人子弟,说VFS就是rootfs。
其实,VFS是一种机制、是Linux下每一种文件系统(包括刚才说的rootfs,还有常见的ext3、yaffs等)都必须按照这个机制去实现的一种规范;而rootfs仅仅是符合VFS规范的而且又具有如上3个特点的一个文件系统。
VFS是Linux文件系统实现必须遵循的一种机制,rootfs是一种具体实现的文件系统、Linux下所有文件系统的实现都必须符合VFS的机制(符合VFS的接口);这就是二者的真正关系。
二.注册/创建、安装/挂载rootfs,并调用set_fs_root设置系统current的根文件系统为rootfs
过程:
第一步:建立rootfs文件系统;
第二步:调用其get_sb函数(对于rootfs这种内存/伪文件系统是get_sb_nodev,实际文件系统比如ext2等是get_sb_bdev)、建立超级块(包含目录项和i节点);
第三步:挂载该文件系统(该文件系统的挂载点指向该文件系统超级块的根目录项);
第四步:将系统current的根文件系统和根目录设置为rootfs和其根目录。
kernel/init/main.c
asmlinkage void __init start_kernel(void) { setup_arch(&command_line);//解析uboot命令行,实际文件系统挂载需要 parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption); vfs_caches_init(num_physpages); }kernel/fs/dcache.c
void __init vfs_caches_init(unsigned long mempages) { mnt_init(); bdev_cache_init(); //块设备文件创建 chrdev_init();//字符设备文件创建 }kernel/fs/namespace.c
void __init mnt_init(void) { init_rootfs(); //向内核注册rootfs init_mount_tree();//重要!!!rootfs根目录的建立以及rootfs文件系统的挂载;设置系统current根目录和根文件系统为rootfs }
下边分两步:
1.向内核注册rootfs虚拟文件系统init_rootfs
kernel/fs/ramfs/inode.c
int __init init_rootfs(void) { err = register_filesystem(&rootfs_fs_type); } static struct file_system_type rootfs_fs_type = { .name = "rootfs", .get_sb = rootfs_get_sb, .kill_sb = kill_litter_super, };2.建立rootfs的根目录,并将rootfs挂载到自己的根目录;设置系统current根目录和根文件系统
kernel/fs/namespace.c
static void __init init_mount_tree(void) { struct vfsmount *mnt; struct mnt_namespace *ns; struct path root; mnt = do_kern_mount("rootfs", 0, "rootfs", NULL); if (IS_ERR(mnt)) panic("Can't create rootfs"); ns = create_mnt_ns(mnt);//将创建的mnt添加到已经注册的rootfs中 if (IS_ERR(ns)) panic("Can't allocate initial namespace"); init_task.nsproxy->mnt_ns = ns; get_mnt_ns(ns); root.mnt = ns->root; //文件系统为rootfs,相当与root.mnt = mnt; root.dentry = ns->root->mnt_root;//目录项为根目录项,相当与root.dentry = mnt->mnt_root; set_fs_pwd(current->fs, &root); //设置系统current的pwd目录和文件系统 set_fs_root(current->fs, &root); //设置系统current根目录,根文件系统。这个是关键!!!整个内核代码最多只有两处调用 }
以下着重分析do_kern_mount函数,它实现了rootfs在自己根目录上的挂载:
kernel/fs/namespace.c
struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
{
struct file_system_type *type = get_fs_type(fstype);
struct vfsmount *mnt;
if (!type)
return ERR_PTR(-ENODEV);
mnt = vfs_kern_mount(type, flags, name, data);
if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
!mnt->mnt_sb->s_subtype)
mnt = fs_set_subtype(mnt, fstype);
put_filesystem(type);
return mnt;
}
kernel/fs/namespace.c
struct vfsmount * vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data) { struct vfsmount *mnt; struct dentry *root; if (!type) return ERR_PTR(-ENODEV); mnt = alloc_vfsmnt(name);//建立并填充vfsmount if (!mnt) return ERR_PTR(-ENOMEM); if (flags & MS_KERNMOUNT) mnt->mnt_flags = MNT_INTERNAL; root = mount_fs(type, flags, name, data);//为文件系统建立并填充超级块(主要是其dentry和inode),建立rootfs根目录 if (IS_ERR(root)) { free_vfsmnt(mnt); return ERR_CAST(root); } mnt->mnt_root = root; //将刚才建立的vfsmount和dentry进行关联 mnt->mnt_sb = root->d_sb; //关联超级快 mnt->mnt_mountpoint = mnt->mnt_root; //文件系统挂载点目录,其实就是刚才建立的”/”目录。挂载点就是自己!!! mnt->mnt_parent = mnt; //父对象就是自己 return mnt; }
至此,rootfs文件系统建立、并且挂载于自己超级块(包括目录项dentry和i节点inod)对应的目录项,设置了系统current根目录和根文件系统、pwd的目录和文件系统。
================================================ 我 是 分 割 线 ===============================================================
第一点就提出了rootfs只是给Linux系统提供一个原始的挂载点,目的就是为了挂载我们自己需要文件系统,例如:ext2 ,ext3,yaffs等,这些文件系统一般都存在于flash中,所以这里称之为磁盘文件系统以便区别rootfs虚拟文件系统。
在挂载磁盘文件系统之前,内核代码会优先判定是否存在initramfs,这是一个存在于内存的根文件包(bin,dev,sur,lib等等),如果initramfs存在,释放Initramfs到rootfs;如果Initramfs中有init,这种情况比较特殊、rootfs就是最后系统使用的根文件系统。
而且此时,不需要在单独烧录根文件系统的img;此时,根文件系统就是内核uImage的一部分。当然,缺陷就是该文件系统运行时的介质是ramdisk即内存盘、它不再与磁盘对应;因此,此时修改根目录下的文件将不被得到保存。它的内核配置项为:CONFIG_INITRAMFS_SOURCE。实际项目中会经常碰到。
make menuconfig->General setup->Initial RAM filesystem and RAM disk(initramfs/initrd) support
底下的Initramfs source file(s)填写根文件系统的路径,如:../out/target/product/tclm6/root;不填的话,将导致initrd或磁盘文件系统的挂载(因为下边将会看到,内核将找不到“/init”)。
对应内核源码:
kernel/init/main.c
static int __init kernel_init(void * unused){ ...... do_basic_setup(); //初始化设备驱动,加载静态内核模块;释放Initramfs到rootfs /* kernel/init/initramfs.c rootfs_initcall(populate_rootfs); static int __init populate_rootfs(void) { printk(KERN_INFO "checking if image is initramfs..."); err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start, 1); //释放ramdisk到rootfs } */ ...... if (!ramdisk_execute_command) ramdisk_execute_command = "/init"; if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) { ramdisk_execute_command = NULL; //如果此时rootfs中没有init,则加载initfd、nfs或磁盘文件系统 //也即磁盘的文件系统挂载至rootfs的/root目录,并设置系统current对应的根目录项为磁盘根目录项、系统current根文件系统为磁盘文件系统 //至此,rootfs对于以后所有进程而言、已被隐藏。 prepare_namespace(); } init_post(); //启动init进程 ...... }看看init_post实现:
static noinline int init_post(void) { if (ramdisk_execute_command) { //Initramfs从这里启动init run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s\n", ramdisk_execute_command); } //initrd、nfs和磁盘都是从如下启动的init if (execute_command) { run_init_process(execute_command); printk(KERN_WARNING "Failed to execute %s. Attempting " "defaults...\n", execute_command); } //一般执行如下 run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh"); }三、挂载实际文件系统至rootfs,并调用set_fs_root设置为系统current的根文件系统
下边从uboot启动内核参数的角度来简单说明:
以下三种情况都是将文件系统挂载到rootfs的/root目录,并将系统current的根目录切换为/root、系统current的根文件系统切换为磁盘文件系统。
kernel/init/do_mounts.c
void __init prepare_namespace(void) { if (initrd_load()) //如果挂载initrd并执行成功,则不再挂载磁盘文件系统 goto out; if (saved_root_name[0]) { 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); //启动时root=参数,如《四.2》中“root=/dev/mtdblock0” goto out; } ROOT_DEV = name_to_dev_t(root_device_name); if (strncmp(root_device_name, "/dev/", 5) == 0) root_device_name += 5; } mount_root(); //将实际文件系统挂载到rootfs的/root目录 out: //sys_mount(".", "/", NULL, MS_MOVE, NULL); 这句话无关紧要,影响理解;屏蔽不影响功能 sys_chroot("."); //将当前目录(/root)设置为系统current根目录,磁盘文件系统设置为系统current根文件系统。 }
下边分两步解释mount_root()和sys_chroot(".")调用:
1.将nfs或磁盘文件系统挂载至rootfs的/root目录(以磁盘为例)
void __init mount_root(void) { if (mount_nfs_root()) //如果网络文件系统挂载成功,则nfs作为根文件系统 return; //挂载磁盘文件系统为根文件系统 //在rootfs中建立/dev/root设备文件 create_dev("/dev/root", ROOT_DEV); //在rootfs中建立/dev/root设备文件,也就是/dev/mtdblock0设备。 //挂载/dev/root到rootfs的/root目录 mount_block_root("/dev/root", root_mountflags); } void __init mount_block_root(char *name, int flags) { int err = do_mount_root(name, p, flags, root_mount_data); } static int __init do_mount_root(char *name, char *fs, int flags, void *data) { int err = sys_mount(name, "/root", fs, flags, data);//将/dev/root挂载到/root sys_chdir("/root"); //系统current->fs->pwd为当前目录/root ROOT_DEV = current->fs->pwd.mnt->mnt_sb->s_dev; return 0; }
2.将当前目录/root设置为系统current根目录,磁盘文件系统设置为系统current根文件系统
fs/open.c
SYSCALL_DEFINE1(chroot, const char __user *, filename) { struct path path; error = user_path_dir(filename, &path); //这才是完成切换的关键!!!!整个内核代码只有两处调用 set_fs_root(current->fs, &path); }注意,如下情况:rootfs特殊文件系统没有被卸载,他只是隐藏在基于磁盘的根文件系统下了。
相关文章推荐
- Linux 内核启动挂载android根文件系统过程分析
- Linux启动过程之内核挂载内存文件系统和真正根文件系统原因及过程分析
- Linux--根文件系统的挂载过程分析
- Linux--根文件系统的挂载过程分析
- Linux 根文件系统的挂载分析
- mini6410 Linux--根文件系统的挂载过程分析
- Linux--根文件系统的挂载过程分析
- linux 内核启动过程以及挂载android 根文件系统的过程 ( 转)
- Linux--根文件系统的挂载过程分析
- mini6410基于linux2.6.36内核通过NFS启动根文件系统总结(四制作根文件系统及通过NFS挂载文件系统)
- Linux--根文件系统的挂载过程分析
- Linux--根文件系统的挂载过程分析
- Linux--根文件系统的挂载过程分析
- linux文件系统的系统分析--(四)sysfs的安装和挂载
- linux 内核启动过程以及挂载android 根文件系统的过程
- Linux--根文件系统的挂载过程分析
- linux启动时挂载根文件系统的过程[转]
- linux 内核启动过程以及挂载android 根文件系统的过程
- mini6410基于linux2.6.36内核通过NFS启动根文件系统总结(四制作根文件系统及通过NFS挂载文件系统)
- linux 内核启动过程以及挂载android 根文件系统的过程,以及介绍android 源代码中文件系统部分的浅析