1. 程式人生 > 其它 >mount 網路_mount系統呼叫(ksys_mount->do_mount->do_new_mount)

mount 網路_mount系統呼叫(ksys_mount->do_mount->do_new_mount)

技術標籤:mount 網路

52599cf90e99fa964128cbe5d3cdc63f.png

回顧

前文我們講了mount系統呼叫的用法,以及每個引數的含義。mount系統呼叫有5個引數,分別是:

  • source: 檔案系統所在裝置名稱(或網路檔案系統的remote掛載點等)
  • target: 要掛載到的位置,一般是目錄名。
  • filesystemtype: 檔案系統名稱。
  • mountflags: 檔案系統通用掛載選項。
  • data: 檔案系統特用掛載選項。

下面我們就來看一下這五個引數在經過mount系統呼叫進入核心之後是如何被傳遞的。(本文會涉及大量程式碼,解釋內容可能部分以程式碼註釋的形式出現)


SYSCALL_mount

這是核心執行mount系統呼叫的第一個地方,程式碼如下(來自Linux 4.17-rc2 fs/namespace.c):

SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,                       
                char __user *, type, unsigned long, flags, void __user *, data)                
{                                              
        return ksys_mount(dev_name, dir_name, type, flags, data);                              
} 

沒有什麼特別的(本文不講解系統呼叫定義過程),mount的五個引數中的四個指標引數所對應的內容還處在使用者層(__user),並別對應了dev_name, dirname, type, flags 和data,內容沒有做任何更改,從字面上也很容易理解。接著直接將引數傳遞給ksys_mount函式繼續處理(老kernel是沒有封裝到ksys_mount函式的)(來自Linux 4.17-rc2 fs/namespace.c):

int ksys_mount(char __user *dev_name, char __user *dir_name, char __user *type,                
               unsigned long flags, void __user *data)                                         
{                                              
        int ret;                               
        char *kernel_type;                     
        char *kernel_dev;                      
        void *options;                         

        kernel_type = copy_mount_string(type); 
        ret = PTR_ERR(kernel_type);            
        if (IS_ERR(kernel_type))               
                goto out_type;                 

        kernel_dev = copy_mount_string(dev_name);                                              
        ret = PTR_ERR(kernel_dev);             
        if (IS_ERR(kernel_dev))                
                goto out_dev;                  

        options = copy_mount_options(data);    
        ret = PTR_ERR(options);                
        if (IS_ERR(options))                   
                goto out_data;                 

        ret = do_mount(kernel_dev, dir_name, kernel_type, flags, options);                     

        kfree(options);                        
out_data:                                      
        kfree(kernel_dev);                     
out_dev:                                       
        kfree(kernel_type);                    
out_type:                                      
        return ret;                            
}

ksys_mount函式很簡單,copy_mount_string將使用者層的內容拷貝到核心層(至於dir_name為什麼沒有被這樣處理我們下面再說),然後就是呼叫do_mount做下面的工作

ksys_mount->do_mount

來自Linux 4.17-rc2 fs/namespace.c):

/*
 * Flags is a 32-bit value that allows up to 31 non-fs dependent flags to
 * be given to the mount() call (ie: read-only, no-dev, no-suid etc).
 *
 * data is a (void *) that can point to any structure up to
 * PAGE_SIZE-1 bytes, which can contain arbitrary fs-dependent
 * information (or be NULL).
 *
 * Pre-0.97 versions of mount() didn't have a flags word.
 * When the flags word was introduced its top half was required
 * to have the magic value 0xC0ED, and this remained so until 2.4.0-test9.
 * Therefore, if this magic number is present, it carries no information
 * and must be discarded.
 */
long do_mount(const char *dev_name, const char __user *dir_name,
                const char *type_page, unsigned long flags, void *data_page)
{
        struct path path;
        // 注意這裡, 後面flags將被分為mnt_flags和sb_flags,
        // 這個在老的核心裡是沒有分開的
        unsigned int mnt_flags = 0, sb_flags;
        int retval = 0;

        /* Discard magic */
        if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
                flags &= ~MS_MGC_MSK;

        /* Basic sanity checks */
        if (data_page)
                ((char *)data_page)[PAGE_SIZE - 1] = 0;

        if (flags & MS_NOUSER)
                return -EINVAL;

        // 上面為什麼沒有對dir_name做copy_mount_string操作, 答案在這。
        // 這裡用user_path直接將dir_name轉撐struct path的格式放入核心層。
        // path是核心做路徑名操作的常用結構體。
        /* ... and get the mountpoint */
        retval = user_path(dir_name, &path);
        if (retval)
                return retval;

        // 這裡和LSM有關,是另外話題
        retval = security_sb_mount(dev_name, &path,
                                   type_page, flags, data_page);
        if (!retval && !may_mount())
                retval = -EPERM;
        // 如果flags使能SB_MANDLOCK,檢查當前kernel是否支援MANDATORY_FILE_LOCKING
        if (!retval && (flags & SB_MANDLOCK) && !may_mandlock())
                retval = -EPERM;
        if (retval)
                goto dput_out;

        // 下面英語說的很清楚...
        /* Default to relatime unless overriden */
        if (!(flags & MS_NOATIME))
                mnt_flags |= MNT_RELATIME;

        // 還在處理flags,對於這些flags的含義請從參考上一篇文章。
        /* 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;
        if (flags & MS_NOATIME)
                mnt_flags |= MNT_NOATIME;
        if (flags & MS_NODIRATIME)
                mnt_flags |= MNT_NODIRATIME;
        if (flags & MS_STRICTATIME)
                mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);
        if (flags & MS_RDONLY)
                mnt_flags |= MNT_READONLY;

        // 和remount相關的處理
        /* The default atime for remount is preservation */
        if ((flags & MS_REMOUNT) &&
            ((flags & (MS_NOATIME | MS_NODIRATIME | MS_RELATIME |
                       MS_STRICTATIME)) == 0)) {
                mnt_flags &= ~MNT_ATIME_MASK;
                mnt_flags |= path.mnt->mnt_flags & MNT_ATIME_MASK;
        }

        // 從flags中提出sb_flags相關的部分
        sb_flags = flags & (SB_RDONLY |
                            SB_SYNCHRONOUS |
                            SB_MANDLOCK |
                            SB_DIRSYNC |
                            SB_SILENT |
                            SB_POSIXACL |
                            SB_LAZYTIME |
                            SB_I_VERSION);

        // 下面根據flags的標記,決定是做下面哪種mount操作。
        // do_new_mount是最普通,我們最常見的mount。bind,move,remount以及和
        // shared-sub-tree有關操作的含義參見相關man page以及Documentation/filesystems/sharedsubtree.txt
        // 相關文件。
        if (flags & MS_REMOUNT)
                retval = do_remount(&path, flags, sb_flags, mnt_flags,
                                    data_page);
        else if (flags & MS_BIND)
                retval = do_loopback(&path, dev_name, flags & MS_REC);
        else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
                retval = do_change_type(&path, flags);
        else if (flags & MS_MOVE)
                retval = do_move_mount(&path, dev_name);
        else
                retval = do_new_mount(&path, type_page, sb_flags, mnt_flags,
                                      dev_name, data_page);
dput_out:
        path_put(&path);
        return retval;
}

從do_mount的程式碼可以它主要就是:

  1. 將dir_name解析為path格式到核心
  2. 一路解析flags位表,將flags拆分位mnt_flags和sb_flags
  3. 根據flags中的標記,決定下面做哪一個mount操作。

為了保持文章的連貫性,這裡就追蹤最普通的mount操作向下看,即do_new_mount。其它mount操作如果有時間再另外講解。

ksys_mount->do_mount->do_new_mount

注意到了do_new_mount這裡原來的五個引數變成了六個引數,flags被分成了兩部分,dir_name也變成了path結構,其他還保持原格式被存入核心page中。

(來自Linux 4.17-rc2 fs/namespace.c)

/*
 * create a new mount for userspace and request it to be added into the
 * namespace's tree
 */
static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
                        int mnt_flags, const char *name, void *data)
{
        struct file_system_type *type;
        struct vfsmount *mnt;
        int err;

        if (!fstype)
                return -EINVAL;

        // get_fs_type我們在第一篇講file_system_type的時候就提到過,它是根據檔案系統
        // 名稱找到對應的已註冊的file_system_type例項。這個例項裡有一個很重要的東西就是
        // mount回撥函式。
        type = get_fs_type(fstype);
        if (!type)
                return -ENODEV;

        // 這個地方是很重要的一步,也是我們第一次接觸vfsmount這個結構的地方。
        // 先簡單來說vfs_kern_mount會呼叫特定檔案系統型別(type裡)的mount回撥函式,
        // 構建好一個vfsmount結構。具體怎麼做的我們等下面講vfsmount的時候再說。
        mnt = vfs_kern_mount(type, sb_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);
        if (IS_ERR(mnt))
                return PTR_ERR(mnt);

        if (mount_too_revealing(mnt, &mnt_flags)) {
                mntput(mnt);
                return -EPERM;
        }

        // 將得到的vfsmount結構加入全域性目錄樹。
        err = do_add_mount(real_mount(mnt), path, mnt_flags);
        if (err)
                mntput(mnt);
        return err;
}

到此有兩個重要的地方vfs_kern_mount和do_add_mount。按照邏輯順序我們下面應該說到do_add_mount了,但是在此之前我們得先解釋一下vfs_kern_mount和vfsmount結構。

struct mount和struct vfsmount

struct mount代表著一個mount例項(一次真正掛載對應一個mount例項),其中struct vfsmount定義的mnt成員是它最核心的部分。過去沒有stuct mount,mount和vfsmount的成員都在vfsmount裡,現在linux將vfsmount改作mount結構體,並將mount中mnt_root, mnt_sb, mnt_flags成員移到vfsmount結構體中了。這樣使得vfsmount的內容更加精簡,在很多情況下只需要傳遞vfsmount而已。

來看一下struct vfsmount的定義(來自Linux 4.17-rc2 include/linux/mount.h)

struct vfsmount {
        struct dentry *mnt_root;        /* 指向這個檔案系統的根的dentry */
        struct super_block *mnt_sb;     /* 指向這個檔案系統的超級塊物件 */
        int mnt_flags;                  /* 此檔案系統的掛載標誌 */
} __randomize_layout;

以及struct mount的定義(來自Linux 4.17-rc2 fs/mount.h)

struct mount {
        struct hlist_node mnt_hash;    /* 用於連結到全域性已掛載檔案系統的連結串列 */
        struct mount *mnt_parent;      /* 指向此檔案系統的掛載點所屬的檔案系統,即父檔案系統 */ 
        struct dentry *mnt_mountpoint; /* 指向此檔案系統的掛載點的dentry */
        struct vfsmount mnt;           /* 指向此檔案系統的vfsmount例項 */
        union {
                struct rcu_head mnt_rcu;
                struct llist_node mnt_llist;
        };
#ifdef CONFIG_SMP
        struct mnt_pcp __percpu *mnt_pcp;
#else
        int mnt_count;
        int mnt_writers;
#endif
        struct list_head mnt_mounts;    /* 掛載在此檔案系統下的所有子檔案系統的連結串列的表頭,下面的節點都是mnt_child */
        struct list_head mnt_child;     /* 連結到被此檔案系統所掛的父檔案系統的mnt_mounts上 */
        struct list_head mnt_instance;  /* 連結到sb->s_mounts上的一個mount例項 */
        const char *mnt_devname;        /* 裝置名,如/dev/sdb1 */
        struct list_head mnt_list;      /* 連結到程序namespace中已掛載檔案系統中,表頭為mnt_namespace的list域 */ 
        struct list_head mnt_expire;    /* 連結到一些檔案系統專有的過期連結串列,如NFS, CIFS等 */
        struct list_head mnt_share;     /* 連結到共享掛載的迴圈連結串列中 */
        struct list_head mnt_slave_list;/* 此檔案系統的slave mount連結串列的表頭 */
        struct list_head mnt_slave;     /* 連線到master檔案系統的mnt_slave_list */
        struct mount *mnt_master;       /* 指向此檔案系統的master檔案系統,slave is on master->mnt_slave_list */
        struct mnt_namespace *mnt_ns;   /* 指向包含這個檔案系統的程序的name space */
        struct mountpoint *mnt_mp;      /* where is it mounted */
        struct hlist_node mnt_mp_list;  /* list mounts with the same mountpoint */
        struct list_head mnt_umounting; /* list entry for umount propagation */
#ifdef CONFIG_FSNOTIFY
        struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
        __u32 mnt_fsnotify_mask;
#endif
        int mnt_id;                     /* mount identifier */
        int mnt_group_id;               /* peer group identifier */
        int mnt_expiry_mark;            /* true if marked for expiry */
        struct hlist_head mnt_pins;
        struct fs_pin mnt_umount;
        struct dentry *mnt_ex_mountpoint;
} __randomize_layout;

如果你被struct mount的結構嚇到了,那你是可以被理解的。能理解裡面所有成員真正含義和用法及所有細節的人基本上就是Al Viro(VFS的maintainer)了。還好你在面對一個最普通的mount操作時不需要理解struct mount裡所有的內容,後面我們會單獨講mount例項在掛載操作中的用途(當然也僅限普通掛載)。

struct vfsmount裡面的東西倒是比較少,只有三個。這3個引數也就是通過vfs_kern_mount來得到的。我們將在下一篇文章講述怎麼通過vfs_kern_mount得到一個vfsmount(以及一個半初始化struct mount)。在這裡先知道,在全域性檔案系統(目錄結構)樹上一個檔案的位置不是由dentry唯一確定,因為有了掛載關係,一切都變的複雜了,比如一個檔案系統可以掛裝載到不同的掛載點。所以檔案系統樹的一個位置要由<mount, dentry>二元組(或者說<vfsmount, dentry>)來確定(路徑名查詢是另一個大坑,那就不是下幾篇文章講的事情了,那是另一系列文章的問題了...)。


更多內容請參閱:

醉臥沙場:README - 專欄文章總索引