rootfs注册挂载过程分析
参考:Linux Filesystem: 解析 Linux 中的 VFS 文件系统机制
主要代码,
init_rootfs();
init_mount_tree();
1.init_rootfs()解析
init_rootfs() -->bdi_init(&ramfs_backing_dev_info); /* 注册过程实际上将表示各实际文件系统的 struct file_system_type 数据结构的实例化, 将rootfs_fs_type加入到static struct file_system_type *file_systems为首的单链表。 */ -->register_filesystem(&rootfs_fs_type);
struct backing_dev_info结构是显示设备信息的描述符,定义如下:
static struct backing_dev_info ramfs_backing_dev_info = { .ra_pages = 0, /* No readahead */ .capabilities = BDI_CAP_NO_ACCT_AND_WRITEBACK | BDI_CAP_MAP_DIRECT | BDI_CAP_MAP_COPY | BDI_CAP_READ_MAP | BDI_CAP_WRITE_MAP | BDI_CAP_EXEC_MAP, };
2.init_mount_tree()解析
init_mount_tree() 这个函数为 VFS 建立了根目录 "/"
init_mount_tree() -->struct vfsmount *mnt = do_kern_mount("rootfs", 0, "rootfs", NULL); /* 1.根据“rootfs”在static struct file_system_type *file_systems链表中 找到代表文件系统的file_system_type结构体 */ -->struct file_system_type *type = get_fs_type("rootfs"); -->struct vfsmount *mnt = vfs_kern_mount(type, 0, "rootfs", NULL); /*2.从mnt_cache缓存分配vfsmount结构体并初始化*/ -->struct vfsmount *mnt = alloc_vfsmnt(name); -->type->get_sb(type, 0, "rootfs", NULL, mnt); 即rootfs_get_sb(type, 0, "rootfs", NULL, mnt); -->get_sb_nodev(type, MS_NOUSER, NULL, ramfs_fill_super,mnt); -->struct super_block *s = sget(type, NULL, set_anon_super, NULL); /*2.分配并初始化super_block结构体*/ -->struct super_block *s = alloc_super(type); -->set(s, NULL); 即set_anon_super(s,NULL) /*设置super_block结构体的s_dev成员*/ -->s->s_dev = MKDEV(0, dev & MINORMASK); -->s->s_type = type; -->strlcpy(s->s_id, type->name, sizeof(s->s_id)); /*系统中所有的super_block结构体都连接到super_blocks为首的list_head链表*/ -->list_add_tail(&s->s_list, &super_blocks); /*属于file_system_type指定文件系统的super_block结构体都连接到type->fs_supers链表*/ -->list_add(&s->s_instances, &type->fs_supers); -->s->s_flags = MS_NOUSER; -->fill_super(s, NULL, 0) 即ramfs_fill_super(s, NULL, 0) -->sb->s_op = &ramfs_ops; /*3.分配inode结构体并初始化*/ -->struct inode *inode = ramfs_get_inode(sb, S_IFDIR | fsi->mount_opts.mode, 0); -->struct inode * inode = new_inode(sb); -->设置inode成员 /*4.分配dentry并初始化,“/”*/ -->struct dentry *root = d_alloc_root(inode); -->static const struct qstr name = { .name = "/", .len = 1 } -->struct dentry *res = d_alloc(NULL, &name); -->res->d_sb = inode->i_sb; -->res->d_parent = res; -->d_instantiate(res, inode); -->list_add(&res->d_alias, &inode->i_dentry); -->res->d_inode = inode; -->s->s_flags |= MS_ACTIVE; -->simple_set_mnt(mnt, s); -->mnt->mnt_sb = s; -->mnt->mnt_root = dget(sb->s_root); mnt->mnt_mountpoint = mnt->mnt_root; mnt->mnt_parent = mnt; /* 为系统最开始的进程(即 init_task 进程)准备它的进程数据块中的namespace 域, 主要目的是将 do_kern_mount() 函数中建立的 mnt 和 dentry 信息记录在了 init_task 进程的进程数据块中, 这样所有以后从 init_task 进程 fork 出来的进程也都先天地继承了这一信息 */ -->struct mnt_namespace *ns = kmalloc(sizeof(*ns), GFP_KERNEL); -->list_add(&mnt->mnt_list, &ns->list); -->ns->root = mnt; -->mnt->mnt_ns = ns; -->struct path root.mnt = ns->root; struct path root.dentry = ns->root->mnt_root; -->set_fs_pwd(current->fs, &root); set_fs_root(current->fs, &root);
3.在根目录下增加目录
Linux 下用系统调用 sys_mkdir 来在 VFS 目录树中增加新的节点。为配合路径搜索,引入了下面一个数据结构:
struct path { struct vfsmount *mnt; struct dentry *dentry; }; /* 这个数据结构在路径搜索的过程中用来记录相关信息,起着类似"路标"的作用 其中前path.dentry记录的是要建目录的父目录的信息 last,flags,last_type三项记录的是所查找路径的最后一个节点(即待建目录或文件)的信息。 */ struct nameidata { struct path path; struct qstr last; unsigned int flags; int last_type; unsigned depth; char *saved_names[MAX_NESTED_LINKS + 1]; /* Intent data */ union { struct open_intent open; } intent; };
4.在 VFS 树中挂载文件系统
这一过程可简单描述为:将某一设备(dev_name)上某一文件系统(file_system_type)安装到VFS目录树上的某一安装点(dir_name)。它要解决的问题是:将对 VFS 目录树中某一目录的操作转化为具体安装到其上的实际文件系统的对应操作。
比如说,如果将 hda2 上的根文件系统(假设文件系统类型为 ext2)安装到 "/dev" 目录上(此时,"/dev" 目录就成为了安装点),那么安装成功之后应达到这样的目的,即:对 VFS 文件系统的 "/dev" 目录执行 "ls" 指令,该条指令应能列出 hda2 上 ext2 文件系统的根目录下所有的目录和文件。
记住:对目录或文件的操作将最终由目录或文件所对应的 inode 结构中的 i_op 和 i_fop 所指向的函数表中对应的函数来执行。所以,不管最终解决方案如何,都可以设想必然要通过将对 "/dev" 目录所对应的 inode 中 i_op 和 i_fop 的调用转换到hda2 上根文件系统 ext2 中根目录所对应的 inode 中 i_op 和 i_fop 的操作。
初始过程由 sys_mount() 系统调用函数发起,该函数原型声明如下:
long sys_mount(char * dev_name, char * dir_name, char * type,unsigned long flags, void * data);
参数 char *type 为标识将要安装的文件系统类型字符串,对于 ext2 文件系统而言,就是"ext2"。
为了更好地理解这一过程,用一个具体的例子来说明:我们准备将来自主硬盘第 2 分区(hda2)上的 ext2 文件系统安装到前面创建的 "/dev" 目录中。那么对于 sys_mount() 函数的调用便具体为:
sys_mount("hda2","/dev ","ext2",…) /* 将来自用户内存空间(user space)的参数拷贝到内核空间后, 便调用 do_mount() 函数开始真正的安装文件系统的工作 */ -->do_mount() -->/*调用 path_lookup() 函数来得到安装点的相关信息, 如同创建目录过程中叙述的那样,该安装点的信息最终记录在 struct nameidata 类型的一个变量当中, 为叙述方便,记该变量为nd */ /*调用 do_add_mount() 函数来向 VFS 树中安装点 "/dev " 安装一个实际的文件系统。*/ -->do_add_mount() /*建立一新的安装区域块*/ -->do_kern_mount() -->graft_tree() -->将 do_kern_mount() 函数返回的一 struct vfsmount 类型的变量加入到安装系统链表中 -->将新分配的 struct vfsmount 类型的变量加入到一个hash表中