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

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

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特殊文件系统没有被卸载,他只是隐藏在基于磁盘的根文件系统下了。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: