------------恢复内容开始------------
选择系统调用
本人学号末尾为21,选择21号系统调用

gdb调试与分析
int main()
{
asm volatile(
"movl $0x15,%eax\n\t"
"syscall\n\t" );
return 0;
}
重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
sys_mount()流程分析
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;
/* 以下几个函数将用户态参数拷贝至内核态,包括:
** 1. kernel_type:挂载文件系统类型,如ext3
** 2. kernel_dir: 挂载点路径
** 3. dev_name: 设备名称
** 4. data_pages: 选项信息
*/
ret = copy_mount_string(type, &kernel_type);
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);
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_data:
kfree(kernel_dev);
out_dev:
putname(kernel_dir);
out_dir:
kfree(kernel_type);
out_type:
return ret;
}
总体上来说,该函数还算比较简单,主要就作了两件事:
/* 参数: ** dev_name : 挂载设备名称 ** dir_name : 挂载点名称 ** type_page: 保存挂载的文件系统类型,如"ext3" ** flags : 挂载标志 ** data_page: 大部分情况下为NULL */ 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; /* Basic sanity checks */ if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE)) return -EINVAL; if (data_page) ((char *)data_page)[PAGE_SIZE - 1] = 0; /* 调用kern_path(),根据挂载点名称查找其dentry等信息 ** 参数:@dir_name : 挂载点路径 ** :@LOOKUP_FOLLOW: 查找标志,遇到链接继续查找 ** :@path : 查找结果保存与此结构中 */ 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); /* 我们不关心别的,只关注do_new_mount */ 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, dev_name, data_page); dput_out: path_put(&path); return retval; }
在do_mount()函数中,作了一些标志位的检查,安全性检查等附加工作,然后根据不同的flag来调用不同的挂载函数,我们这里主要关注普通块设备的挂载,其他的以后遇到再分析吧,这这里面调用了下面两个主要的函数:
/* 参数:
** @path: 保存挂载点信息,包括dentry结构等
** @type: 挂载文件系统类型,如ext3
** @flags & mnt_flags:挂载标志
** @name:设备名称
** @data: 保存额外的数据,一般是NULL
*/
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;
/* we need capabilities... */
/* 必须是root权限*/
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
lock_kernel();
/* 调用do_kern_mount()来完成挂载第一步,主要是创建一个vfsmount结构并对其初始化
** 并读设备的superblock并初始化文件系统在内存中的结构等
*/
mnt = do_kern_mount(type, flags, name, data);
unlock_kernel();
if (IS_ERR(mnt))
return PTR_ERR(mnt);
/* 将上面创建的vfsmount结构添加到全局结构中
** 形成目录树结构
*/
return do_add_mount(mnt, path, mnt_flags, NULL);
}
do_new_mount()主要调用两个函数,进行具体挂载:
1. do_kern_mount():该函数主要是为新的文件系统准备一个挂载结构vfsmount,初始化,并从设备上读出超级块等信息,在内存中构建文件系统的轮廓,会在后面具体描述这一过程;
2. do_add_mount():将1中创建的vfsmount结构添加到全局结构中,以便在内存中形成一棵树结构。
让我们对这两个函数一一击破吧,先从do_kern_mount,主要的挂载工作在该函数中完成。
/* 参数: fstype: 文件系统名称,如"ext3"
** flags : 挂载标志
** name : 设备名称,如"/dev/sda"
** data : 其他挂载数据,暂时忽略
*/
struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
{
/* 首先根据文件系统名称获取文件系统结构file_system_type
** 内核中所有支持的文件系统的该结构通过链表保存
*/
struct file_system_type *type = get_fs_type(fstype);
struct vfsmount *mnt;
if (!type)
return ERR_PTR(-ENODEV);
/* 调用vfs_kern_mount()完成主要挂载*/
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;
}
该函数主要流程是:1. 调用get_fs_type()通过文件系统名查找到文件系统的struct file_system_type结构;2.通过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 vfs_mount结构
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;
}
//调用具体文件系统的get_sb方法
//从name代表的设备上读出超级块信息
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);
//设置挂载点的dentry结构为刚读出的设备的根目录
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);
}
我们追踪到这里对于第一个部分的函数调用do_kern_mount()就已经基本结束,在这个相当底层的函数中,它主要调用了具体文件系统的get_sb()方法来从设备上读出超级块,这个我们会在具体文件系统剖析的时候去作深入地分析。其实就是读出超级块,作有效性检查,内存构造根目录项等工作。
至此,我们第一部分的主要工作就完成了,在该部分的核心工作就是创建一个struct vfsmount,并读出文件系统超级块来初始化该结构。接下来就是将该结构添加到全局结构中,这就是do_add_mount()的主要工作。
/*参数: mnt: 本次挂载新建的vfsmount结构
** : 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;
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()。
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; //这个与sharemount相关,暂时忽略 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,所以我们 //对else分支比较感兴趣 if (parent_path) { detach_mnt(source_mnt, parent_path); attach_mnt(source_mnt, path); touch_mnt_namespace(parent_path->mnt->mnt_ns); } else { //这里主要是将新建的source_mnt与挂载点(dest_dentry) 挂钩 //并和挂载点所在的dest_mnt建立起父子关系 mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt); //该函数也比较简单,不深入了 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; }
该函数中很多的代码可能与sharemount机制相关,关于该机制我会在另外一篇博客中专门阐述,这里就忽略了,因为参数parent_path主要为NULL,所以我们比较关注的代码分支是else,即mnt_set_mountpoint和commit_tree,其实两个函数干的事情都比较简单,主要是将当前新建的vfsmount结构与挂载点挂钩,并和挂载点所在的vfsmount形成一种父子关系结构。形成的结构图如下所示:
------------恢复内容结束------------
原文:https://www.cnblogs.com/olddriver555111/p/12965789.html