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)
{
out_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);
out_dev:kfree(kernel_dev);
out_dir:putname(kernel_dir);
out_type:kfree(kernel_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)
{
dput_out: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);
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);