您的位置:首页 > 运维架构 > Linux

linux2.6.36文件系统分析---sys_mount()

2011-12-30 08:56 393 查看

sys_mount()代码分析

在linux上,任何磁盘或磁盘的分区在被使用之前必须首先被格式化成某个特定的文件系统(如ext2,ext3等),然后挂载到某个系统的目录树上,只有这样,该磁盘或分区才能被用户看到,使用。挂载一个文件系统的常用格式如下:
mount
-t ext3 -o option /device/hda /home/mountdir

其中-t选项后面的参数代表挂载的文件系统的类型;-o后面的参数是一些可选的选项,具体内容可参考google;/device/hda代表需要挂载的的磁盘或者分区的名称;/home/mountdir代表需要将文件挂载的目录。下面我们就对linux内核如何实现mount进行具体的分析,参考linux内核版本2.6.36。
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------
sys_mount的代码位于fs/namespace.c文件中,具体的形式为:(关于SYSCALL_DEFINEN的介绍可参考google)
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;
char *kernel_dir;
char *kernel_dev;
unsigned long data_page;

//将参数type=挂载文件系统的类型,由用户态拷贝至内核
ret = copy_mount_string(type, &kernel_type);
if (ret < 0)
goto out_type;

//将参数dir_name=挂载点名字,由用户态拷贝至内核
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;

//将参数中的选项参数拷贝至内核,该函数在实现的时候会为这些可选参数分配一个page大小的空间,
//首先在该page起始的地方填充用户态传入的参数,page剩余的空间填充'\0'
ret = copy_mount_options(data, &data_page);
if (ret < 0)
goto out_data;

//接下来调用核心处理函数,do_mount完成主要的挂载工作
ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags,(void *) data_page);

free_page(data_page);

20000
out_data:

kfree(kernel_dev);

out_dev:

putname(kernel_dir);

out_dir:

kfree(kernel_type);

out_type:

return ret;

}
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------
do_mount(kernel_dev,
kernel_dir, kernel_type, flags,(void *) data_page)

long do_mount(char *dev_name, char *dir_name, 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)
flags
&= ~MS_MGC_MSK;

//简单的参数判断,没有通过判断则会返回-EINVAL表示传入参数出错
if (!dir_name || !*dir_name || !memchr(dir_name,
0, PAGE_SIZE))
return
-EINVAL;

if (data_page)
((char
*)data_page)[PAGE_SIZE - 1] = 0;

//获取挂载点信息,具体说是挂载点的dentry信息,dir_name代表挂载点名字;
//挂载点的dentry信息保存在path中;
//LOOKUP_FOLLOW:如果最后一个分量是符号链接,则解释它;
retval = kern_path(dir_name, LOOKUP_FOLLOW, &path);
if (retval)
return retval;

//跟安全机制相关,暂时不去理会
retval = security_sb_mount(dev_name, &path,type_page,
flags, data_page);
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;

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
//主要关注该函数,上面的几种情况暂时不去关注;
//path代表挂载点的信息,在上面的kern_path()获得
//type_page:
//flags:
//mnt_flgas:
//dev_name:
//data_page:
retval = do_new_mount(&path, type_page, flags, mnt_flags,dev_name,
data_page);

dput_out:

path_put(&path);
return retval;

}
注:挂载的工作主要在do_mount()函数中完成,该函数又主要被分成两个步骤:
1.获取挂载点dir_name的dentry和vfsmount结构,在主要调用了函数kern_path();
2.根据标记位,选择合适的挂载方法,我们重点关注的是do_new_mount()函数;
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------
kern_path(dir_name,
LOOKUP_FOLLOW, &path)

int kern_path(const
char *name, unsigned int flags, struct path *path)

{

struct nameidata nd;
//调用函数do_path_lookup()来查找name对应的dentry结构
//AT_FDCWD:表示从当前目录开始查找;
//name:需要查找的挂载点的名字;
//flags:调用者传入的为LOOKUP_FOLLOW,表示如果路径名中有分量是符号链接,则解释它;
//nd:用来保存查找结果,其结构可参考blog中的“数据结构”部分;
//res==0表示函数成功执行,否则,函数执行失败;
int
res = do_path_lookup(AT_FDCWD, name, flags, &nd);
if (!res)
*path
= nd.path;
return res;

}

简单翻阅代码可以知道,该函数只是简单的封装,引入了一个结构体struct nameidata,对该结构体的分析可参考blog中的“数据结构部分”。进行简单的封装后,调用了更底层的do_path_lookup(),其参数的意义如代码中的注释,函数的返回结果保存在nd中。do_path_lookup()的详细分析见下文。
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------
do_path_lookup(AT_FDCWD,
name, flags, &nd)

static
int do_path_lookup(int dfd, const char *name,unsigned int flags, struct nameidata *nd)

{

//初始化查找环境,如根据路径名name决定是从根目录还是当前目录查找;
//dfd=AT_FDCWD,如果路径名不是以'/'开始,那么就从当前目录开始查找;
//name:查找的文件/目录的路径名;
//flags:查找标志,由kern_path()传入,为LOOKUP_FOLLOW;
//nd;struct
nameidata,设置查找初始化环境,保存在该结构中;
int
retval = path_init(dfd, name, flags, nd);
if
(!retval)
//进入真正地查找过程;
//name表示查找的文件/目录的路径名;
//nd:在path_init()中设置的查找环境,如从根目录或当前目录开始查找等,同时保存查找结果;
retval
= path_walk(name, nd);
if
(unlikely(!retval && !audit_dummy_context() && nd->path.dentry &&nd->path.dentry->d_inode))
audit_inode(name,
nd->path.dentry);
if
(nd->root.mnt) {
path_put(&nd->root);
nd->root.mnt
= NULL;
}
return
retval;

}

do_path_lookup()函数中完成主要的查找工作,该函数主要又分为以下两个步骤:
1.初始化查找环境,诸如设置初始查找目录,设置查找标志,这些均被保存在struct
nameidata中,该过程调用函数path_init();
2.进入真正地查找过程,调用函数path_walk(),这同时也是linux中的一个最基础的函数,对于该函数的详细分析见下文;
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------
path_init(dfd,
name, flags, nd)

static
int path_init(int dfd, const char *name, unsigned int flags, struct nameidata *nd)

{

int
retval = 0;
int
fput_needed;
struct
file *file;

//nd->last_type表示当前查找路径中最后一个分量的类型
nd->last_type
= LAST_ROOT; /* if there are only slashes... */
nd->flags
= flags;
nd->depth
= 0;
nd->root.mnt
= NULL;

//如果查找的文件/目录的路径以'/'开始,那么设置从根目录开始查找;
//即设置nd->root=nd->path=当前进程的根目录,同时增加nd->root的引用计数(因为nd->path=nd->root表示nd->root被//引用)
if
(*name=='/') {
set_root(nd);
nd->path
= nd->root;
path_get(&nd->root);
//如果不是以'/'开始,同时调用者设置查找从进程的当前工作目录开始查找,那么设置nd->path为当前工作目录;
}else
if (dfd == AT_FDCWD) {

get_fs_pwd(current->fs, &nd->path);
get_fs_pwd(current->fs,
&nd->path);
}
else {

struct
dentry *dentry;

file
= fget_light(dfd, &fput_needed);

retval
= -EBADF;

if
(!file)

goto
out_fail;

dentry
= file->f_path.dentry;

retval
= -ENOTDIR;

if
(!S_ISDIR(dentry->d_inode->i_mode))

goto
fput_fail;

retval
= file_permission(file, MAY_EXEC);

if
(retval)

goto
fput_fail;

nd->path
= file->f_path;

path_get(&file->f_path);

fput_light(file,
fput_needed);

}

return
0;

fput_fail:

fput_light(file,
fput_needed);

out_fail:

return
retval;

}

该函数的主要作用是初始化查找环境,如查找的起始目录,查找的标志(LOOKUP_FOLLOW),这些信息均被保存在数据结构struct
nameidata变量nd中;在设置查找的起始目录时候会根据待查找的文件目录的路径来判断:
1.若路径名以'/'开始,那么设置查找的初始目录为进程的根目录;
2.若不满足1的情况,那么根据调用者传入的参数来设置查找的起始目录是进程的当前工作目录或是别的情况;
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------
path_walk(name,
nd)

static
int path_walk(const char *name, struct nameidata *nd)

{

struct
path save = nd->path;
int
result;

current->total_link_count
= 0;

/*
make sure the stuff we saved doesn't go away */
//增加引用计数,保证nd->path在查找过程中不会被释放;
path_get(&save);

//调用link_path_walk()完成主要的查找任务,该函数在下文仔细分析;
result
= link_path_walk(name, nd);
if
(result == -ESTALE) {

/*
nd->path had been dropped */

current->total_link_count
= 0;

nd->path
= save;

path_get(&nd->path);

nd->flags
|= LOOKUP_REVAL;

result
= link_path_walk(name, nd);

}
//将前面增加的引用计数减1,可能会释放nd->path;
path_put(&save);

return
result;

}

-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------
link_path_walk(name,
nd)

static
int link_path_walk(const char *name, struct nameidata *nd)

{

struct
path next;
struct
inode *inode;
int
err;
//前面设置的查找的标志位,为LOOKUP_FOLLOW;
unsigned
int lookup_flags = nd->flags;
//忽略路径名起始部分的'/';
while
(*name=='/')
name++;
if
(!*name)
goto
return_reval;

inode
= nd->path.dentry->d_inode;
//前面初始化的时候设置的nd->depth=0;
if
(nd->depth)
lookup_flags
= LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);

/*
At this point we know we have a real path component. */
for(;;)
{

unsigned
long hash;

struct
qstr this;

unsigned
int c;

nd->flags
|= LOOKUP_CONTINUE;

//检查对于inode是否有执行的权限;

err
= exec_permission(inode);

if
(err)

break;

//用this来保存查找过程中路径名中的每个部分;

//查找的过程中,首先对该部分计算hash,然后根据父目录和hash来查找name对应的文件/目录的dentry结构;

this.name
= name;

c
= *(const unsigned char *)name;

//对路径名的当前分量作hash计算,define
init_name_hash 0;

hash
= init_name_hash();

do
{
name++;

hash = partial_name_hash(c, hash);

c = *(const unsigned char *)name;

}
while (c && (c != '/'));

this.len
= name - (const char *) this.name;

this.hash
= end_name_hash(hash);

//如果已经查找到路径名的最后分量,那么进入last_component过程处理;

if
(!c)

goto
last_component;

//如果路径名是以'/'结束,那么需要作一些特殊的处理,进入last_with_slashes分支

while
(*++name == '/');

if
(!*name)

goto
last_with_slashes;

//对于路径名中有.或者..的情况,需要做一些特殊的处理,.表示当前目录,..表示父目录;

if
(this.name[0] == '.') switch (this.len) {

default:

break;

case
2:

if
(this.name[1] != '.')

break;

//进入当前目录的父目录,该函数中需要判断父目录是否跟当前目录属于同一个文件系统

follow_dotdot(nd);

inode
= nd->path.dentry->d_inode;

/*
如果当前分量仅仅是一个.,那么继续下一次循环,因为这表示当前目录 */

case
1:

continue;

}

/*
This does the actual lookups.. */

//do_lookup()开始真正地查找过程;

//nd保存查找信息,最主要的是nd->path保存了父目录的信息;

//this:要在父目录nd->path中查找的文件名信息;

//next:保存查找的结果,即struct
path结构;

err
= do_lookup(nd, &this, &next);

if
(err)

break;

err
= -ENOENT;

//根据查找到的dentry找到该文件/目录的inode,若为NULL,表明出错;

//出错进入out_dput释放前面找到的dentry,即将其引用计数--;

inode
= next.dentry->d_inode;

if
(!inode)

goto
out_dput;

//如果找到的文件是一个符号链接,那么需要进入do_follow_link()函数,解析该符号链接;

if
(inode->i_op->follow_link) {

err
= do_follow_link(&next, nd);

if
(err)

goto
return_err;

err
= -ENOENT;

inode
= nd->path.dentry->d_inode;

if
(!inode)

break;

}
else

//否则,一切正常,那么就将本次查找得到的结果(在next中保存)更新nd->path,继续进入下一次查找;

path_to_nameidata(&next,
nd);

err
= -ENOTDIR;

//函数流程进入这个地方表示查找到的一定是目录(如果是常规文件则就进入last_component分支了),那么其

//lookup方法一定不能为NULL,否则意味着出错;

if
(!inode->i_op->lookup)

break;

continue;

//进入该分支意味着路径名以'/'结束,那么需要设置查找标志LOOKUP_DIRECTORY,表示查找的是一个目录;

last_with_slashes:

lookup_flags
|= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;

//进入这里面意味着查找过程即将结束,进入路径名最后分量的解析;

last_component:

/*
Clear LOOKUP_CONTINUE iff it was previously unset */

nd->flags
&= lookup_flags | ~LOOKUP_CONTINUE;

//如果设置了LOOKIP_PARENT标志位,那么无需解析最后的分量,因为上层要求查找父目录;

if
(lookup_flags & LOOKUP_PARENT)

goto
lookup_parent;

//接下来即是查找路径名中最后分量的过程,类似于上面代码中的for循环的每一次查找;

if
(this.name[0] == '.') switch (this.len) {

default:

break;

case
2:

if
(this.name[1] != '.')

break;

follow_dotdot(nd);

inode
= nd->path.dentry->d_inode;

case
1:

goto
return_reval;

}

err
= do_lookup(nd, &this, &next);

if
(err)

break;

inode
= next.dentry->d_inode;

if
(follow_on_final(inode, lookup_flags)) {

err
= do_follow_link(&next, nd);

if
(err)

goto
return_err;

inode
= nd->path.dentry->d_inode;

}
else

path_to_nameidata(&next,
nd);

err
= -ENOENT;

if
(!inode)

break;

if
(lookup_flags & LOOKUP_DIRECTORY) {

err
= -ENOTDIR;

if
(!inode->i_op->lookup)

break;

}

goto
return_base;

//如果调用者要求的只是查找文件的父目录,那么设置LOOKUP_PARENT;

//进入如下的分支,在该分支中,父目录的dentry信息已经找到,在nd结构中保存文件信息,

//nd->last就是记录路径名的最后一个分量;

lookup_parent:

nd->last
= this;

nd->last_type
= LAST_NORM;

if
(this.name[0] != '.')

goto
return_base;

if
(this.len == 1)

nd->last_type
= LAST_DOT;

else
if (this.len == 2 && this.name[1] == '.')

nd->last_type
= LAST_DOTDOT;

else

goto
return_base;

return_reval:

/*

*
We bypassed the ordinary revalidation routines.

*
We may need to check the cached dentry for staleness.

*/

if
(nd->path.dentry && nd->path.dentry->d_sb &&(nd->path.dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) {

err
= -ESTALE;

/*
Note: we do not d_invalidate() */

if
(!nd->path.dentry->d_op->d_revalidate(nd->path.dentry, nd))

break;

}

return_base:

return
0;

out_dput:

path_put_conditional(&next,
nd);

break;

}
path_put(&nd->path);
return_err:
return
err;

}

link_path_walk()是执行路径名解析的主要地方,即将传入的路径名的每个分量解析成为struct
dentry结构,解析的方法如下:
计算路径名中的分量的hash。为了提升系统性能,操作系统会缓存部分文件的dentry,关于dentry的缓存可参考google,因此该函数首先就要根据路径名中的每个分量的hash以及其父目录的dentry结构查找该文件的dentry是否缓存在内存中,如果没有,还需要从磁盘上读出该文件的dentry结构,并缓存在内存中。这样一直不停地去解析路径名的各个分量直到出现如下情况:
已经解析到了路径中的最后分量,此时需要谨慎处理,进入last_component分支。该分支也是进行正常的查找,只是查找的是路径名的最后分量。查找之前需要判断标志位的LOOKUP_PARENT是否被设置,如果有,则表示上层需要查找的是父目录dentry结构(其实这种情况很常见),那么此时就无需再去查找了,进入lookup_parent分支。如果路径名以'/'结束,那么函数转到last_with_slashes分支中,在该分支中需要设置一个查找的标志位LOOKUP_DIRECTORY,表示要查找的是一个目录;真正执行查找的函数是do_lookup()。
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------
do_lookup(nd,
&this, &next)
static
int do_lookup(struct nameidata *nd, struct qstr *name,struct path *path)

{

struct
vfsmount *mnt = nd->path.mnt;
struct
dentry *dentry, *parent;
struct
inode *dir;

//底层的文件系统可能会实现自己的hash方法
if
(nd->path.dentry->d_op && nd->path.dentry->d_op->d_hash) {
int
err = nd->path.dentry->d_op->d_hash(nd->path.dentry, name);
if
(err < 0)
return
err;
}

//__d_lookup()在内存中查找文件名为name的dentry;
//nd->path.dentry是父目录的dentry结构;
//name是需要查找的文件的文件名;
dentry
= __d_lookup(nd->path.dentry, name);
if
(!dentry)
goto
need_lookup;

found:

if
(dentry->d_op && dentry->d_op->d_revalidate)
goto
need_revalidate;

done:

//查找到的结果保存在path中返回;

path->mnt
= mnt;
path->dentry
= dentry;
//判断当前查找到的dentry是否是一个挂载点,如果是的话,需要找到挂载点挂载的文件系统的根目录;
__follow_mount(path);
return
0;

need_lookup:

parent
= nd->path.dentry;
dir
= parent->d_inode;

mutex_lock(&dir->i_mutex);
//在获取锁的过程中该文件的dentry可能已经被读入内存,所以这里重新查找一番;
dentry
= d_lookup(parent, name);
if
(likely(!dentry)) {

dentry
= d_alloc_and_lookup(parent, name, nd);

mutex_unlock(&dir->i_mutex);

if
(IS_ERR(dentry))

goto
fail;

goto
done;

}

mutex_unlock(&dir->i_mutex);
goto
found;

need_revalidate:

dentry
= do_revalidate(dentry, nd);
if
(!dentry)

goto
need_lookup;

if
(IS_ERR(dentry))

goto
fail;

goto
done;

fail:

return
PTR_ERR(dentry);

}
do_lookup()函数根据父目录的dentry以及待查找的文件的文件名name查找文件的dentry结构,首先在内存中查找,调用__do_lookup(),如果存在内存中,那么比较简单;如果不在内存中,那么还需要从磁盘中将父目录下的文件名为name的文件的dentry读出。查找的结果保存在path结构中,查找成功以后还需要判断该dentry上是否挂载了别的文件系统,如果挂载了,那么还需要将返回的path设置为被挂载文件系统的根目录信息,具体是调用了函数__follow_mount()。
至此,kern_path()的过程就已经基本完成,找到了挂载点的struct
dentry结构。下面的函数分析就进入了真正的挂载过程。
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------

do_new_mount(&path, type_page, flags, mnt_flags,dev_name,
data_page)

static int do_new_mount(struct path *path, char *type, int flags,int mnt_flags, char *name, void *data)

{

struct vfsmount *mnt;

if (!type)

return -EINVAL;

/* mount需要管理员权限 */

if (!capable(CAP_SYS_ADMIN))

return -EPERM;

/*lock_kernel ???*/

lock_kernel();
//type:需要挂载的文件系统的类型;
//flags:挂载标志位,在上面设置;
//name:挂载设备的名称;
//data:挂载的参数选项;

mnt = do_kern_mount(type, flags, name, data);

unlock_kernel();

if (IS_ERR(mnt))

return PTR_ERR(mnt);

return do_add_mount(mnt, path, mnt_flags, NULL);

}
上文的查找挂载点dentry工作完成以后就要进入真正的挂载过程。do_new_mount()主要包含以下部分:
1.参数检查,type不能为空,否则返回参数错误信息;权限检查,没有管理员权限无法进行挂载;
2.作真正挂载前的准备工作,调用函数do_kern_mount(),这里主要是初始化一个struct vfsmount结构,对结构的解析可参考数据 结构部分;
3.真正的挂载过程在函数do_add_mount()中完成。
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------
do_kern_mount(type, flags, name, data)

struct vfsmount *

do_kern_mount(const char *fstype, int flags, const char *name, void *data)

{
//根据文件系统的名称在内核中查找该文件系统的实例,即代表该文件系统的struct file_system_type结构;
//在内核中,内核支持的文件系统会被组织成为一个单链表,struct file_system_type结构参考数据结构部分的描述;

struct file_system_type *type = get_fs_type(fstype);

struct vfsmount *mnt;

if (!type)

return ERR_PTR(-ENODEV);

//初始化一个struct vfsmount结构,用来将文件系统挂载至目录树中;
//type:上面找到的文件系统实例;
mnt = vfs_kern_mount(type, flags, name, data);

if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&!mnt->mnt_sb->s_subtype)

mnt = fs_set_subtype(mnt, fstype);

put_filesystem(type);

return mnt;

}

该函数首先根据待挂载的文件系统的名称找到内核中保存的该文件系统的struct file_system_type实例;
然后根据该实例初始化struct vfsmount,调用函数vfs_kern_mount();
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------
vfs_kern_mount(type, flags, name, data)

struct vfsmount *

vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)

{

struct vfsmount *mnt;

char *secdata = NULL;

int error;

if (!type)

return ERR_PTR(-ENODEV);

error = -ENOMEM;
//分配一个struct vfsmount结构;

mnt = alloc_vfsmnt(name);

if (!mnt)

goto out;

if (flags & MS_KERNMOUNT)

mnt->mnt_flags = MNT_INTERNAL;

if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) {

secdata = alloc_secdata();

if (!secdata)

goto out_mnt;

error = security_sb_copy_data(data, secdata);

if (error)

goto out_free_secdata;

}

//对于ext2文件系统来说,get_sb()方法其实是ext2_get_sb();
//用来从磁盘上读取超级块信息struct super_block,将这些信息保存在mnt中
//阅读代码发现,mnt中主要保存的信息有mnt->mnt_sb保存读出的super_block;
//mnt->mnt_root保存待挂载的文件系统的根目录;

error = type->get_sb(type, flags, name, data, mnt);

if (error < 0)

goto out_free_secdata;

BUG_ON(!mnt->mnt_sb);

WARN_ON(!mnt->mnt_sb->s_bdi);

mnt->mnt_sb->s_flags |= MS_BORN;

//安全相关,暂时不去理会;

error = security_sb_kern_mount(mnt->mnt_sb, flags, secdata);

if (error)

goto out_sb;

/*

* filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE

* but s_maxbytes was an unsigned long long for many releases. Throw

* this warning for a little while to try and catch filesystems that

* violate this rule. This warning should be either removed or

* converted to a BUG() in 2.6.34.

*/

WARN((mnt->mnt_sb->s_maxbytes < 0), "%s set sb->s_maxbytes to "

"negative value (%lld)\n", type->name, mnt->mnt_sb->s_maxbytes);

//设置挂载点等信息;

mnt->mnt_mountpoint = mnt->mnt_root;

mnt->mnt_parent = mnt;
//写者释放信号量;

up_write(&mnt->mnt_sb->s_umount);

free_secdata(secdata);

return mnt;

out_sb:

dput(mnt->mnt_root);

deactivate_locked_super(mnt->mnt_sb);

out_free_secdata:

free_secdata(secdata);

out_mnt:

free_vfsmnt(mnt);

out:

return ERR_PTR(error);

}

该函数主要作用是分配并初始化一个struct vfsmount结构:
1.调用alloc_vfsmnt()分配一个struct vfsmount结构并作一些最简单的初始化;
2.调用type->get_sb()来读取需要挂载的文件系统的super_block结构,读出其根目录dentry,设置struct vfsmount;
至此,为后续的挂载过程分配挂载连接件的任务就已经完成,下面就需要将该连接件挂入文件系统的目录树中。
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------

do_add_mount(mnt, path, mnt_flags, NULL)
int
do_add_mount(struct vfsmount *newmnt, struct path *path,

int mnt_flags, struct list_head *fslist)

{

int err;

mnt_flags &= ~(MNT_SHARED | MNT_WRITE_HOLD | MNT_INTERNAL);

//要申请信号量,对全局变量的读写;

down_write(&namespace_sem);

/* Something was mounted here while we slept */
//down_write()可能导致休眠,挂载点可能在休眠的时候被挂载别的文件系统,那么需要进入被挂载的文件系统的根目录
//d_mountpoint()判断目录是否被挂载文件系统,follow_down()进入被挂载文件系统的目录树结构;

while (d_mountpoint(path->dentry) &&

follow_down(path))

;

err = -EINVAL;

if (!(mnt_flags & MNT_SHRINKABLE) && !check_mnt(path->mnt))

goto unlock;

/* Refuse the same filesystem on the same mount point */

err = -EBUSY;
//如果挂载点struct
path
//path->mnt->mnt_sb代表挂载点所属的文件系统的super_block,path->mnt->mnt_root代表挂载点所在文件系统的根目录
//那么下面这个判断代表什么意思??暂时还是有点糊涂。

if (path->mnt->mnt_sb == newmnt->mnt_sb &&

path->mnt->mnt_root == path->dentry)

goto unlock;

err = -EINVAL;

if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode))

goto unlock;

newmnt->mnt_flags = mnt_flags;
//完成主要的挂载工作;
//newmnt代表需要挂载的文件系统的连接件;
//path代表挂载点信息;

if ((err = graft_tree(newmnt, path)))

goto unlock;

//传入的参数中flist=NULL,暂时可不去理会;

if (fslist) /* add to the specified expiration list */

list_add_tail(&newmnt->mnt_expire, fslist);

up_write(&namespace_sem);

return 0;

unlock:

up_write(&namespace_sem);

mntput(newmnt);

return err;

}
该函数进行挂载任务:
1.检查参数,判断挂载点是否出现别的情况,在我们休眠的时候,如是否已经挂载了别的文件系统等;
2.调用graft_tree()完成主要的挂载工作;
-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------

graft_tree(newmnt, path)

static
int graft_tree(struct vfsmount *mnt, struct path *path)

{

int err;

if (mnt->mnt_sb->s_flags & MS_NOUSER)

return -EINVAL;

//判断挂载点以及待挂载的文件系统的是否都是目录;应该是不支持将文件系统挂载到一个文件上的;

if (S_ISDIR(path->dentry->d_inode->i_mode) !=

S_ISDIR(mnt->mnt_root->d_inode->i_mode))

return -ENOTDIR;

err = -ENOENT;

mutex_lock(&path->dentry->d_inode->i_mutex);
//暂时还不清楚这是用来判断什么的;

if (cant_mount(path->dentry))

goto out_unlock;

//暂时还不清楚d_unlinked(path->dentry)到底是干什么的;

if (!d_unlinked(path->dentry))
//将连接件插入到文件系统的目录树中

err = attach_recursive_mnt(mnt, path, NULL);

out_unlock:

mutex_unlock(&path->dentry->d_inode->i_mutex);

return err;

}

-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------

attach_recursive_mnt(mnt,
path, NULL)

static
int attach_recursive_mnt(struct vfsmount *source_mnt,struct path *path, struct path *parent_path)

{
//这是什么用法???

LIST_HEAD(tree_list);

struct vfsmount *dest_mnt = path->mnt;

struct dentry *dest_dentry = path->dentry;

struct vfsmount *child, *p;

int err;

if (IS_MNT_SHARED(dest_mnt)) {

err = invent_group_ids(source_mnt, true);

if (err)

goto out;

}
//暂时还不明了其含义;

err = propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list);

if (err)

goto out_cleanup_ids;

br_write_lock(vfsmount_lock);

if (IS_MNT_SHARED(dest_mnt)) {

for (p = source_mnt; p; p = next_mnt(p, source_mnt))

set_mnt_shared(p);

}
//调用的时候传入的parent_path=NULL

if (parent_path) {

detach_mnt(source_mnt, parent_path);

attach_mnt(source_mnt, path);

touch_mnt_namespace(parent_path->mnt->mnt_ns);

} else {
//设置挂载点dest_mnt和被挂载文件系统的vfsmount之间的关系
//void
mnt_set_mountpoint(struct vfsmount *mnt, struct dentry *dentry,struct vfsmount *child_mnt)

//{

// child_mnt->mnt_parent = mntget(mnt);

// child_mnt->mnt_mountpoint = dget(dentry);

// dentry->d_mounted++;

//}

mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);
//将代表新挂载文件系统的struct
vfsmount结构体添加到各种链表中并修改全局的一些信息;

commit_tree(source_mnt);

}

//这是干什么??暂不清楚。。。

list_for_each_entry_safe(child, p, &tree_list, mnt_hash) {

list_del_init(&child->mnt_hash);

commit_tree(child);

}

br_write_unlock(vfsmount_lock);

return 0;

out_cleanup_ids:

if (IS_MNT_SHARED(dest_mnt))

cleanup_group_ids(source_mnt, NULL);

out:

return err;

}

该函数完成了对新挂载文件系统的struct
vfsmount结构的插入工作,主要来说分以下几个步骤:
1.设置挂载点文件系统的struct
vfsmount结构 source_vfsmount和待挂载的文件系统的struct vfsmount dest_vfsmount之间的关 系,设置的过程调用了函数mnt_set_mountpoint()
2.将新挂载的文件系统的struct
vfsmount结构添加到系统的各种链表中,调用函数commit_tree();
下图能够很好地描述挂载点文件系统和被挂载的文件系统之间的关系:



-------------------------------------------------------------------------------------------------------------------*******************************************************************************************************************-------------------------------------------------------------------------------------------------------------------
commit_tree(source_mnt)

static void commit_tree(struct
vfsmount *mnt)

{
//前面设置了被挂载文件系统的struct
vfsmount结构的mnt->parent成员,即挂载点所在的文件系统的struct vfsmount;

struct vfsmount *parent = mnt->mnt_parent;

struct vfsmount *m;
//head表示什么链表的表头?

LIST_HEAD(head);

struct mnt_namespace *n = parent->mnt_ns;

BUG_ON(parent == mnt);

//添加到全局的struct vfsmount链表中;

list_add_tail(&head, &mnt->mnt_list);

list_for_each_entry(m, &head, mnt_list)

m->mnt_ns = n;

list_splice(&head, n->list.prev);

//添加到全局的mount_hashtable的某个链表的表尾;

list_add_tail(&mnt->mnt_hash, mount_hashtable +hash(parent, mnt->mnt_mountpoint));
//将被挂载文件系统的struct
vfsmount结构添加到其父vfsmount的孩子链表中;

list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
//对于全局的namespace作了修改,这个函数应该是提交这些修改,但暂时不确定;

touch_mnt_namespace(n);

}

该函数主要是将被挂载的文件系统的struct
vfsmount结构体添加到各种链表中,见代码上面的注释。
该函数中涉及到了一个结构体struct
mnt_namespace,其含义可参见“数据结构部分”。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息