mount 網路_mount系統呼叫(ksys_mount->do_mount->do_new_mount)
技術標籤:mount 網路
回顧
前文我們講了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的程式碼可以它主要就是:
- 將dir_name解析為path格式到核心
- 一路解析flags位表,將flags拆分位mnt_flags和sb_flags
- 根據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 - 專欄文章總索引