首页 > 其他 > 详细

深入理解系统调用

时间:2020-05-26 15:50:32      阅读:37      评论:0      收藏:0      [点我收藏+]

------------恢复内容开始------------

选择系统调用

本人学号末尾为21,选择21号系统调用

技术分享图片

 

 

 

gdb调试与分析

int main()
{
  asm volatile(
  "movl $0x15,%eax\n\t"
  "syscall\n\t" );
  return 0;
}

  

重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化

 sys_mount()流程分析

 
      内核mount函数的入口为sys_mount(),你需要在fs/namespace.c的SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data)才能找到其实现,其源代码如下:
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;
}
       

  

总体上来说,该函数还算比较简单,主要就作了两件事:

 
1. 将用户态的参数拷贝至内核态,因为在后面需要使用这些参数(为什么需要,难道内核态不能直接使用用户态的数据吗?);
2.  调用do_mount()接手接下来的挂载工作。
/* 参数:  
** 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来调用不同的挂载函数,我们这里主要关注普通块设备的挂载,其他的以后遇到再分析吧,这这里面调用了下面两个主要的函数:

1. kern_path():该函数的主要作用是根据挂载点的路径名在内核中查找其内存目录项结构(struct dentry),保存在path中;
2. do_new_mount(),该函数接手来完成接下来的挂载工作。
/* 参数:
** @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形成一种父子关系结构。形成的结构图如下所示:

 技术分享图片

 
 
 至此,整个的mount过程的代码分析也就差不多了,虽然个中还有不少的细节问题需要再深入地研究。最后,让我们归纳下整个的调用流程:
sys_mount()
       |
       ----------->do_mount()
                                |
                                ------------->do_new_mount()
                                                              |
                                                              ----------->do_kern_mount()
                                                              |                            |
                                                              |                             -------->vfs_kern_mount()
                                                              |
                                                              ------------->do_add_mount()
                                                                                            |
                                                                                            ----------->graft_tree()
                                                                                                                     |
                                                                                                                     -------->attach_recursive_mnt()

 

 

------------恢复内容结束------------

深入理解系统调用

原文:https://www.cnblogs.com/olddriver555111/p/12965789.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!