------------恢复内容开始------------
选择系统调用
本人学号末尾为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