linux libfcmain.so,xfs文件系統:格式化以及掛載

本文详细解析了mkfs.xfs用于创建XFS文件系统的过程,包括源码分析和参数处理,以及mount命令将格式化后的磁盘挂载到目录的操作。同时介绍了umount卸载挂载点的流程,涉及内核安全检查和挂载管理机制。

引言

情景:

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);

}

總結

到此告一段落,其中有很多地方還有待學習,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值