Linux VFS文件系统之rootfs注册与挂载

编程入门 行业动态 更新时间:2024-10-23 07:39:19

Linux VFS<a href=https://www.elefans.com/category/jswz/34/1771295.html style=文件系统之rootfs注册与挂载"/>

Linux VFS文件系统之rootfs注册与挂载

------------------------------------------------
#纯属个人理解,如有问题敬请谅解!
#kernel version: 2.6.26
#Author: andy wang
-------------------------------------------------一: 序言文件系统在linux这个“阿房宫”中扮演着重要的角色,它与许多其他子系统紧密联系;关系错中复杂,但是要想对linux有一个深入的理解,文件系统这关是必须要过的。在linux中用虚拟文件系统(VFS)来为linux文件系统提供一个统一的模型,VFS是一个纯软件机制;它为文件系统的操作提供了一个统一的接口)(文件read,write……);是linux实现’一切皆文件’的口号实现的基础。二:rootfs注册既然是一棵树那么根就是它存在的基础, 所以要研究VFS我们必须从文件系统的根入手;那么VFS的根什么咋个建立起来的呢? 以及VFS这颗大树是如何发展起来的呢?要弄清楚这么都必须先从rootfs入手,接下来我们就具体看一下rootfs文件系统是咋个建立起来的。在linux kernel初始化阶段会调用int __init init_rootfs(void)向内核注册rootfs文件系统,下面看看rootfs怎样注册到内核中的:int __init init_rootfs(void)                                                     
{                                                                                int err;                                                                 err = bdi_init(&ramfs_backing_dev_info);                                 if (err)                                                                 return err;                                                      err = register_filesystem(&rootfs_fs_type);                              if (err)                                                                 bdi_destroy(&ramfs_backing_dev_info);                            return err;                                                              
}    这个函数比较简单,函数被宏__init修饰,说明该函数被linux链接脚本链接到.init段中,在系统初始化的时候调用,而在调用完之后会将其占用的内存释放掉,在kernel中定义的一些修饰宏还是挺有意思的,以后有时间得专门写个文档..好了我们来看看代码, 其中核心的函数为register_filesystem();这个函数将结构file_system_type 注册在内核一个单链表中,每个文件系统必须调用此函数来注册到内核.    三: rootfs挂载此时该文件系统已经成功注册到内核中,在后面的挂载文件系统中需要再次遍历链表取出对于的file_system_type结构进行挂载。接下来该分析rootfs挂载过程。挂载rootfs首先会调用do_kern_mount()函数,具体代码段为: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);//挂载的核心函数…………….return mnt;}根据传入文件系统的名字fstype,调用get_fs_type()去匹配刚才注册的文件系统,如果未找到返回失败.接下来会调用挂载文件系统的核心函数vfs_kern_mount();struct vfsmount * vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data){………….. mnt = alloc_vfsmnt(name);//在cache中分配一个vfsmount,并对其进行初始化if (!mnt)goto out;………..error = type->get_sb(type, flags, name, data, mnt);//分配文件系统超级块的回调函数if (error < 0)goto out_free_secdata;BUG_ON(!mnt->mnt_sb);error = security_sb_kern_mount(mnt->mnt_sb, secdata);if (error)goto out_sb;mnt->mnt_mountpoint = mnt->mnt_root;mnt->mnt_parent = mnt;up_write(&mnt->mnt_sb->s_umount);free_secdata(secdata);return mnt;……….}在alloc_vfsmnt()中,会在cache中动态分配并并初始化一个vfsmount, 由内核动态分配一个mnt_id,再初始化一些链表头和vfsmount名字。接下来会调用一个重要的回调函数get_sb为文件系统建立一个超级块。在rootfs中回调函数为rootfs_get_sb();static int rootfs_get_sb(struct file_system_type *fs_type,int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{return get_sb_nodev(fs_type, flags|MS_NOUSER, data, ramfs_fill_super, mnt);
}一般的伪文件系统或叫内存文件系统分配超级块都是调用get_sb_nodev()或者get_sb_single(); 而实际文件系统如磁盘文件系统,在分配超级块的时候则调用get_sb_bdev()需要在具体块设备上建立超级块。因为rootfs是内存文件系统所以调用get_sb_nodev();下面跟一下这个代码的实现:int get_sb_nodev(struct file_system_type *fs_type,int flags, void *data,int (*fill_super)(struct super_block *, void *, int), struct vfsmount *mnt){int error;struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);//在内存中分配一个超级块if (IS_ERR(s))return PTR_ERR(s);s->s_flags = flags;error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);//填充超级块的回调函数if (error) {up_write(&s->s_umount);deactivate_super(s);//kill超级块,会调用fs->kill_sb(s)return error;}s->s_flags |= MS_ACTIVE;return simple_set_mnt(mnt, s);//关联超级块和vfsmount}在这个函数中重点关注一下回调函数fill_super();这个函数会初始化超级块的部分成员,并建立VFS的根目录,也就是我们上面图中的 ”/” 。下面是rootfs fill_super的程序片段:static int ramfs_fill_super(struct super_block * sb, void * data, int silent){struct inode * inode;struct dentry * root;sb->s_maxbytes = MAX_LFS_FILESIZE;  //文件的最大值sb->s_blocksize = PAGE_CACHE_SIZE;  //以字节为单位块的大小sb->s_blocksize_bits = PAGE_CACHE_SHIFT;  //以位为单位块的大小sb->s_magic = RAMFS_MAGIC;  //文件系统的魔数sb->s_op = &ramfs_ops;  //超级块的方法 ,在处理inode的时候会有用sb->s_time_gran = 1;inode = ramfs_get_inode(sb, S_IFDIR | 0755, 0); //建立根目录索引节点if (!inode)return -ENOMEM;root = d_alloc_root(inode); //建立根目录目录对象if (!root) {iput(inode);return -ENOMEM;}sb->s_root = root;  //超级块的s_root指向刚建立的根目录对象return 0;}在linux文件系统的中目录也看成文件, 在建立一个文件的时候都会在目录高速缓存中建立一个目录项对象,并建立一个索引节点与之关联。注意一个目录项对象只能关联唯一一个索引节点,而一个索引节点却可以对应多个目录项对象.比如我们常见的在linux 下的硬链接就是一个索引节点对于多个目录项对象。目录项对象的存在主要是为了我们进行路径的查找, 但是我们最终的目标是要找到目录项对象关联的索引节点,只有找到一个文件的索引节点才能找到指向该文件的操作方法。下面来看看文件索引节点的创建和初始化,这是一个比较中要的函数,因为一个文件的属性和操作方法都是在这个函数里面定义的。下面是rootfs的索引节点创建函数, 实际的磁盘文件系统(如EXT2)索引节点的创建更为复杂。程序片段为:struct inode *ramfs_get_inode(struct super_block *sb, int mode, dev_t dev){struct inode * inode = new_inode(sb);//在索引节点高速缓存中创建一个inode if (inode) {inode->i_mode = mode;  //文件的类型inode->i_uid = current->fsuid;inode->i_gid = current->fsgid;inode->i_blocks = 0;  //文件的块数inode->i_mapping->a_ops = &ramfs_aops;inode->i_mapping->backing_dev_info = &ramfs_backing_dev_info;mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;switch (mode & S_IFMT) {  //判断文件类型default:init_special_inode(inode, mode, dev); //特殊文件;如:字符~块设备文件,FIFO,SOCKET文件break;case S_IFREG: //普通文件inode->i_op = &ramfs_file_inode_operations;  //索引节点的操作方法inode->i_fop = &ramfs_file_operations;  //缺省普通文件的操作方法break;case S_IFDIR:  //目录文件inode->i_op = &ramfs_dir_inode_operations; inode->i_fop = &simple_dir_operations;  //目录文件的操作方法 /* directory inodes start off with i_nlink == 2 (for "." entry) */inc_nlink(inode);break;case S_IFLNK:  //符号链接inode->i_op = &page_symlink_inode_operations;break;}}return inode; //返回创建的inode与对应的目录项对象关联}在new_inode函数中就是分配并且初始化一个inode,需要特别关注的是在alloc_inode()函数中的一段代码:if (sb->s_op->alloc_inode)inode = sb->s_op->alloc_inode(sb);
elseinode = (struct inode *) kmem_cache_alloc(inode_cachep, GFP_KERNEL);如果在超级块方法中定义了alloc_inode函数,在inode时就需要用此函数来分配,在这种情况下struct inode可能被包含在一个更大的结构中被分配,在rootfs中并没有此定义,在以后分析其他文件系统中会看到它的用处。否则就会在cache中分配一个inode结构.接下来我们来看看inode操作方法和文件操作方法的定义,init_special_inode()都做了些啥子,这些函数将会为我们揭开文件的操作的神秘的面纱 ;先来看看特殊文件的操作方法是咋个定义的:void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev){inode->i_mode = mode;if (S_ISCHR(mode)) {          //字符设备文件inode->i_fop = &def_chr_fops;   //默认字符设操作方法inode->i_rdev = rdev;} else if (S_ISBLK(mode)) {          //块设备文件inode->i_fop = &def_blk_fops;     //默认块设备操作方法inode->i_rdev = rdev;} else if (S_ISFIFO(mode))   //FIFO文件inode->i_fop = &def_fifo_fops;    //默认FIFO文件操作方法else if (S_ISSOCK(mode))  //SOCKET文件inode->i_fop = &bad_sock_fops;  //默认SOCKET文件的操作方法elseprintk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\n",mode);}看了这个函数对于我们理解linux设备文件可能会有很大的帮助,这个在以后分析设备驱动模型的时候再写吧。对于像rootfs,ramfs,tmpfs等内存文件系统,文件其实就是放在内存中的,对于文件的操作其实就是对内存的读写操作,这个又是一个很大的话题了,设计到linux内存管理等复杂东东了;等研究透linux内存管理再来研究这部分吧。在建立好inode之后当然还需要创建rootfs根目录的目录项对象,不然VFS是找不到刚才建立的inode的,那还咋个操作文件呢,当然是空话了!!!       来看看VFS根目录是咋个创建的:struct dentry * d_alloc_root(struct inode * root_inode){struct dentry *res = NULL; if (root_inode) {static const struct qstr name = { .name = "/", .len = 1 }; res = d_alloc(NULL, &name);if (res) {res->d_sb = root_inode->i_sb; //指向该文件系统的超级块res->d_parent = res;  //根目录的父亲当然是它自己了d_instantiate(res, root_inode); //关联 dentry 和 inode}}return res;}这里的name=”/” 就是我们根目录的名字了,它就是VFS树根的名字;然后在目录项高速缓存中建立一个dentry ,最后一件重要的事情就是关联dentry和inode,看一下代码片断:void d_instantiate(struct dentry *entry, struct inode * inode){BUG_ON(!list_empty(&entry->d_alias)); spin_lock(&dcache_lock);if (inode)list_add(&entry->d_alias, &inode->i_dentry);  //将dentry加到inde链表中entry->d_inode = inode;  //关联inodefsnotify_d_instantiate(entry, inode);spin_unlock(&dcache_lock);security_d_instantiate(entry, inode);}BUG_ON(!list_empty(&entry->d_alias));可以证明我们刚才说的 “一个目录项对象只能关联一个索引节点”list_add(&entry->d_alias, &inode->i_dentry); 此可以证明刚才说的”一个索引节点可以关联多个目录项对象”   最后sb->s_root = root;将超级块的根目录指向刚才建立的根目录, 至此ramfs_fill_super函数就完成了它的使命。不过我们会发现直到这里我们都没有看到最初建立的vfsmount ,不要着急下面我们会看到它了。在执行完fill_super()回调函数以后, vfsmount就登场了,看下面的代码:int simple_set_mnt(struct vfsmount *mnt, struct super_block *sb){mnt->mnt_sb = sb;  //对 mnt_sb超级块指针附值了mnt->mnt_root = dget(sb->s_root); // 对mnt_root指向的根目录附值return 0;}最后回到vfs_kern_mount()函数  还需要对vfsmount进行一些初始化:mnt->mnt_mountpoint = mnt->mnt_root;   //文件系统挂载点目录,其实就是刚才建立的”/”目录mnt->mnt_parent = mnt; //父对象是自己                 到此rootfs挂载就算完成了,VFS的根就这样建立起来啦, 让我们看看内存中数据结构的关系图,“理理更清晰,洗洗更健康“。 当然rootfs是一个特殊的文件系统,做了上面的工作是不够的,我们还需要建立一个命名空间,它主要是要将do_kern_mount()中建立起来的mnt和dentry记录在init进程中,这样在linux下fork出来的所有进程都会继承这样的属性。比如所有进程的根目录都是”/” .{       ……….ns->root = mnt;mnt->mnt_ns = ns; init_task.nsproxy->mnt_ns = ns;get_mnt_ns(ns);root.mnt = ns->root;root.dentry = ns->root->mnt_root; set_fs_pwd(current->fs, &root);   //设置进程的当前目录set_fs_root(current->fs, &root);   //设置进程的根目录}四: 总结 本文介绍了rootfs的注册和挂载,可以说rootfs是VFS的基础, 有了rootfs文件系统VFS这颗树才可以发展壮大.下文会介绍VFS是如何发展的,既目录和文件的建立。

更多推荐

Linux VFS文件系统之rootfs注册与挂载

本文发布于:2024-02-27 17:16:48,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1707509.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:文件系统   Linux   VFS   rootfs

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!