xfs檔案系統:格式化以及掛載
引言
情景:
<原始碼> linux:3.14.56 xfsprogs:3.2.0
<命令> mkfs.xfs -f /dev/[sdx] ; mount /dev/[sdx]; umount /dev/[sdx]
如情景所示,來分析分析,mkfs.xfs mount 以及umount操作都做了些什麼事情。下述內容均為本人隨意跟蹤,看到那說到哪!僅作參考。
MKFS.XFS
本例格式化xfs檔案系統,有關xfs檔案系統就不介紹了。倒是首先要分析mkfs.xfs命令,先得獲取相應的原始碼,原始碼獲取可到github搜尋xfsprogs,之前找了一個util-linux的包,編譯後才發覺不支援mkfs.xfs,其它的檔案系統倒還支援一些的,可見xfs還是比較“特殊”的,值得了解一下。好吧,總之下載即可。
- mkfs.xfs -f /dev/sdc
- 原始碼分析
1、進入 xfs_mkfs.c檔案主函式main。對於一個提供給使用者使用的終端命令(或說控制介面),它做的事情無非就是在接受使用者指令後,對相應指令引數進行解析,而後下發給後端進/執行緒做進一步處理,當然最終會返回資訊給當前主程序,處理完事務後(同步的話),程序也就退出了。
- 原始碼分析
... ...
while ((c = getopt(argc, argv, "b:d:i:l:L:m:n:KNp:qr:s:CfV")) != EOF) {
switch (c) {
case 'C':
case 'f':
force_overwrite = 1;
break;
... ...
跟蹤force_overwrite標誌:
memset(&ft, 0, sizeof(ft)); ----/1/
get_topology(&xi, &ft, force_overwrite); ----/2/
/1/ 首先初始化以ft為首地址的fs_topology結構體,那麼不妨先來看看這個結構體的內容有哪些:
/*
* Device topology information.
*/
struct fs_topology {
int dsunit; /* stripe unit - data subvolume:*/
int dswidth; /* stripe width - data subvolume */
int rtswidth; /* stripe width - rt subvolume */
int lsectorsize; /* logical sector size &*/
int psectorsize; /* physical sector size */
int sectoralign; //扇區對齊標誌:該標誌位置位1時,要求處理的塊大小與扇區大小相同(老linux版本中),因而通常不會置位。
};
/2/ 若ENABLE_BLKID有定義,即安裝有blkid原始碼,即可執行blkid命令,獲取裝置的拓撲結構如下:
static void get_topology(
libxfs_init_t *xi,
struct fs_topology *ft,
int force_overwrite)
{
if (!xi->disfile) { ----/2.1/
const char *dfile = xi->volname ? xi->volname : xi->dname;
blkid_get_topology(dfile, &ft->dsunit, &ft->dswidth, ----/2.2/
&ft->lsectorsize, &ft->psectorsize,
force_overwrite);
}
if (xi->rtname && !xi->risfile) {
int dummy;
blkid_get_topology(xi->rtname, &dummy, &ft->rtswidth,
&dummy, &dummy, force_overwrite);
}
}
/2.1/ libxfs_init_t函式memset初始化為0,其成員變數disfile可知其初始化為0,又當前mkfs.xfs命令未加-d選項,則進入該分支處理。下面分支同理。
/2.2/ 處理獲取裝置拓撲結構,這裡不好追蹤就不展開了,關鍵函式定義實現在blkid命令原始碼中如:當前函式中的blkid_new_probe_from_filename。
/2// 若ENABLE_BLKID無定義,獲取裝置的拓撲結構:
static void get_topology(
libxfs_init_t *xi,
struct fs_topology *ft,
int force_overwrite)
{
char *dfile = xi->volname ? xi->volname : xi->dname;
int bsz = BBSIZE;
if (!xi->disfile) {
int fd;
long long dummy;
get_subvol_stripe_wrapper(dfile, SVTYPE_DATA, ----/2.1//
&ft->dsunit, &ft->dswidth, &ft->sectoralign);
fd = open(dfile, O_RDONLY);
/* If this fails we just fall back to BBSIZE */
if (fd >= 0) {
platform_findsizes(dfile, fd, &dummy, &bsz);
close(fd);
}
}
ft->lsectorsize = bsz;
ft->psectorsize = bsz;
if (xi->rtname && !xi->risfile) {
int dummy1;
get_subvol_stripe_wrapper(dfile, SVTYPE_RT, &dummy1,
&ft->rtswidth, &dummy1);
}
}
/2.1// 不妨進入該函式:
void
get_subvol_stripe_wrapper(
char *dev,
sv_type_t type,
int *sunit,
int *swidth,
int *sectalign)
{
struct stat64 sb;
if (dev == NULL)
return;
if (stat64(dev, &sb)) {
fprintf(stderr, _("Cannot stat %s: %s\n"),
dev, strerror(errno));
exit(1);
}
if ( dm_get_subvol_stripe(dev, type, sunit, swidth, sectalign, &sb))
return;
if ( md_get_subvol_stripe(dev, type, sunit, swidth, sectalign, &sb)) ----/2.1.1//
return;
if ( lvm_get_subvol_stripe(dev, type, sunit, swidth, sectalign, &sb))
return;
if ( xvm_get_subvol_stripe(dev, type, sunit, swidth, sectalign, &sb))
return;
if (evms_get_subvol_stripe(dev, type, sunit, swidth, sectalign, &sb))
return;
//可新增其它型別的裝置驅動,資訊獲取如上格式
/* ... add new device drivers here */
}
/2.1.1// 分析它即可,其它的同理。顧名思義,即獲取Multiple Devices相關的東西,也就是有關矩陣raid條帶化的資訊,因為mkfs.xfs命令未加-d選項,故而獲取的是建立md裝置時指定的條帶資訊。進入該函式可知,主要是對其引數進行賦值。
返回main主函式,呼叫libxfs_init函式進行xfs檔案系統的初始化,主要關注一下函式:
1、radix_tree_init
基樹初始化,主要用於記憶體管理,該樹為典型的字典型別結構(有待研究)
2、libxfs_device_open
開啟一個裝置並且獲取其裝置號,即便不是真的裝置亦返回一個偽裝置號
3、cache_init
初始化快取,返回cache結構體
4、manage_zones
manage_zones函式實現釋放xfs分割槽或生成xfs分割槽。而生成啟動xfs目錄結構呼叫的是xfs_dir_startup函式,該函式定義在linux原始碼lib庫中(可使得生成目錄下”.”以及“..”檔案)
回到main函式,進入判斷force_overwrite標誌位的分支結構,呼叫zero_old_xfs_structures函式,函式將置0初始化各個次要AG的sb超級塊結構,通常128M至4T左右容量設的備,4個AG用於管理磁碟空間。當然若是raid陣列的話就另當別論了,相同容量需建立更多的AG。AG超級塊結構參考:http://www.lenky.info/archives/2012/01/648
回到main函式,呼叫libxfs_mount —-/3/,該函式主要就是對結構體進行填充,不過卻是非常重要的,不妨來看看:
/*
* Mount structure initialization, provides a filled-in xfs_mount_t
* such that the numerous XFS_* macros can be used. If dev is zero,
* no IO will be performed (no size checks, read root inodes).
*/
xfs_mount_t * ----/3.1/
libxfs_mount(
xfs_mount_t *mp,
xfs_sb_t *sb,
dev_t dev,
dev_t logdev,
dev_t rtdev,
int flags)
{
、、、
xfs_sb_mount_common(mp, sb); ----/3.2/
、、、
xfs_dir_mount(mp); ----/3.3/
、、、
libxfs_readbuf ----/3.4/
、、、
libxfs_putbuf ----/3.5/
、、、
/3.1/ 首先要理解的是xfs_mount_t這個結構體型別,我們定義了一個使用者層面上的xfs_mount結構體,同時方便了使用以XFS_*為字首的巨集。即該函式填充xfs_umount結構體,並將其返回,以供使用者程序訪問,也就是mkfs.xfs了。(注:即便裝置上的檔案系統格式化了,在未mount掛載前也是不能直接訪問的)。而在程序使用完後,即在main函式最後,會呼叫libxfs_umount釋放佔用的資源(可理解xfs_mount為中間變數)。
/3.2/ 而該函式使用xfs_sb超級塊的資訊,建立了各類通用的mount掛載域
/3.3/ 顧名思義,
/3.4/ 填充好xfs_mount資訊後,將其讀取到相應的快取區xfs_buf中
/3.5/ 而libxfs_putbuf –> cache_node_put,該函式將xfs_buf型別強制轉化成cache_node型別,並新增至cache下發連結串列。最終使得以完成裝置檔案系統的格式化。
最後回到main函式,呼叫libxfs_getsb,同理該函式同樣讀取xfs_buf快取中的內容,若讀取出來的超級塊資訊與寫入的相同,則標識檔案系統格式化成功。到此,程序mkfs.xfs的整個執行流程也算是結束了,當然中間有很多東西都無視了(呵呵)
MOUNT
即將格式化xfs的磁碟掛載到相應目錄
- mount /dev/[sdx] /mnt
- 原始碼分析
首先應用層shell實現系統呼叫sys_mount,而在本linux原始碼版本:呼叫SYSCALL_DEFINE5,申明定義在include/linux/syscall.h中。至於為什麼使用這種方式定義sys_mount,實質上是為在64位的核心上實現呼叫32位的系統呼叫(有待研究)。
- 原始碼分析
asmlinkage long sys_mount(char __user *dev_name, char __user *dir_name, ----/0/
char __user *type, unsigned long flags,
void __user *data);
函式sys_mount由SYSCALL_DEFINE5(5:代表引數個數)定義:
#define SYSCALL_DEFINE0(sname) \
SYSCALL_METADATA(_##sname, 0); \
asmlinkage long sys_##sname(void)
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \
__attribute__((alias(__stringify(SyS##name)))); \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
{ \
long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \
__MAP(x,__SC_TEST,__VA_ARGS__); \
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
return ret; \
} \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))
在核心中,結構體以及巨集函式的使用可謂出神入化。有關這裡的巨集函式定義如何實現的,暫不展開,不好表達!
其實如下示:compat_sys_mount函式定義與上述SYSCALL_DEFINE5非常相似,而與sys_mount也似乎存在著某種聯絡(有待研究):
/include/uapi/asm-generic/unistd.h:
#define __NR_mount 40
__SC_COMP(__NR_mount, sys_mount, compat_sys_mount)
/0/ 上述的asmlinkage標識著呼叫函式引數的傳遞方式。若加該標識標識使用堆疊,預設時使用的是暫存器傳遞函式引數。如:彙編中呼叫c函式,且使用堆疊傳遞函式,則在定c義函式時,需在函式前新增巨集asmlinkage。需要注意的是核心只在系統呼叫時使用該巨集,原因:一是,普通核心函式呼叫暫存器傳參比堆疊傳參快很多,想想就是無需解釋。二是:對於系統呼叫時使用堆疊傳參必然有其的合理性。有關第二點說到的合理性,不妨稍作解釋,就不展開了:
1,涉及到使用者態與核心態的轉換,轉換過程實現需系統呼叫。也就是說系統呼叫函式既需訪問使用者態資源,與此同時訪問核心資源。而核心與使用者態的資源組織方式(可以這麼說吧!)可是不同的,如:地址空間的對映方式就不同。
2、有人說堆疊的資訊來源不就是來自暫存器的copy嗎,我想其實兩者都能實現引數的傳遞,其本質不都是要實現壓棧,現場保護嗎。只不過使用者態與核心態的轉換,或說既要實現兩者之間的通訊,又要實現兩者的隔離。而畢竟核心態的東西使用使用者態堆疊訪問方式相當於做了一些隔離。在此可能真的犧牲了一些傳參效率吧!(呵呵)。
下面不妨進入SYSCALL_DEFINE5函式定義,看看它做了些什麼:
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
char __user *, type, unsigned long, flags, void __user *, data)
{
int ret;
char *kernel_type;
struct filename *kernel_dir;
char *kernel_dev;
unsigned long data_page;
ret = copy_mount_string(type, &kernel_type); ----/1/
if (ret < 0)
goto out_type;
kernel_dir = getname(dir_name);
if (IS_ERR(kernel_dir)) {
ret = PTR_ERR(kernel_dir);
goto out_dir;
}
ret = copy_mount_string(dev_name, &kernel_dev);
if (ret < 0)
goto out_dev;
ret = copy_mount_options(data, &data_page); ----/2/
if (ret < 0)
goto out_data;
ret = do_mount(kernel_dev, kernel_dir->name, kernel_type, flags, ----/3/
(void *) data_page);
free_page(data_page);
out_data:
kfree(kernel_dev);
out_dev:
putname(kernel_dir);
out_dir:
kfree(kernel_type);
out_type:
return ret;
}
/1/ 該函式將mount掛載的選項字串核心的一個空閒頁中,如下示:
int copy_mount_string(const void __user *data, char **where)
{
char *tmp;
if (!data) {
*where = NULL;
return 0;
}
//成功返回指向核心PAGE大小資料頁指標
tmp = strndup_user(data, PAGE_SIZE); ----/1.1/
if (IS_ERR(tmp))
return PTR_ERR(tmp);
*where = tmp;
return 0;
}
/1.1/ 該函式最終呼叫copy_from_user函式,實現選項內容拷貝到指定空閒頁中。需注意的是,該函式可睡眠,可能導致缺頁情況。其中睡眠當然是針對當前程序而言的,若程序在copy_from_user或copy_to_user前被剝奪執行許可權,而又在此期間觸發了交換條件(參照:虛擬記憶體實現原理,交換通常發生在記憶體資源緊張時),則系統會將程序執行所需的相應資源從記憶體交換到硬碟中。之後當程序獲得執行許可權時,由於硬碟的資源只有在要使用的時候才載入到記憶體中(linux機制),因而即便是立即就載入資料到記憶體,程序也需阻塞睡眠等待。還有種情況就是,剛要載入資料時,程序又睡眠了,連資料載入操作都做不了。
/2/ 較新的核心版本均使用該函式,該函式與copy_mount_string函式不同之處在於:
int copy_mount_options(const void __user * data, unsigned long *where)
{
int i;
unsigned long page;
unsigned long size;
*where = 0;
if (!data)
return 0;
if (!(page = __get_free_page(GFP_KERNEL))) ----/2.1/
return -ENOMEM;
/* We only care that *some* data at the address the user
* gave us is valid. Just in case, we'll zero
* the remainder of the page.
*/
/* copy_from_user cannot cross TASK_SIZE ! */
size = TASK_SIZE - (unsigned long)data; ----/2.2/
if (size > PAGE_SIZE)
size = PAGE_SIZE;
i = size - exact_copy_from_user((void *)page, data, size); ----/2.3/
if (!i) {
free_page(page);
return -EFAULT;
}
if (i != PAGE_SIZE)
memset((char *)page + i, 0, PAGE_SIZE - i); ----/2.4/
*where = page;
return 0;
}
/2.1/ 函式最終呼叫__get_free_pages函式,返回一個32位的非高階頁記憶體地址
/2.2/ 拷貝使用者態資料不能超過TASK_SIZE(3G),超過3G已是屬於核心態資料區域,故使用TASK_SIZE減出需拷貝的資料大小,更安全。
/2.3/ 呼叫exact_copy_from_user函式,較之於copy_from_user(返回值成功:0 失敗:失敗位元組個數),最終能返回精確的copy資料大小。
/2.4/ 當拷貝數不足PAGE資料位元組大小時,將未使用的頁面資料區域設定為零
/3/ 該函式mount實現掛載檔案系統的主戰場,不妨進入該函式,看看都做了些什麼?
/*
* 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 *dir_name,
const char *type_page, unsigned long flags, void *data_page)
{
struct path path;
int retval = 0;
int mnt_flags = 0;
/* Discard magic */
if ((flags & MS_MGC_MSK) == MS_MGC_VAL) ----/3.1/
flags &= ~MS_MGC_MSK;
/* Basic sanity checks */
if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE)) ----/3.2/
return -EINVAL;
if (data_page)
((char *)data_page)[PAGE_SIZE - 1] = 0;
/* ... and get the mountpoint */
retval = kern_path(dir_name, LOOKUP_FOLLOW, &path); ----/3.3/
if (retval)
return retval;
retval = security_sb_mount(dev_name, &path, ----/3.4/
type_page, flags, data_page);
if (!retval && !may_mount())
retval = -EPERM;
if (retval)
goto dput_out;
/* Default to relatime unless overriden */
if (!(flags & MS_NOATIME))
mnt_flags |= MNT_RELATIME;
/* 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;
/* 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 &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN |
MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |
MS_STRICTATIME);
if (flags & MS_REMOUNT)
retval = do_remount(&path, flags & ~MS_REMOUNT, 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, flags, mnt_flags, ----/4.5/
dev_name, data_page);
dput_out:
path_put(&path);
return retval;
}
/3.1/ 如該函式註釋所訴:這些magic數使用於早些版本(2.4.0-test9以前),即無視MS_MGC_MSK與MS_MGC_VAL作用,通過從flags中獲取,取反後寫入flags,呵呵,意圖很明顯啊!。
/3.2/ 對於該函式memchr,就不展開了,它的作用是:在dir_name指向的記憶體區域中的前PAGE大小個位元組中查詢int=0的字元null,當第一次遇到該字元時停止查詢,成功返回指向字元的指標;否則返回NULL。其實就是做了一下引數的檢查,看指定資料內容是否存在。
/3.3/ 接下來,進入kern_path函式:
int kern_path(const char *name, unsigned int flags, struct path *path)
{
struct nameidata nd;
int res = do_path_lookup(AT_FDCWD, name, flags, &nd); ----/4.3.1/
if (!res)
*path = nd.path; ----/4.3.2/
return res;
}
/3.3.2/ 從kern_path和該行可知,函式只要實現將使用者態path路徑賦值到核心態路徑,以便後續再核心態的操作。當然除此之外,要做的事情就是稽核path的有效性了。如:對於某一塊裝置/dev/[sdx]而言,系統呼叫mount操作最終實現將一個可訪問的塊裝置(該裝置)安裝到一個可訪問的節點,即裝置安裝前就是要可訪問的,也就是說在掛載之前,/dev/sdx已經與核心建立過相關的聯絡(nameidata結構體),而如上示/1/就將對其進行稽核。
/3.3.1/ 該函式do_path_lookup –> filename_lookup,不妨來看看filename_lookup函式:
/* dfd為AT_FDCWD:指示操作應在當前目錄
* name為/dev/[sdx]
* flags為LOOKUP_FOLLOW
* nd為區域性變數且尚未初始化
*/
static int filename_lookup(int dfd, struct filename *name,
unsigned int flags, struct nameidata *nd)
{
int retval = path_lookupat(dfd, name->name, flags | LOOKUP_RCU, nd); ----/4.3.1/
if (unlikely(retval == -ECHILD)) //等價於if (retval == -ECHILD)
retval = path_lookupat(dfd, name->name, flags, nd);
if (unlikely(retval == -ESTALE))
retval = path_lookupat(dfd, name->name,
flags | LOOKUP_REVAL, nd);
if (likely(!retval))
audit_inode(name, nd->path.dentry, flags & LOOKUP_PARENT); ----/4.3.2/
return retval;
}
/3.3.1/ 路徑查詢函式,若/dev/[sdx]塊裝置存在,path_lookupat函式返回值retval=0,使nd有效(一系列操作將其初始化為正真的裝置並與/dev/[sdx]比較該裝置是否存在)。該函式返回0,do_path_lookup返回值ret為0,kern_path返回值為0
/3.3.2/ if (likely(!retval)) 等價於if (!retval),即進入分支對inode掛載節點進行稽核,使得最終kern_path獲取掛載點(nd的dentry域)。
/3.4/ 安全性檢查
最終返回到do_mount,而後根據引數flags的值來決定呼叫:do_remount do_loopback do_change_type do_move_mount do_new_mount其中的某一函式。下面以do_new_mount來簡單介紹一下:
/3.5/ 進入該函式:
/*
* 在使用者空間建立一個新的掛載點並且將其新增到名稱空間樹
*
* 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 flags,
int mnt_flags, const char *name, void *data)
{
struct file_system_type *type;
struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns;
struct vfsmount *mnt;
int err;
if (!fstype)
return -EINVAL;
/*get_fs_type --> __get_fs_type --> find_filesystem,同時呼叫try_module_get,遞增模組module結構體全域性計數,若是沒有找到則呼叫request_module來註冊新的檔案系統到file_systems鏈中,有關request_module如何實現模組註冊,這裡涉及到驅動層,就不展開了。*/
type = get_fs_type(fstype); ----/4.5.1/
if (!type)
return -ENODEV;
if (user_ns != &init_user_ns) {
if (!(type->fs_flags & FS_USERNS_MOUNT)) {
put_filesystem(type);
return -EPERM;
}
/* Only in special cases allow devices from mounts
* created outside the initial user namespace.
*/
if (!(type->fs_flags & FS_USERNS_DEV_MOUNT)) {
flags |= MS_NODEV;
mnt_flags |= MNT_NODEV | MNT_LOCK_NODEV;
}
}
/*vfs_kern_mount --> mount_fs --> mount :該函式為mount統一路口
* 當前格式化檔案系統為xfs,有 .mount = xfs_fs_mount (fs/xfs_super.c)
* 故type->mount --> xfs_fs_mount
*/
mnt = vfs_kern_mount(type, flags, name, data); ---->/4.5.2/
if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
!mnt->mnt_sb->s_subtype)
mnt = fs_set_subtype(mnt, fstype);
put_filesystem(type); ----/4.5.3/
if (IS_ERR(mnt))
return PTR_ERR(mnt);
err = do_add_mount(real_mount(mnt), path, mnt_flags); ----/4.5.4/
if (err)
mntput(mnt);
return err;
}
/3.5.1/ 該檢視核心是否註冊了引數fstype所指的檔案系統,get_fs_type會將引數fstype字串跟核心連結串列中所有已經註冊的檔案系統結構體file_system_type的name成員向比較,若已註冊則返回file_system_type結構體。
/3.5.2/ 該函式成功返回vfsmount結構體,核心中代表掛載檔案系統的vfsmountt結構體填充成功。
/3.5.3/ 該函式執行在vfs_kern_mount函式後,將檔案系統模組使用量減1。
/3.5.4/ 將新掛載的檔案系統(由vfsmount表示)新增到系統的名稱空間結構體的已掛載檔案系統連結串列中,名稱空間是指系統中以掛載檔案系統樹,每個程序的PCB中都有namespace成員來表示該程序的名稱空間,大多數的程序共享同一個名稱空間,所以如果在一個程序中將磁碟掛載到系統中,在另一個程序也是可以看到的,這就是由名稱空間來實現的。vfsmount新增到相應的namespace中的vfsmount連結串列成功後do_new_mount返回。
這樣從sys_mount到vfs_kern_mount到mount(有些版本為get_sb)再到具體的檔案系統層呼叫xfs_fs_mount進行處理,處理完層層返回,核心也就實現了指定檔案系統的磁碟掛載到指定目錄。總體感覺在這個過程中核心主要實現vfsmount結構體的填充,而這個結構體中最重要的是super_block的填充,然後將vfsmount新增到相應程序PCB的namespace成員所指向的namespace結構體中,大部分程序都指向這個namespace,所以掛載後對大部分程序都是可見的。
UMOUNT
即解除安裝格式化xfs檔案系統的掛載點
- umount /dev/sdc /mnt
- 原始碼分析
系統呼叫sys_umount,而該函式使用SYSCALL_DEFINE2定義實現。首先不妨看看整個系統呼叫,umount實現的呼叫流程:
- 原始碼分析
sys_umount() --> SYSCALL_DEFINE2()
-->do_umount --> security_sb_umount --> sb_umount --> mq_put_mnt --> kern_unmount
SYSCALL_DEFINE2()
-->do_umount
--> security_sb_umount //安全檢查
--> umount_begin (統一介面)
--> fuse_umount_begin(fs/fuse/inode.c) --> fuse_abort_conn --> ... ...
--> mq_put_mnt
--> kern_unmount
--> mntput
--> mntput_no_expire() // -->cleanup_mnt() (有些linux版本有該函式,做多一次封裝後繼續往下呼叫)
--> deactivate_super()
--> deactivate_locked_super()
--> kill_sb() //統一入口 若為xfs檔案系統 :static struct file_system_type xfs_fs_type
則 .kill_sb = kill_block_super (在fs/xfs/xfs_super.c中申明,注:在此有宣告,但定義在fs/super.c中)
--> kill_block_super()
--> generic_shutdown_super()
--> put_super() 同理上述 對映至 ==> xfs_fs_put_super
--> xfs_fs_put_super()
--> xfs_unmountfs()
--> xfs_ail_push_all_sync()
--> schedule() //程序排程的主體程式
一般來說,獲取資源會比釋放資源的處理過程要複雜一些的,那麼可以說umount處理流程會簡易一些嗎?進到SYSCALL_DEFINE2函式中去看看吧!
SYSCALL_DEFINE2(umount, char __user *, name, int, flags)
{
struct path path;
struct mount *mnt;
int retval;
int lookup_flags = 0;
if (flags & ~(MNT_FORCE | MNT_DETACH | MNT_EXPIRE | UMOUNT_NOFOLLOW))
return -EINVAL;
if (!may_mount())
return -EPERM;
if (!(flags & UMOUNT_NOFOLLOW))
lookup_flags |= LOOKUP_FOLLOW;
retval = user_path_mountpoint_at(AT_FDCWD, name, lookup_flags, &path); ----/1/
if (retval)
goto out;
mnt = real_mount(path.mnt); ----/2/
retval = -EINVAL;
if (path.dentry != path.mnt->mnt_root)
goto dput_and_out;
if (!check_mnt(mnt))
goto dput_and_out;
if (mnt->mnt.mnt_flags & MNT_LOCKED)
goto dput_and_out;
retval = -EPERM;
//capable函式做使用者許可權檢查
if (flags & MNT_FORCE && !capable(CAP_SYS_ADMIN))
goto dput_and_out;
retval = do_umount(mnt, flags); ----/3/
dput_and_out:
/* we mustn't call path_put() as that would clear mnt_expiry_mark */
dput(path.dentry);
mntput_no_expire(mnt);
out:
return retval;
}
/1/ 查詢掛載點
/2/ real_mount(path.mnt) –> container_of(mnt, struct mount, mnt)有關container_of巨集,該巨集可利用結構體中的某一成員變數的首地址計算出整個結構變數的地址。在這裡,我們想給mount結構的指標變數賦值,即可通過使用path結構體中定義的指標變數path.mnt作為real_mount引數。上述第一個mnt為mount結構體中的一個vfsmount型別成員變數的首地址,而第二個mnt其實是mount結構體成員變數(型別當然也是為vfsmount)。
/3/ 該函式時umount的主戰場:
static int do_umount(struct mount *mnt, int flags)
{
struct super_block *sb = mnt->mnt.mnt_sb;
int retval;
retval = security_sb_umount(&mnt->mnt, flags); ----/1.1/
if (retval)
return retval;
/*
* Allow userspace to request a mountpoint be expired rather than
* unmounting unconditionally. Unmount only happens if:
* (1) the mark is already set (the mark is cleared by mntput())
* (2) the usage count == 1 [parent vfsmount] + 1 [sys_umount]
*/
if (flags & MNT_EXPIRE) { ----/1.2/
if (&mnt->mnt == current->fs->root.mnt ||
flags & (MNT_FORCE | MNT_DETACH))
return -EINVAL; ----/1.3/
/*
* probably don't strictly need the lock here if we examined
* all race cases, but it's a slowpath.
*/
//說那麼多,其實就是說最好是加把鎖唄!
lock_mount_hash();
if (mnt_get_count(mnt) != 2) {
unlock_mount_hash();
return -EBUSY; ----/1.4/
}
unlock_mount_hash();
if (!xchg(&mnt->mnt_expiry_mark, 1))
return -EAGAIN; ----/1.5/
}
/*
* If we may have to abort operations to get out of this
* mount, and they will themselves hold resources we must
* allow the fs to do things. In the Unix tradition of
* 'Gee thats tricky lets do it in userspace' the umount_begin
* might fail to complete on the first run through as other tasks
* must return, and the like. Thats for the mount program to worry
* about for the moment.
*/
if (flags & MNT_FORCE && sb->s_op->umount_begin) {
sb->s_op->umount_begin(sb); ----/1.6/
}
/*
* No sense to grab the lock for this test, but test itself looks
* somewhat bogus. Suggestions for better replacement?
* Ho-hum... In principle, we might treat that as umount + switch
* to rootfs. GC would eventually take care of the old vfsmount.
* Actually it makes sense, especially if rootfs would contain a
* /reboot - static binary that would close all descriptors and
* call reboot(9). Then init(8) could umount root and exec /reboot.
*/
if (&mnt->mnt == current->fs->root.mnt && !(flags & MNT_DETACH)) {
/*
* Special case for "unmounting" root ...
* we just try to remount it readonly.
*/
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
down_write(&sb->s_umount);
if (!(sb->s_flags & MS_RDONLY))
retval = do_remount_sb(sb, MS_RDONLY, NULL, 0);
up_write(&sb->s_umount);
return retval;
}
namespace_lock();
lock_mount_hash();
event++;
if (flags & MNT_DETACH) {
if (!list_empty(&mnt->mnt_list))
umount_tree(mnt, 2);
retval = 0;
} else {
shrink_submounts(mnt);
retval = -EBUSY;
if (!propagate_mount_busy(mnt, 2)) {
if (!list_empty(&mnt->mnt_list))
umount_tree(mnt, 1);
retval = 0;
}
}
unlock_mount_hash();
namespace_unlock();
return retval;
}
/1.1/ 函式do_umount –> security_sb_umount –> sb_umount –> selinux_umount 顧名思義,是在安全的模式下做一些操作,那該函式是用來實現在安全模式下完成umoun解除安裝的任務嗎?分析可知,其實這個函式只是通過獲取一些資訊(參照:superblock_has_perm函式)進而來評估,在當前環境能否順利的完成umount操作。若不能則返回retval>0的數,umount任務取消。反之,繼續。
/1.2/ 進入該分支說明MNT_EXPIRE置位為1了,即掛載時效到期(掛載時間到期感覺不好理解,把它當做普通標誌位理解即可)
/1.3/ 中途返回說明umount失敗,這裡表示:若當前掛載點知程序的根目錄或某程序使用強制手段解除安裝節點,再或是MNT_DETACH置位(該引數置位的話,將不會立即執行umount操作,會等掛載點退出忙碌狀態時再操作),均立即返回操作失敗。
/1.4/ 檢查vfsmount的引用計數是否為2 若不為2則umount失敗立即返回,計數2代表的是當前vfsmount結構的父vfsmount結構體,以及sys_mmount()對本物件的引用,而簡單的可以理解就是:在檔案系統解除安裝的時候不能再有額外的引用,想想也是!
/1.5/ 設定vfsmount物件mnt_expiry_mark欄位為1
/1.6/ umount_begin該函式正式開始處理umount事務。umount_begin –> fuse_abort_conn 該函式用於終結所有與掛載點的連結如:IO連結等
void fuse_abort_conn(struct fuse_conn *fc)
{
spin_lock(&fc->lock);
if (fc->connected) {
fc->connected = 0;
fc->blocked = 0;
fc->initialized = 1;
end_io_requests(fc);
end_queued_requests(fc);
end_polls(fc);
wake_up_all(&fc->waitq);
wake_up_all(&fc->blocked_waitq);
kill_fasync(&fc->fasync, SIGIO, POLL_IN);
}
spin_unlock(&fc->lock);
}
總結
到此告一段落,其中有很多地方還有待學習,