引言
情景:
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;
... ...1
2
3
4
5
6
7
8
跟踪force_overwrite标志:
memset(&ft, 0, sizeof(ft)); ----/1/
get_topology(&xi, &ft, force_overwrite); ----/2/1
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版本中),因而通常不会置位。
};1
2
3
4
5
6
7
8
9
10
11
/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);
}
}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函数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);
}
}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// 不妨进入该函数:
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 */
}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相关的东西,也就是有关矩阵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/
、、、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这个结构体类型,我们定义了一个用户层面上的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);1
2
3
函数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__))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
在内核中,结构体以及宏函数的使用可谓出神入化。有关这里的宏函数定义如何实现的,暂不展开,不好表达!
其实如下示: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)1
2
3
/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
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/ 该函数将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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/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;
}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/ 函数最终调用__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;
}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/ 如该函数注释所诉:这些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;
}1
2
3
4
5
6
7
8
/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;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/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;
}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/ 该查看内核是否注册了参数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() //进程调度的主体程序1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
一般来说,获取资源会比释放资源的处理过程要复杂一些的,那么可以说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
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)有关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
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/ 函数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);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
总结
到此告一段落,其中有很多地方还有待学习,