欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

linux虚拟文件系统(VFS)(1)

程序员文章站 2022-03-09 10:38:06
...

概要

最近在学习文件系统的东西,刚接触linux内核,有许多东西不是特别明白,先用博客记录一下学习的东西,后面慢慢的完善进步。这篇文章的内容多半参考《深入理解linux内核》

什么是虚拟文件系统(VFS)

虚拟文件系统是linux设计的一种方便统一管理各种文件系统的文件系统类型,它把对不同文件系统的提供的不同的操作,都能统一转换成linux所支持的文件系统操作。简单理解就是应用程序与真正的文件系统之间的中间层,应用程序—虚拟文件系统—文件系统(ext3,ext2,nfs等),应用程序使用虚拟文件系统提供的操作去处理文件,具体对真正的文件系统的操作,由虚拟文件系统去转换执行。
VFS所隐含的主要思想在于引入了一个通用的文件模型,这个模型能够表示所有支持的文件系统。
虚拟文件系统,并不是对一个特定的函数进行硬编码去执行,而是对每个操作都必须使用一个指针,指向要访问的具体文件系统的适当函数。
总的一句话来说就是,面对不同的类型,提供不同的处理方法,有点类似于C++中面向对象的思想。

二.VFS支持的文件系统的类型

磁盘文件系统

1.linux家族的文件系统,ext2,ext3
1. Unix家族文件系统,BSD, SystemV
2. 微软的文件系统

网络文件系统

NFS, Coda,AFS,CIFS等

特殊文件系统

典型的特殊文件系统就是/proc

三.通用文件模型

超级块对象(superblock object)

存放已经安装的文件系统的信息,超级块这个结构在很多地方都会用到,但是注意在通用文件模型中,超级块对象代表的是已经安装的文件系统的信息,而后面会学习到的ext2,ext3等文件系统的超级块,存储的是文件系统本身需要的信息,这两处还是有一定的区别的。

索引节点对象(inode object)

存放一个具体文件的信息,每个索引节点对象都有一个索引节点号,这个节点号唯一地标识文件系统中的文件。

文件对象(file object)

存放打开文件与进程之间进行交互的有关信息,这个文件对象,只有有进程具体访问某个文件时,才会创建出来,存在内核内存中,至于文件访问结束后,根据linux的机制来说,是会保留在缓存中的,这个具体到后面再看一下。

目录项对象(dentry object)

存放目录项,也就是文件的特定名称,与对应文件进行链接的有关信息,每个磁盘文件系统都是以自己特有的方式将该类信息存在磁盘上。系统最近使用的目录项对象存在目录项高速缓存中。

VFS的数据结构

每个vfs对象都存放在一个适当的数据结构中,下面会介绍这几个数据结构的内容,但是只是拿出来一些重要的参数。

超级块对象
struct super_block{
    dev_t            s_dev                //设备标识符
    struct file_system_type *s_type       //文件系统类型
    struct super_operations *s_op         //超级块方法
    struct dentry           *s_root       //文件系统根目录的目录项对象
    struct rw_semaphore     s_umount      //卸载所用的信号量
    struct semaphore        s_lock        //超级块信号量
    struct list_head        s_inodes      //所有索引节点的链表
    struct list_head        s_dirty       //改进型索引节点的链表
    struct list_head        s_io          //等待被写入磁盘的索引节点的链表
    struct list_head        s_files       //文件对象的链表
    struct block_device     *s_bdev       //指向块设备驱动程序描述符的指针
    char []                 s_id          //包含超级块的块设备名称
"""void *               s_fs_info     //指向特定文件系统的超级块信息的指针"""
    struct semaphore       s_vfs_rename_sem //当VFS通过目录重命名文件时使用的信息量
}

s_fs_info: 这个字段比较重要,指向属于具体文件系统的超级块信息,比如需要的是ext2文件系统,那么该字段就指向ext2的超级块对象
s_op : 与这些具体文件系统的超级块关联的方法就是所谓的超级块操作。每个具体的文件系统都有定义自己的超级块操作,VFS通过这个字段去调用具体操作。

索引节点对象

文件系统所处理的文件所需要的所有信息都放在一个名为索引节点的数据结构中。索引节点与文件是唯一对应的。

struct inode{
    struct hlist_head       i_hash      //用于散列链表的指针
    struct list_head        i_list      //用于描述索引节点当前状态的链表的指针
    struct list_head        i_sb_list   //用于超级块的索引节点链表的指针
    struct list_head        i_dentry    //用于索引节点的目录项对象链表的头
    unsigned long           i_ino       //索引节点号
    atomic                  i_count     //引用计数器
    umode_t                 i_mode      //文件类型与访问权限
    unsigned int            i_nlink     //硬链接数目,每个进程访问当前文件时会打开一个文件对象,文件对象会跟当前索引节点产生一个硬链接
    uid_t                   i_uid       //所有者标识符
    gid_t                   i_gid       //文件所属的组
    dev_t                   i_rdev      //实设备标识符
    loff_t                  i_size      //文件的字节数
    unsigned int            i_blkbits   //块的位数
    unsigned long           i_blksize   //块的字节数
    unsigned long           i_blocks    //文件的块数
    struct semaphore        i_sem       //索引节点信号量
    struct rw_semaphore     i_alloc_sem //在直接io文件操作中避免出现竞争条件的读/写信号量
    struct inode_operations *i_op       //索引节点的操作
    struct file_operations  *i_fop      //缺省文件操作
    struct super_block      *i_sb       //指向超级块对象的指针
    struct file_lock        *i_flock    //指向文件锁链表的指针
    struct address_space    *i_mapping  //指向address_space对象的指针
    struct address_space    *i_data     //文件的address_space对象
    struct pipe_inode_info  *i_pipe     //如果文件是一个管道则使用它
    struct block_device     *i_bdev     //指向块设备驱动程序的指针
    struct cdev             *i_cdev     //指向字符设备驱动程序的指针
    unsigned long            i_state     //索引节点的状态标志
    unsigned int             i_flags     //文件系统的安装标志
}
文件对象

文件对象描述进程怎样与一个打开的文件进行交互,文件对象实在文件被打开时创建的,由一个file结构组成,文件对象在磁盘上没有对应的映像,因此file结构中没有设置脏字段来表示文件对象是否已被修改

struct file{
    struct list_head     f_list   //用于通用文件对象链表的指针
    struct dentry       *f_dentry //与文件相关的目录项对象
    struct vfsmount     *f_vfsmnt //含有该文件的已安装文件系统
    struct file_operations *f_op  //指向文件操作表的指针
    atomic_t             f_count  //文件对象的引用计数器
    unsigned int         f_flags  //当打开文件是所指定的模式
    mode_t               f_mode   //进程的访问模式
    int                  f_error  //网络写操作的错误码
    loff_t               f_ops    //当前的文件位移量
    struct fown_struct   f_owner  //通过信号进行io事件通知的数据
    unsigned int         f_uid    //用户的UID   用户id
    unsigned int         f_gid    //用户的GID   组id
    unfigned long        f_version //版本号,每次使用后自动递增
    void *               private_date  //指向特定文件系统或设备驱动程序所需的数据的指针
    struct list head     f_ep_links    //文件的事件轮询等待者链表的头
    spinlock_t           f_ep_lock     //保护f_ep_links链表的自旋锁
    struct address_space *f_mapping    //指向文件地址空间对象的指针
}

当VFS进程代表进程必须打开一个文件时,它调用get_empty_filp()函数来分配一个新的文件对象,该函数调用kmem_cache_alloc()从filp高速缓存中获得一个空闲的文件对象,然后初始化这个对象的字段,下面看一下具体的代码

struct file *get_empty_filp(void)      //函数返回一个file
{
    static int old_max;
    struct file * f;

    /*
     * Privileged users can go above max_files
     */
    if (files_stat.nr_files < files_stat.max_files ||
                capable(CAP_SYS_ADMIN)) {
        f = kmem_cache_alloc(filp_cachep, GFP_KERNEL);     //后面会说到,这是一个内核内存分配函数
        if (f) {
            memset(f, 0, sizeof(*f));
            if (security_file_alloc(f)) {
                file_free(f);
                goto fail;
            }
            eventpoll_init_file(f);                   //初始化file中的f_ep_links和f_ep_lock
            atomic_set(&f->f_count, 1);
            f->f_uid = current->fsuid;                //用户uid为当前用户的用户uid
            f->f_gid = current->fsgid;                //组id为当前组的id
            f->f_owner.lock = RW_LOCK_UNLOCKED;
            /* f->f_version: 0 */
            INIT_LIST_HEAD(&f->f_list);
            return f;
        }
    }

    /* Ran out of filps - report that */
    if (files_stat.max_files >= old_max) {
        printk(KERN_INFO "VFS: file-max limit %d reached\n",
                    files_stat.max_files);
        old_max = files_stat.max_files;
    } else {
        /* Big problems... */
        printk(KERN_WARNING "VFS: filp allocation failed\n");
    }
fail:
    return NULL;
}
目录项对象

目录项是分级创建的,例如如果要查找/usr/bin/tmp,内核为/创建一个目录项对象,为根目录下的usr项创建一个第二级目录项对象,为/usr目录下的bin项创建一个第三级目录项对象,依次类推。
注意,目录项对象在磁盘上并没有对应的映像,因此在dentry结构中不包含之处该对象已被修改的字段,目录项对象存放在名为dentry_cache的slab分配器高速缓存中,因此,目录项对象的创建和删除使通过调用kmem_cache_alloc()和kmem_cache_free()实现的。

struct dentry{
    atomic_t               d_count     //目录项对象引用计数器
    unsigned int           d_flag      //目录项高速缓存标志
    spinlock_t             d_lock      //保护目录项对象的自旋锁
    struct inode           *d_inode    //与文件名关联的索引节点
    struct dentry          *d_parent   //父目录的目录项对象
    struct qstr            d_name      //文件名
    struct list_head       d_lru       //未使用目录项链表的指针
    struct list_head       d_clild     //对目录而言,用于同一父目录中的目录项链表的指针
    struct list_head       d_subdirs   //对目录而言,子目录项链表的头
    struct dentry_operations  *d_op     //目录项方法
    struct super_block        *d_sb     //文件的超级块对象
    void *                    d_fsdata  //依赖于文件系统的数据
    struct dcookies_struct   *d_cookie  //指向内核配置文件使用的数据结构的指针
    struct hlist_head        d_hash     //指向散列表表项链表的指针
    int                     d_mounted   //对目录而言,用于记录安装该目录项的文件系统的计数器
    unsigned char []        d_iname     //存放短文件名的空间    
}
文件系统与这四个对象之前的关系

linux虚拟文件系统(VFS)(1)
linux虚拟文件系统(VFS)(1)

看一个常用的超级块操作方法
//这个操作是为索引节点对象分配空间,包括具体文件系统的数据所需要的空间
static struct inode *alloc_inode(struct super_block *sb)
{
    static struct address_space_operations empty_aops; //页高速缓存操作
    static struct inode_operations empty_iops;         //索引节点操作
    static struct file_operations empty_fops;          //文件对象操作
    struct inode *inode;

//判断如果超级块操作已经定义了alloc_inode方法,就使用超级块操作为inode分配空间,否则使用keme_cache_alloc在缓存中为inode分配空间
    if (sb->s_op->alloc_inode)
        inode = sb->s_op->alloc_inode(sb);
    else
        inode = (struct inode *) kmem_cache_alloc(inode_cachep, SLAB_KERNEL);

    if (inode) {
        struct address_space * const mapping = &inode->i_data;         //当前inode的address_space对象

        inode->i_sb = sb;
        inode->i_blkbits = sb->s_blocksize_bits;                       //块的大小(以位为单位)
        inode->i_flags = 0;
        atomic_set(&inode->i_count, 1);
        inode->i_sock = 0;
        inode->i_op = &empty_iops;
        inode->i_fop = &empty_fops;
        inode->i_nlink = 1;
        atomic_set(&inode->i_writecount, 0);
        inode->i_size = 0;
        inode->i_blocks = 0;
        inode->i_bytes = 0;
        inode->i_generation = 0;
#ifdef CONFIG_QUOTA
        memset(&inode->i_dquot, 0, sizeof(inode->i_dquot));
#endif
        inode->i_pipe = NULL;
        inode->i_bdev = NULL;
        inode->i_cdev = NULL;
        inode->i_rdev = 0;
        inode->i_security = NULL;
        inode->dirtied_when = 0;
        if (security_inode_alloc(inode)) {
            if (inode->i_sb->s_op->destroy_inode)
                inode->i_sb->s_op->destroy_inode(inode);
            else
                kmem_cache_free(inode_cachep, (inode));
            return NULL;
        }

        mapping->a_ops = &empty_aops;
        mapping->host = inode;
        mapping->flags = 0;
        mapping_set_gfp_mask(mapping, GFP_HIGHUSER);
        mapping->assoc_mapping = NULL;
        mapping->backing_dev_info = &default_backing_dev_info;

        /*
         * If the block_device provides a backing_dev_info for client
         * inodes then use that.  Otherwise the inode share the bdev's
         * backing_dev_info.
         */
        if (sb->s_bdev) {
            struct backing_dev_info *bdi;

            bdi = sb->s_bdev->bd_inode_backing_dev_info;
            if (!bdi)
                bdi = sb->s_bdev->bd_inode->i_mapping->backing_dev_info;
            mapping->backing_dev_info = bdi;
        }
        memset(&inode->u, 0, sizeof(inode->u));
        inode->i_mapping = mapping;
    }
    return inode;
}

#####虚拟文件系统基本的知识差不多就这些了,这些东西在网上很多博主都有写过,但是既然要学习linux内核,光知道基本知识还是不够的,还是是结合内核源代码来看,后面的内容,会结合代码一起讲
----------

二.虚拟文件系统的内核源码解析

目录项高速缓存

内核使用d_lookup()函数在散列表中查找给定的父目录对象和文件名,为了避免发生竞争,使用顺序锁(seqlock).__d_lookup()函数与之类似,不过它假定不会发生竞争,因此不使用顺序锁。

struct dentry * d_lookup(struct dentry * parent, struct qstr * name)   //qstr是文件名,通过文件名查找对应的目录项对象
{
    struct dentry * dentry = NULL;
    unsigned long seq;

        do {
                seq = read_seqbegin(&rename_lock);
                dentry = __d_lookup(parent, name);
                if (dentry)
            break;
    } while (read_seqretry(&rename_lock, seq));
    return dentry;
}
struct dentry * __d_lookup(struct dentry * parent, struct qstr * name)
{
    unsigned int len = name->len;
    unsigned int hash = name->hash;
    const unsigned char *str = name->name;
    struct hlist_head *head = d_hash(parent,hash);
    struct dentry *found = NULL;
    struct hlist_node *node;

    rcu_read_lock();      //禁止内核抢占

    hlist_for_each_rcu(node, head) {     //哈希查找
        struct dentry *dentry; 
        struct qstr *qstr;

        dentry = hlist_entry(node, struct dentry, d_hash);

        smp_rmb();    //内存屏障的东西 不太明白

        if (dentry->d_name.hash != hash)
            continue;
        if (dentry->d_parent != parent)
            continue;

        spin_lock(&dentry->d_lock);

        /*
         * If lookup ends up in a different bucket due to concurrent
         * rename, fail it
         */
        if (unlikely(dentry->d_bucket != head))
            goto terminate;

        /*
         * Recheck the dentry after taking the lock - d_move may have
         * changed things.  Don't bother checking the hash because we're
         * about to compare the whole name anyway.
         */
        if (dentry->d_parent != parent)
            goto next;

        qstr = rcu_dereference(&dentry->d_name);
        if (parent->d_op && parent->d_op->d_compare) {
            if (parent->d_op->d_compare(parent, qstr, name))
                goto next;
        } else {
            if (qstr->len != len)
                goto next;
            if (memcmp(qstr->name, str, len))
                goto next;
        }

        if (!d_unhashed(dentry)) {
            atomic_inc(&dentry->d_count);
            found = dentry;
        }
terminate:
        spin_unlock(&dentry->d_lock);
        break;
next:
        spin_unlock(&dentry->d_lock);
    }
    rcu_read_unlock();

    return found;
}
进程与文件对象

每个进程都有它自己的当前的工作目录和它自己的根目录。

struct fs_struct{
    atomic_t   count        //共享这个表的进程个数
    rwlock_t   lock         //用于表中字段的读/写自旋锁
    struct dentry *  root   //根目录的目录项
    struct dentey *  pwd    //当前工作目录的目录项
    struct detry  *  altroot //模拟根目录的目录项
    struct vfsmount *   rootmnt //根目录所安装的文件系统对象
    struct vfsmount *   pwdmnt  //当前工作目录所安装的文件系统对象
    struct vfsmount *   altrootmnt //模拟根目录所安装的文件系统对象
}

进程描述符files字段,表示进程当前打开的文件,该表的类型为files_struct结构

struct files_struct{
    atomic_t  count         //有几个进程使用该表
    rwlock_t file_lock      //用于表中字段的读/写自旋锁
    int      max_fds        //文件对象的当前最大数目
    int      max_fdset      //文件描述符的当前最大数目
    int next_fd             //所分配的最大文件描述符加1
    struct file ** fd       //指向文件对象数组的指针
    fd_set   open_fds       //指向打开文件描述符的指针
    struct file *[] fd_array //文件对象指针的初始化数组
}
特殊文件系统

特殊文件系统可以为系统程序员和管理员提供一种容易的方式来操作内核的的数据结构并实现操作系统的特殊特征。
内核给每个安装的特殊文件系统分配一个虚拟的块设备,让其 主设备号0而次设备号具有任意值,set_anon_super()函数用于初始化特殊文件系统的超级块,该函数本质上获得一个未使用的次设备号dev,然后用主设备号0和次设备号dev设置新超级块的s_dev字段。而另一个kill_anon_super()函数移走特殊文件系统的超级块。unnamed_dev_idr变量包含指向一个辅助结构的指针。

//为超级块获得一个未使用的次设备号dev
int set_anon_super(struct super_block *s, void *data)
{
    int dev;
    int error;

 retry:
    if (idr_pre_get(&unnamed_dev_idr, GFP_ATOMIC) == 0)    //分配可用的idr_layer
        return -ENOMEM;
    spin_lock(&unnamed_dev_lock);
    error = idr_get_new(&unnamed_dev_idr, NULL, &dev);   //unnamed_dev_idr 记录当前在用的次设备号
    spin_unlock(&unnamed_dev_lock);
    if (error == -EAGAIN)
        /* We raced and lost with another CPU. */
        goto retry;
    else if (error)
        return -EAGAIN;

    if ((dev & MAX_ID_MASK) == (1 << MINORBITS)) {
        spin_lock(&unnamed_dev_lock);
        idr_remove(&unnamed_dev_idr, dev);
        spin_unlock(&unnamed_dev_lock);
        return -EMFILE;
    }
    s->s_dev = MKDEV(0, dev & MINORMASK);
    return 0;
}
EXPORT_SYMBOL(set_anon_super);
文件系统类型注册

文件系统的源代码一般存在内核映映像中或者被动态编译进内核中,VFS必须对代码目前在内核中的所有文件系统的类型进行追踪,这就是通过文件系统类型注册来实现的。
每个注册的文件系统都用一个类型为file_system_type的对象来表示

struct file_system_type{
    const char *      name         //文件系统名
    int               fs_flags     //文件系统类型标志
    struct super_block *(*)()   get_sb //读超级块的方法
    void (*)()         kill_sb     //删除超级块的方法
    struct file_system_type *next  //指向文件系统类型链表中下一个元素的指针
    struct list_head   fs_super    //具有相同文件系统类型的超级块对象链表的头
}

在系统初始化期间,调用register_filesystem()函数来注册编译时指定的每个文件系统,该函数把相应的file_system_type对象插入到文件系统类型的链表中。
get_fs_type()函数扫描已注册的文件系统链表以查找文件系统类型的name字段,并返回指向相应的file_system_type对象的指针

//注册编译时指定的文件系统
int register_filesystem(struct file_system_type * fs)
{
    int res = 0;
    struct file_system_type ** p;

    if (!fs)
        return -EINVAL;
    if (fs->next)
        return -EBUSY;
    INIT_LIST_HEAD(&fs->fs_supers);
    write_lock(&file_systems_lock);
    p = find_filesystem(fs->name);
    if (*p)
        res = -EBUSY;
    else
        *p = fs;
    write_unlock(&file_systems_lock);
    return res;
}

//获取指定文件系统的file_system_type
struct file_system_type *get_fs_type(const char *name)
{
    struct file_system_type *fs;

    read_lock(&file_systems_lock);
    fs = *(find_filesystem(name));
    if (fs && !try_module_get(fs->owner))
        fs = NULL;
    read_unlock(&file_systems_lock);
    if (!fs && (request_module("%s", name) == 0)) {
        read_lock(&file_systems_lock);
        fs = *(find_filesystem(name));
        if (fs && !try_module_get(fs->owner))
            fs = NULL;
        read_unlock(&file_systems_lock);
    }
    return fs;
}
命名空间(文件系统的安装点)

在linux中,每个进程都可以拥有自己的已安装文件系统树,叫做进程的命名空间

strcut namespace{
    atomic_t  count           //引用计数器
    struct vfsmount * root    //命名空间根目录的已安装文件描述符
    struct list_head  list    //所有已安装文件系统描述符链表的头
    struct rw_semaphore sem   //保护这个结构的读/写信号量
}
//已安装的文件系统描述符
struct vfsmount{
    struct list_head mnt_hash         //用于散列表链表的指针
    struct vfsmount * mnt_parent      //指向父文件系统,这个文件系统安装在其上
    struct dentry   * mnt_mountpoint  //指向这个文件系统安装点目录的dentry
    struct dentry   * mnt_root        //指向这个文件系统根目录的dentry
    struct super_block *mnt_sb        //指向这个文件系统根目录的超级块对象
    struct list_head mnt_mounts       //包含所以文件系统描述符链表的头
    struct list_head mnt_child        //用于已安装文件系统链表mnt_mounts的指针
    atomic_t mnt_count                //引用计数器
    int mnt_flags                     //标志
    char * mnt_devname                //设备文件名
}

//分配和初始化一个已安装文件系统描述符
struct vfsmount *alloc_vfsmnt(const char *name)
{
    struct vfsmount *mnt = kmem_cache_alloc(mnt_cache, GFP_KERNEL); 
    if (mnt) {
        memset(mnt, 0, sizeof(struct vfsmount));
        atomic_set(&mnt->mnt_count,1);
        INIT_LIST_HEAD(&mnt->mnt_hash);
        INIT_LIST_HEAD(&mnt->mnt_child);
        INIT_LIST_HEAD(&mnt->mnt_mounts);
        INIT_LIST_HEAD(&mnt->mnt_list);
        INIT_LIST_HEAD(&mnt->mnt_fslink);
        if (name) {
            int size = strlen(name)+1;
            char *newname = kmalloc(size, GFP_KERNEL);
            if (newname) {
                memcpy(newname, name, size);
                mnt->mnt_devname = newname;
            }
        }
    }
    return mnt;
}

//在散列表中查找一个描述符并返回它的地址
struct vfsmount *lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
{
    struct list_head * head = mount_hashtable + hash(mnt, dentry);
    struct list_head * tmp = head;
    struct vfsmount *p, *found = NULL;

    spin_lock(&vfsmount_lock);
    for (;;) {
        tmp = tmp->next;
        p = NULL;
        if (tmp == head)
            break;
        p = list_entry(tmp, struct vfsmount, mnt_hash);
        if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) {
            found = mntget(p);
            break;
        }
    }
    spin_unlock(&vfsmount_lock);
    return found;
}
安装普通文件系统

服务例程sys_mount用来安装一个普通的文件系统

/* dev_name 文件系统所在的设备文件的路径名
 * dir_name 文件系统的安装点
 * type     文件系统的类型,必须是已注册文件系统的名字
 * flags    安装标志
 * data     指向一个与文件系统相关的数据结构的指针
*/
asmlinkage long sys_mount(char __user * dev_name, char __user * dir_name,
              char __user * type, unsigned long flags,
              void __user * data)
{
    int retval;
    unsigned long data_page;
    unsigned long type_page;
    unsigned long dev_page;
    char *dir_page;
    //copy_mount_options()这个函数的作用是把文件系统的参数的值拷贝到临时内核缓冲区
    retval = copy_mount_options (type, &type_page);
    if (retval < 0)
        return retval;

    dir_page = getname(dir_name);    //安装点路径
    retval = PTR_ERR(dir_page);
    if (IS_ERR(dir_page))
        goto out1;

    retval = copy_mount_options (dev_name, &dev_page);
    if (retval < 0)
        goto out2;

    retval = copy_mount_options (data, &data_page);
    if (retval < 0)
        goto out3;

    lock_kernel();              //获取大内核锁
    retval = do_mount((char*)dev_page, dir_page, (char*)type_page,
              flags, (void*)data_page);
    unlock_kernel();            //释放大内核锁
    free_page(data_page); 

out3:
    free_page(dev_page);
out2:
    putname(dir_page);
out1:
    free_page(type_page);
    return retval;
}

//文件系统真正的安装操作
long do_mount(char * dev_name, char * dir_name, char *type_page,
          unsigned long flags, void *data_page)
{
    struct nameidata nd;
    int retval = 0;
    int mnt_flags = 0;

    /*中间的一些检查部分省略*/

    /* Separate the per-mountpoint flags */

    //将挂载标志转换为文件系统的安装标志
    if (flags & MS_NOSUID) 
        mnt_flags |= MNT_NOSUID;
    if (flags & MS_NODEV)
        mnt_flags |= MNT_NODEV;
    if (flags & MS_NOEXEC)
        mnt_flags |= MNT_NOEXEC;
    flags &= ~(MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_ACTIVE);

    /* ... and get the mountpoint */
//调用path_lookup查找安装点的路径,将结果存放在namespace(&nd)中
    retval = path_lookup(dir_name, LOOKUP_FOLLOW, &nd);
    if (retval)
        return retval;
    //安全检查
    retval = security_sb_mount(dev_name, &nd, type_page, flags, data_page);
    if (retval)
        goto dput_out;
    //检查安装标志决定做什么类型的安装
    if (flags & MS_REMOUNT)
        retval = do_remount(&nd, flags & ~MS_REMOUNT, mnt_flags,
                    data_page);
    else if (flags & MS_BIND)
        retval = do_loopback(&nd, dev_name, flags & MS_REC);
    else if (flags & MS_MOVE)
        retval = do_move_mount(&nd, dev_name);
    else
        retval = do_new_mount(&nd, type_page, flags, mnt_flags,   //安装一个新的文件系统
                      dev_name, data_page);
dput_out:
    path_release(&nd);
    return retval;
}

/* nd 新文件系统的安装点
 * type 文件系统的安装类型
 * flags 安装标志
 * mnt_flags 安装类型
 * name 文件系统所在设备的路径名
 * data 传递的一些数据的指针
*/
static int do_new_mount(struct nameidata *nd, char *type, int flags,
            int mnt_flags, char *name, void *data)
{
    struct vfsmount *mnt;

    if (!type || !memchr(type, 0, PAGE_SIZE))
        return -EINVAL;

    /* we need capabilities... */
    if (!capable(CAP_SYS_ADMIN))
        return -EPERM;

    mnt = do_kern_mount(type, flags, name, data);    //安装核心操作
    if (IS_ERR(mnt))
        return PTR_ERR(mnt);

    return do_add_mount(mnt, nd, mnt_flags, NULL);   //将新安装的文件系统加入到namespace链表中
}

/* fstype 要安装的文件系统的类型名
   flags  安装标志
   name   存放文件系统的块设备的路径名
   data   指向传递给文件系统的read_super方法的附加数据的指针
*/
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 super_block *sb = ERR_PTR(-ENOMEM);
    struct vfsmount *mnt;
    int error;
    char *secdata = NULL;

    if (!type)
        return ERR_PTR(-ENODEV);

    mnt = alloc_vfsmnt(name);      //分配一个新的vfsmount描述符
    if (!mnt)
        goto out;

    if (data) {
        secdata = alloc_secdata();
        if (!secdata) {
            sb = ERR_PTR(-ENOMEM);
            goto out_mnt;
        }

        error = security_sb_copy_data(type, data, secdata);
        if (error) {
            sb = ERR_PTR(error);
            goto out_free_secdata;
        }
    }

    sb = type->get_sb(type, flags, name, data);   //分配一个新的超级块,分配超级块的操作是具体文件系统定义的,后面会介绍一下ext2文件系统的获取超级块的操作。
    if (IS_ERR(sb))
        goto out_free_secdata;
    error = security_sb_kern_mount(sb, secdata);
    if (error)
        goto out_sb;
    //初始化mnt的一些重要字段
    mnt->mnt_sb = sb;
    mnt->mnt_root = dget(sb->s_root);
    mnt->mnt_mountpoint = sb->s_root;
    mnt->mnt_parent = mnt;
    mnt->mnt_namespace = current->namespace;
    up_write(&sb->s_umount);
    put_filesystem(type);
    return mnt;
out_sb:
    up_write(&sb->s_umount);
    deactivate_super(sb);
    sb = ERR_PTR(error);
out_free_secdata:
    free_secdata(secdata);
out_mnt:
    free_vfsmnt(mnt);
out:
    put_filesystem(type);
    return (struct vfsmount *)sb;
}
//以上,就是安装一个普通文件系统的全部过程
分配超级块对象(ext2为例)
/* fs_type 文件系統類型描述符
   flag    安裝標誌
   dev_name 安装的文件系统的所在设备的路径名
   void *data 指向传递给文件系统的read_super方法的附加数据的指针
*/
static struct super_block *ext2_get_sb(struct file_system_type *fs_type,
    int flags, const char *dev_name, void *data)
{
    return get_sb_bdev(fs_type, flags, dev_name, data, ext2_fill_super);
}
//get_sb_bdev函数分配并初始化一个新的适合于磁盘文件系统的超级块
卸载文件系统
/* name 安装点目录或者块设备文件名
   flag
*/
asmlinkage long sys_umount(char __user * name, int flags)
{
    struct nameidata nd;
    int retval;

    retval = __user_walk(name, LOOKUP_FOLLOW, &nd);   //查找安装点路径名并存放在nd中.
    if (retval)
        goto out;
    retval = -EINVAL;
    //如果查找的最终目录不是文件系统的安装点。
    if (nd.dentry != nd.mnt->mnt_root)
        goto dput_and_out;
    if (!check_mnt(nd.mnt))
        goto dput_and_out;

    retval = -EPERM;
    if (!capable(CAP_SYS_ADMIN))
        goto dput_and_out;

    retval = do_umount(nd.mnt, flags);  //卸载操作
dput_and_out:
    path_release_on_umount(&nd);
out:
    return retval;
}

上面的内容在书中都有介绍,代码的话可以自己找份内核源码参考。https://elixir.bootlin.com/linux/latest/source