引言
情景:
<源碼> linux:3.14.56 xfsprogs:3.2.0
<命令> mkfs.xfs -f /dev/[sdx] ; mount /dev/[sdx]; umount /dev/[sdx]
如情景所示,,來分析分析,mkfs.xfs mount 以及umount操作都做了些什么事情,。下述內(nèi)容均為本人隨意跟蹤,,看到那說到哪!僅作參考,。
MKFS.XFS
本例格式化xfs文件系統(tǒng),,有關(guān)xfs文件系統(tǒng)就不介紹了。倒是首先要分析mkfs.xfs命令,先得獲取相應(yīng)的源碼,,源碼獲取可到github搜索xfsprogs,,之前找了一個util-linux的包,編譯后才發(fā)覺不支持mkfs.xfs,,其它的文件系統(tǒng)倒還支持一些的,,可見xfs還是比較“特殊”的,值得了解一下,。好吧,,總之下載即可。
- mkfs.xfs -f /dev/sdc
- 源碼分析
1,、進入 xfs_mkfs.c文件主函數(shù)main。對于一個提供給用戶使用的終端命令(或說控制接口),,它做的事情無非就是在接受用戶指令后,,對相應(yīng)指令參數(shù)進行解析,而后下發(fā)給后端進/線程做進一步處理,,當然最終會返回信息給當前主進程,,處理完事務(wù)后(同步的話),進程也就退出了,。
... ...
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結(jié)構(gòu)體,,那么不妨先來看看這個結(jié)構(gòu)體的內(nèi)容有哪些:
/*
* 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; //扇區(qū)對齊標志:該標志位置位1時,要求處理的塊大小與扇區(qū)大小相同(老linux版本中),,因而通常不會置位,。
};
/2/ 若ENABLE_BLKID有定義,即安裝有blkid源碼,,即可執(zhí)行blkid命令,,獲取設(shè)備的拓撲結(jié)構(gòu)如下:
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
/2.1/ libxfs_init_t函數(shù)memset初始化為0,其成員變量disfile可知其初始化為0,,又當前mkfs.xfs命令未加-d選項,,則進入該分支處理。下面分支同理,。
/2.2/ 處理獲取設(shè)備拓撲結(jié)構(gòu),,這里不好追蹤就不展開了,關(guān)鍵函數(shù)定義實現(xiàn)在blkid命令源碼中如:當前函數(shù)中的blkid_new_probe_from_filename,。
/2// 若ENABLE_BLKID無定義,,獲取設(shè)備的拓撲結(jié)構(gòu):
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
/2.1// 不妨進入該函數(shù):
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;
//可添加其它類型的設(shè)備驅(qū)動,信息獲取如上格式
/* ... add new device drivers here */
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
/2.1.1// 分析它即可,,其它的同理,。顧名思義,即獲取Multiple Devices相關(guān)的東西,,也就是有關(guān)矩陣raid條帶化的信息,,因為mkfs.xfs命令未加-d選項,,故而獲取的是創(chuàng)建md設(shè)備時指定的條帶信息。進入該函數(shù)可知,,主要是對其參數(shù)進行賦值,。
返回main主函數(shù),調(diào)用libxfs_init函數(shù)進行xfs文件系統(tǒng)的初始化,,主要關(guān)注一下函數(shù):
1,、radix_tree_init
基樹初始化,主要用于內(nèi)存管理,,該樹為典型的字典類型結(jié)構(gòu)(有待研究)
2,、libxfs_device_open
打開一個設(shè)備并且獲取其設(shè)備號,即便不是真的設(shè)備亦返回一個偽設(shè)備號
3,、cache_init
初始化緩存,,返回cache結(jié)構(gòu)體
4、manage_zones
manage_zones函數(shù)實現(xiàn)釋放xfs分區(qū)或生成xfs分區(qū),。而生成啟動xfs目錄結(jié)構(gòu)調(diào)用的是xfs_dir_startup函數(shù),,該函數(shù)定義在linux源碼lib庫中(可使得生成目錄下”.”以及“..”文件)
回到main函數(shù),進入判斷force_overwrite標志位的分支結(jié)構(gòu),,調(diào)用zero_old_xfs_structures函數(shù),,函數(shù)將置0初始化各個次要AG的sb超級塊結(jié)構(gòu),通常128M至4T左右容量設(shè)的備,,4個AG用于管理磁盤空間,。當然若是raid陣列的話就另當別論了,相同容量需創(chuàng)建更多的AG,。AG超級塊結(jié)構(gòu)參考:http://www./archives/2012/01/648
回到main函數(shù),,調(diào)用libxfs_mount —-/3/,該函數(shù)主要就是對結(jié)構(gòu)體進行填充,,不過卻是非常重要的,,不妨來看看:
/*
* 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/
,、、,、
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
/3.1/ 首先要理解的是xfs_mount_t這個結(jié)構(gòu)體類型,,我們定義了一個用戶層面上的xfs_mount結(jié)構(gòu)體,同時方便了使用以XFS_*為前綴的宏,。即該函數(shù)填充xfs_umount結(jié)構(gòu)體,,并將其返回,以供用戶進程訪問,,也就是mkfs.xfs了,。(注:即便設(shè)備上的文件系統(tǒng)格式化了,在未mount掛載前也是不能直接訪問的),。而在進程使用完后,,即在main函數(shù)最后,會調(diào)用libxfs_umount釋放占用的資源(可理解xfs_mount為中間變量),。
/3.2/ 而該函數(shù)使用xfs_sb超級塊的信息,建立了各類通用的mount掛載域
/3.3/ 顧名思義,,
/3.4/ 填充好xfs_mount信息后,,將其讀取到相應(yīng)的緩存區(qū)xfs_buf中
/3.5/ 而libxfs_putbuf –> cache_node_put,該函數(shù)將xfs_buf類型強制轉(zhuǎn)化成cache_node類型,,并添加至cache下發(fā)鏈表,。最終使得以完成設(shè)備文件系統(tǒng)的格式化。
最后回到main函數(shù),,調(diào)用libxfs_getsb,,同理該函數(shù)同樣讀取xfs_buf緩存中的內(nèi)容,若讀取出來的超級塊信息與寫入的相同,,則標識文件系統(tǒng)格式化成功,。到此,進程mkfs.xfs的整個執(zhí)行流程也算是結(jié)束了,,當然中間有很多東西都無視了(呵呵)
MOUNT
即將格式化xfs的磁盤掛載到相應(yīng)目錄
- mount /dev/[sdx] /mnt
- 源碼分析
首先應(yīng)用層shell實現(xiàn)系統(tǒng)調(diào)用sys_mount,,而在本linux源碼版本:調(diào)用SYSCALL_DEFINE5,申明定義在include/linux/syscall.h中,。至于為什么使用這種方式定義sys_mount,,實質(zhì)上是為在64位的內(nèi)核上實現(xiàn)調(diào)用32位的系統(tǒng)調(diào)用(有待研究)。
asmlinkage long sys_mount(char __user *dev_name, char __user *dir_name, ----/0/
char __user *type, unsigned long flags,
void __user *data);
函數(shù)sys_mount由SYSCALL_DEFINE5(5:代表參數(shù)個數(shù))定義:
#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__))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
在內(nèi)核中,,結(jié)構(gòu)體以及宏函數(shù)的使用可謂出神入化,。有關(guān)這里的宏函數(shù)定義如何實現(xiàn)的,暫不展開,不好表達,!
其實如下示:compat_sys_mount函數(shù)定義與上述SYSCALL_DEFINE5非常相似,,而與sys_mount也似乎存在著某種聯(lián)系(有待研究):
/include/uapi/asm-generic/unistd.h:
#define __NR_mount 40
__SC_COMP(__NR_mount, sys_mount, compat_sys_mount)
/0/ 上述的asmlinkage標識著調(diào)用函數(shù)參數(shù)的傳遞方式。若加該標識標識使用堆棧,,缺省時使用的是寄存器傳遞函數(shù)參數(shù),。如:匯編中調(diào)用c函數(shù),且使用堆棧傳遞函數(shù),,則在定c義函數(shù)時,,需在函數(shù)前添加宏asmlinkage。需要注意的是內(nèi)核只在系統(tǒng)調(diào)用時使用該宏,,原因:一是,,普通內(nèi)核函數(shù)調(diào)用寄存器傳參比堆棧傳參快很多,想想就是無需解釋,。二是:對于系統(tǒng)調(diào)用時使用堆棧傳參必然有其的合理性,。有關(guān)第二點說到的合理性,不妨稍作解釋,,就不展開了:
1,,涉及到用戶態(tài)與內(nèi)核態(tài)的轉(zhuǎn)換,轉(zhuǎn)換過程實現(xiàn)需系統(tǒng)調(diào)用,。也就是說系統(tǒng)調(diào)用函數(shù)既需訪問用戶態(tài)資源,,與此同時訪問內(nèi)核資源。而內(nèi)核與用戶態(tài)的資源組織方式(可以這么說吧?。┛墒遣煌?,如:地址空間的映射方式就不同。
2,、有人說堆棧的信息來源不就是來自寄存器的copy嗎,,我想其實兩者都能實現(xiàn)參數(shù)的傳遞,其本質(zhì)不都是要實現(xiàn)壓棧,,現(xiàn)場保護嗎,。只不過用戶態(tài)與內(nèi)核態(tài)的轉(zhuǎn)換,或說既要實現(xiàn)兩者之間的通訊,,又要實現(xiàn)兩者的隔離,。而畢竟內(nèi)核態(tài)的東西使用用戶態(tài)堆棧訪問方式相當于做了一些隔離。在此可能真的犧牲了一些傳參效率吧?。ê呛牵?。
下面不妨進入SYSCALL_DEFINE5函數(shù)定義,看看它做了些什么:
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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
/1/ 該函數(shù)將mount掛載的選項字符串內(nèi)核的一個空閑頁中,,如下示:
int copy_mount_string(const void __user *data, char **where)
{
char *tmp;
if (!data) {
*where = NULL;
return 0;
}
//成功返回指向內(nèi)核PAGE大小數(shù)據(jù)頁指針
tmp = strndup_user(data, PAGE_SIZE); ----/1.1/
if (IS_ERR(tmp))
return PTR_ERR(tmp);
*where = tmp;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
/1.1/ 該函數(shù)最終調(diào)用copy_from_user函數(shù),,實現(xiàn)選項內(nèi)容拷貝到指定空閑頁中,。需注意的是,該函數(shù)可睡眠,,可能導(dǎo)致缺頁情況,。其中睡眠當然是針對當前進程而言的,若進程在copy_from_user或copy_to_user前被剝奪執(zhí)行權(quán)限,,而又在此期間觸發(fā)了交換條件(參照:虛擬內(nèi)存實現(xiàn)原理,,交換通常發(fā)生在內(nèi)存資源緊張時),則系統(tǒng)會將進程執(zhí)行所需的相應(yīng)資源從內(nèi)存交換到硬盤中,。之后當進程獲得執(zhí)行權(quán)限時,,由于硬盤的資源只有在要使用的時候才加載到內(nèi)存中(linux機制),因而即便是立即就加載數(shù)據(jù)到內(nèi)存,,進程也需阻塞睡眠等待,。還有種情況就是,剛要加載數(shù)據(jù)時,,進程又睡眠了,,連數(shù)據(jù)加載操作都做不了。
/2/ 較新的內(nèi)核版本均使用該函數(shù),,該函數(shù)與copy_mount_string函數(shù)不同之處在于:
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
/2.1/ 函數(shù)最終調(diào)用__get_free_pages函數(shù),,返回一個32位的非高端頁內(nèi)存地址
/2.2/ 拷貝用戶態(tài)數(shù)據(jù)不能超過TASK_SIZE(3G),超過3G已是屬于內(nèi)核態(tài)數(shù)據(jù)區(qū)域,,故使用TASK_SIZE減出需拷貝的數(shù)據(jù)大小,,更安全。
/2.3/ 調(diào)用exact_copy_from_user函數(shù),,較之于copy_from_user(返回值成功:0 失敗:失敗字節(jié)個數(shù)),,最終能返回精確的copy數(shù)據(jù)大小,。
/2.4/ 當拷貝數(shù)不足PAGE數(shù)據(jù)字節(jié)大小時,將未使用的頁面數(shù)據(jù)區(qū)域設(shè)置為零
/3/ 該函數(shù)mount實現(xiàn)掛載文件系統(tǒng)的主戰(zhàn)場,,不妨進入該函數(shù),,看看都做了些什么?
/*
* 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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
/3.1/ 如該函數(shù)注釋所訴:這些magic數(shù)使用于早些版本(2.4.0-test9以前),,即無視MS_MGC_MSK與MS_MGC_VAL作用,,通過從flags中獲取,取反后寫入flags,,呵呵,,意圖很明顯啊,!,。
/3.2/ 對于該函數(shù)memchr,,就不展開了,它的作用是:在dir_name指向的內(nèi)存區(qū)域中的前PAGE大小個字節(jié)中查找int=0的字符null,,當?shù)谝淮斡龅皆撟址麜r停止查找,,成功返回指向字符的指針;否則返回NULL,。其實就是做了一下參數(shù)的檢查,,看指定數(shù)據(jù)內(nèi)容是否存在。
/3.3/ 接下來,,進入kern_path函數(shù):
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和該行可知,,函數(shù)只要實現(xiàn)將用戶態(tài)path路徑賦值到內(nèi)核態(tài)路徑,以便后續(xù)再內(nèi)核態(tài)的操作,。當然除此之外,,要做的事情就是審核path的有效性了。如:對于某一塊設(shè)備/dev/[sdx]而言,,系統(tǒng)調(diào)用mount操作最終實現(xiàn)將一個可訪問的塊設(shè)備(該設(shè)備)安裝到一個可訪問的節(jié)點,,即設(shè)備安裝前就是要可訪問的,也就是說在掛載之前,,/dev/sdx已經(jīng)與內(nèi)核建立過相關(guān)的聯(lián)系(nameidata結(jié)構(gòu)體),,而如上示/1/就將對其進行審核。
/3.3.1/ 該函數(shù)do_path_lookup –> filename_lookup,,不妨來看看filename_lookup函數(shù):
/* dfd為AT_FDCWD:指示操作應(yīng)在當前目錄
* 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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
/3.3.1/ 路徑查找函數(shù),,若/dev/[sdx]塊設(shè)備存在,path_lookupat函數(shù)返回值retval=0,,使nd有效(一系列操作將其初始化為正真的設(shè)備并與/dev/[sdx]比較該設(shè)備是否存在),。該函數(shù)返回0,do_path_lookup返回值ret為0,,kern_path返回值為0
/3.3.2/ if (likely(!retval)) 等價于if (!retval),,即進入分支對inode掛載節(jié)點進行審核,使得最終kern_path獲取掛載點(nd的dentry域),。
/3.4/ 安全性檢查
最終返回到do_mount,,而后根據(jù)參數(shù)flags的值來決定調(diào)用:do_remount do_loopback do_change_type do_move_mount do_new_mount其中的某一函數(shù)。下面以do_new_mount來簡單介紹一下:
/3.5/ 進入該函數(shù):
/*
* 在用戶空間創(chuàng)建一個新的掛載點并且將其添加到命名空間樹
*
* 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,,同時調(diào)用try_module_get,,遞增模塊module結(jié)構(gòu)體全局計數(shù),若是沒有找到則調(diào)用request_module來注冊新的文件系統(tǒng)到file_systems鏈中,,有關(guān)request_module如何實現(xiàn)模塊注冊,,這里涉及到驅(qū)動層,就不展開了,。*/
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 :該函數(shù)為mount統(tǒng)一路口
* 當前格式化文件系統(tǒng)為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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
/3.5.1/ 該查看內(nèi)核是否注冊了參數(shù)fstype所指的文件系統(tǒng),,get_fs_type會將參數(shù)fstype字符串跟內(nèi)核鏈表中所有已經(jīng)注冊的文件系統(tǒng)結(jié)構(gòu)體file_system_type的name成員向比較,若已注冊則返回file_system_type結(jié)構(gòu)體,。
/3.5.2/ 該函數(shù)成功返回vfsmount結(jié)構(gòu)體,,內(nèi)核中代表掛載文件系統(tǒng)的vfsmountt結(jié)構(gòu)體填充成功。
/3.5.3/ 該函數(shù)執(zhí)行在vfs_kern_mount函數(shù)后,,將文件系統(tǒng)模塊使用量減1,。
/3.5.4/ 將新掛載的文件系統(tǒng)(由vfsmount表示)添加到系統(tǒng)的命名空間結(jié)構(gòu)體的已掛載文件系統(tǒng)鏈表中,命名空間是指系統(tǒng)中以掛載文件系統(tǒng)樹,,每個進程的PCB中都有namespace成員來表示該進程的命名空間,,大多數(shù)的進程共享同一個命名空間,所以如果在一個進程中將磁盤掛載到系統(tǒng)中,,在另一個進程也是可以看到的,,這就是由命名空間來實現(xiàn)的。vfsmount添加到相應(yīng)的namespace中的vfsmount鏈表成功后do_new_mount返回,。
這樣從sys_mount到vfs_kern_mount到mount(有些版本為get_sb)再到具體的文件系統(tǒng)層調(diào)用xfs_fs_mount進行處理,,處理完層層返回,內(nèi)核也就實現(xiàn)了指定文件系統(tǒng)的磁盤掛載到指定目錄,??傮w感覺在這個過程中內(nèi)核主要實現(xiàn)vfsmount結(jié)構(gòu)體的填充,而這個結(jié)構(gòu)體中最重要的是super_block的填充,,然后將vfsmount添加到相應(yīng)進程PCB的namespace成員所指向的namespace結(jié)構(gòu)體中,,大部分進程都指向這個namespace,所以掛載后對大部分進程都是可見的,。
UMOUNT
即卸載格式化xfs文件系統(tǒng)的掛載點
- umount /dev/sdc /mnt
- 源碼分析
系統(tǒng)調(diào)用sys_umount,,而該函數(shù)使用SYSCALL_DEFINE2定義實現(xiàn)。首先不妨看看整個系統(tǒng)調(diào)用,,umount實現(xiàn)的調(diào)用流程:
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 (統(tǒng)一接口)
--> fuse_umount_begin(fs/fuse/inode.c) --> fuse_abort_conn --> ... ...
--> mq_put_mnt
--> kern_unmount
--> mntput
--> mntput_no_expire() // -->cleanup_mnt() (有些linux版本有該函數(shù),,做多一次封裝后繼續(xù)往下調(diào)用)
--> deactivate_super()
--> deactivate_locked_super()
--> kill_sb() //統(tǒng)一入口 若為xfs文件系統(tǒng) :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() //進程調(diào)度的主體程序
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
一般來說,獲取資源會比釋放資源的處理過程要復(fù)雜一些的,,那么可以說umount處理流程會簡易一些嗎,?進到SYSCALL_DEFINE2函數(shù)中去看看吧!
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函數(shù)做用戶權(quán)限檢查
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
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
/1/ 查找掛載點
/2/ real_mount(path.mnt) –> container_of(mnt, struct mount, mnt)有關(guān)container_of宏,,該宏可利用結(jié)構(gòu)體中的某一成員變量的首地址計算出整個結(jié)構(gòu)變量的地址,。在這里,我們想給mount結(jié)構(gòu)的指針變量賦值,,即可通過使用path結(jié)構(gòu)體中定義的指針變量path.mnt作為real_mount參數(shù),。上述第一個mnt為mount結(jié)構(gòu)體中的一個vfsmount類型成員變量的首地址,,而第二個mnt其實是mount結(jié)構(gòu)體成員變量(類型當然也是為vfsmount)。
/3/ 該函數(shù)時umount的主戰(zhàn)場:
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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
/1.1/ 函數(shù)do_umount –> security_sb_umount –> sb_umount –> selinux_umount 顧名思義,是在安全的模式下做一些操作,,那該函數(shù)是用來實現(xiàn)在安全模式下完成umoun卸載的任務(wù)嗎,?分析可知,其實這個函數(shù)只是通過獲取一些信息(參照:superblock_has_perm函數(shù))進而來評估,,在當前環(huán)境能否順利的完成umount操作,。若不能則返回retval>0的數(shù),umount任務(wù)取消,。反之,,繼續(xù)。
/1.2/ 進入該分支說明MNT_EXPIRE置位為1了,,即掛載時效到期(掛載時間到期感覺不好理解,,把它當做普通標志位理解即可)
/1.3/ 中途返回說明umount失敗,這里表示:若當前掛載點知進程的根目錄或某進程使用強制手段卸載節(jié)點,,再或是MNT_DETACH置位(該參數(shù)置位的話,,將不會立即執(zhí)行umount操作,會等掛載點退出忙碌狀態(tài)時再操作),,均立即返回操作失敗,。
/1.4/ 檢查vfsmount的引用計數(shù)是否為2 若不為2則umount失敗立即返回,計數(shù)2代表的是當前vfsmount結(jié)構(gòu)的父vfsmount結(jié)構(gòu)體,,以及sys_mmount()對本對象的引用,,而簡單的可以理解就是:在文件系統(tǒng)卸載的時候不能再有額外的引用,想想也是,!
/1.5/ 設(shè)置vfsmount對象mnt_expiry_mark字段為1
/1.6/ umount_begin該函數(shù)正式開始處理umount事務(wù),。umount_begin –> fuse_abort_conn 該函數(shù)用于終結(jié)所有與掛載點的鏈接如: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);
}
總結(jié)
到此告一段落,其中有很多地方還有待學(xué)習(xí),,
|