Linux 系统调用之open(一)

Linux下一切设备均视为文件,本文来简单分析下系统调用里面是如何打开文件的。本文仅以下面简单代码为例,看Linux内核如何完成open的系统调用的(参考内核版本:4.14.13,x86_64)。为了简单起见,本文中暂时忽略并发性访问的同步问题以及错误处理(一般情况下,上述事例中的操作都会成功,除非有权限问题,所以我们先不考虑函数调用失败的情况)。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
                                                                                
int main(int argc, char* argv[])
{
        int fd = open("/home/gaobsh/a.txt", O_RDONLY);
        if (fd >= 0) close(fd);
        return 0;
}

do_sys_open()函数开始分析,至于程序如何进入该函数,本文不做分析。该函数定义如下:

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
    /* 函数参数如下:
    * dfd = -100 (AT_FDCWD)
    * filename = "/home/gaobsh/a.txt"
    * flags = 0x8000 (O_RDONLY | O_LARGEFILE);
    * mode = 0
    */
    struct open_flags op;
	int fd = build_open_flags(flags, mode, &op);
	struct filename *tmp;

	if (fd)
		return fd;

	tmp = getname(filename);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	fd = get_unused_fd_flags(flags);
	if (fd >= 0) {
		struct file *f = do_filp_open(dfd, tmp, &op);
		if (IS_ERR(f)) {
			put_unused_fd(fd);
			fd = PTR_ERR(f);
		} else {
			fsnotify_open(f);
			fd_install(fd, f);
		}
	}
	putname(tmp);
	return fd;
}

其中各个参数除了 int dfd 之外,其他的都很熟悉,就是open()系统调用的各个参数。至于参数dfd,它的值为 AT_FDCWD (-100).在这个函数里面,首先分析下 build_open_flags()函数。这个函数主要是用来构建flags,并返回到结构体 struct open_flags op中。该函数定义如下:

static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op)
{
    int lookup_flags = 0;
    int acc_mode = ACC_MODE(flags);

    flags &= VALID_OPEN_FLAGS;

    if (flags & (O_CREAT | __O_TMPFILE)) /* if 判断为 0 */
        ...
    else
        op->mode = 0;

    /* Must never be set by userspace */
    flags &= ~FMODE_NONOTIFY & ~O_CLOEXEC;

    if (flags & __O_SYNC) /* if 判断为 0 */
        ...

    if (flags & __O_TMPFILE) { /* if 判断为 0 */
        ...
    } else if (flags & O_PATH) { /* if 判断为 0 */
       ...
    }

    op->open_flag = flags;

    if (flags & O_TRUNC) /* if 判断为 0 */
        ...
    if (flags & O_APPEND) /* if 判断为 0 */
        ...

    op->acc_mode = acc_mode;
    op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN; /* op->intent = LOOKUP_OPEN */

    if (flags & O_CREAT) { /* if 判断为 0 */
        ...
    }
    if (flags & O_DIRECTORY) /* if 判断为 0 */
        ...
    if (!(flags & O_NOFOLLOW)) /* if 判断为 1 */
        lookup_flags |= LOOKUP_FOLLOW;
    op->lookup_flags = lookup_flags;
    
    /* 此时 op 各个成员变量值如下:
    * op->mode = 0
    * op->open_flag = 0x8000
    * op->acc_mode = 0x4
    * op->intent = 0x100 (LOOKUP_OPEN)
    * op->lookup_flags = 0x1 (LOOKUP_FOLLOW)
    */
    return 0;
}
可见在本示例中,不考虑很多特殊情况,该 build_open_flags()函数中很多代码是不用执行的。   在 get_unused_fd_flags()函数中,找到一个可用的文件描述符,并返回该值。其中涉及的函数和数据结构这里不做分析, 这篇博客解释的非常清楚,虽然是基于ARM的,但源码基本一样。

下面来重点介绍do_filp_open()函数。该函数定义如下:

struct file *do_filp_open(int dfd, struct filename *pathname,
		const struct open_flags *op)
{
    /* 函数入口参数:
    * dfd = -100
    * pathname.name = "/home/gaobsh/a.txt"
    * op 见 build_open_flags
    */ 
    struct nameidata nd;
	int flags = op->lookup_flags;
	struct file *filp;

	set_nameidata(&nd, dfd, pathname);
	filp = path_openat(&nd, op, flags | LOOKUP_RCU);
	if (unlikely(filp == ERR_PTR(-ECHILD)))
		filp = path_openat(&nd, op, flags);
	if (unlikely(filp == ERR_PTR(-ESTALE)))
		filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
	restore_nameidata();
	return filp;
}

其中set_nameidata()函数主要用来设置结构体struct nameidata的值,这个结构体是个非常重要的结构提,在解析和查找路径名时会经常引用到。函数定义如下:

static void set_nameidata(struct nameidata *p, int dfd, struct filename *name)
{
    struct nameidata *old = current->nameidata;
    p->stack = p->internal;
    p->dfd = dfd;
    p->name = name; 
    p->total_link_count = old ? old->total_link_count : 0;
    p->saved = old;
    current->nameidata = p;
}
再下面就是 do_filp_open()函数的主要部分:调用 path_openat()函数。这个函数可能被调用三次。第一次调用尝试以 rcu模式打开,在本事例中我们分析在rcu模式中成功打开的情况。该函数定义如下:
static struct file *path_openat(struct nameidata *nd,
            const struct open_flags *op, unsigned flags)
{
    /* 函数入口参数: flags = LOOKUP_FOLLOW | LOOKUP_RCU */
    const char *s;
    struct file *file;
    int opened = 0;
    int error;

    file = get_empty_filp();
    if (IS_ERR(file)) /* if 判断为 0 */
        ...

    file->f_flags = op->open_flag; /* f_flags == O_LARGEFILE */

    if (unlikely(file->f_flags & __O_TMPFILE)) { /* if 判断为 0 */
        ...
    }
    if (unlikely(file->f_flags & O_PATH)) { /* if 判断为 0 */
        ...
    }

    s = path_init(nd, flags);
    if (IS_ERR(s)) { /* if 判断为 0 */
        ...
    }
    while (!(error = link_path_walk(s, nd)) &&
        (error = do_last(nd, file, op, &opened)) > 0) { /*do_last()函数返回 0,循环退出 */
        ...
        }
    }
    terminate_walk(nd);
out2:
    if (!(opened & FILE_OPENED)) { /* if 判断为 0 */
        ...
    }
    if (unlikely(error)) { /* if 判断为 0 */
        ...
    }
    return file;
}

先看其中的 path_init()函数。在解析路径的过程中,需要遍历路径中的每个部分,而其中使用结构体struct nameidata来保存当前遍历的状态。path_init()函数主要用来将该结构体初始化。函数定义如下:

static const char *path_init(struct nameidata *nd, unsigned flags)
{
    /* 函数入口参数: flags = LOOKUP_FOLLOW | LOOKUP_RCU */
    const char *s = nd->name->name; /* s = "/home/gaobsh/a.txt" */

    if (!*s) /* if 判断为 0 */
        ...

    nd->last_type = LAST_ROOT; /* if there are only slashes... */
    nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT;
    nd->depth = 0;
    if (flags & LOOKUP_ROOT) { /* if 判断为 0 */
        ...
    }

    nd->root.mnt = NULL;
    nd->path.mnt = NULL;
    nd->path.dentry = NULL;

    nd->m_seq = read_seqbegin(&mount_lock); 
    if (*s == '/') { /* if 判断为 1,表示从绝对路径‘/’开始解析目录 */
        if (flags & LOOKUP_RCU) /* if 判断为 1 */
            rcu_read_lock();
        set_root(nd);
        if (likely(!nd_jump_root(nd))) /* if 判断为 1 */
            return s; /* 函数由此处返回 */
        ...
    }
    ...
}

下面分析set_root()函数,该函数设置struct nameidata nd 的值,使得路径解析从根目录开始(而不是当前工作目录)。函数定义如下:

/* 本示例中,该函数简化如下:
nd->root = current->fs->root;
*/
static void set_root(struct nameidata *nd)
{
    struct fs_struct *fs = current->fs;

    if (nd->flags & LOOKUP_RCU) {
        unsigned seq;

        do {
            seq = read_seqcount_begin(&fs->seq);
            nd->root = fs->root;
            nd->root_seq = __read_seqcount_begin(&nd->root.dentry->d_seq);
        } while (read_seqcount_retry(&fs->seq, seq));
    } else {
        get_fs_root(fs, &nd->root);
    }
}
下面分析 nd_jump_root()函数。该函数作用和 set_root()函数差不多,但做的工作要更多一些。函数定义如下:
static int nd_jump_root(struct nameidata *nd)
{
    if (nd->flags & LOOKUP_RCU) { /* if 判断为 1 */
        struct dentry *d;
        nd->path = nd->root;
        d = nd->path.dentry;
        nd->inode = d->d_inode;
        nd->seq = nd->root_seq;
        if (unlikely(read_seqcount_retry(&d->d_seq, nd->seq))) /* if 判断为 0 */
            ...
    } else {
        ...
    }
    nd->flags |= LOOKUP_JUMPED;
    return 0;
}
现在我们回到 path_openat()函数,对struct nameidata nd 结构体初始化完成之后,便进入路径解析的主要部分: link_path_walk() 函数。在这个函数里面,将路径名逐步解析,并最终找到目标文件的 struct dentry 结构体(保存在参数 struct nameidata *nd 中)。该函数很长,在本示例中,由于我们并不涉及链接文件以及特殊文件名(.和..)的解析问题,该函数可简化如下(忽略权限检查及错误处理):
static int link_path_walk(const char *name, struct nameidata *nd)
{
    int err;

    while (*name=='/') 
        name++;
    if (!*name) /* if 判断为 0 */
        ...

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

        err = may_lookup(nd); 
        if (err) /* if 判断为 0 */
            ...

        hash_len = hash_name(nd->path.dentry, name);

        type = LAST_NORM;
        if (name[0] == '.') /* if 判断为 0 */
            ...
        if (likely(type == LAST_NORM)) { /* if 判断为 1 */
            struct dentry *parent = nd->path.dentry;
            nd->flags &= ~LOOKUP_JUMPED;
            if (unlikely(parent->d_flags & DCACHE_OP_HASH)) { /* if 判断为 0 */
                ...
            }
        }

        nd->last.hash_len = hash_len;
        nd->last.name = name;
        nd->last_type = type;

        name += hashlen_len(hash_len);
        if (!*name) /* 除了路径中的最后一部分('a.txt')if判断为1,其他 if 判断为 0 */
            goto OK;
        /*
         * If it wasn't NUL, we know it was '/'. Skip that
         * slash, and continue until no more slashes.
         */
        do {
            name++;
        } while (unlikely(*name == '/'));
        if (unlikely(!*name)) { /* if 判断为 0 */
OK:
            /* pathname body, done */
            if (!nd->depth)
                return 0; /* 解析到路径最后一部分时,该函数由此处返回 */
            ...
        } else {
            /* not the last component */
            err = walk_component(nd, WALK_FOLLOW | WALK_MORE); /* err == 0 */
        }
        if (err < 0) /* if 判断为 0 */
            ...

        if (err) { /* if 判断为 0 */
           ...
        }
        if (unlikely(!d_can_lookup(nd->path.dentry))) { /* if 判断为 0 */
            ...
        }
    } /* go back to for loop */
}

可见,该函数是在一个死循环for(;;)里面对路径名中的每一个部分进行解析,完成解析后函数返回。解析前两个部分(home 和 gaobsh)时,均调用walk_component()函数。其定义如下(以解析‘home’为例来分析该函数):

static int walk_component(struct nameidata *nd, int flags)
{
    /* 函数入口参数: flags = WALK_FOLLOW | WALK_MORE */
    struct path path;
    struct inode *inode;
    unsigned seq;
    int err;
    /*
     * "." and ".." are special - ".." especially so because it has
     * to be able to know about the current root directory and
     * parent relationships.
     */
    if (unlikely(nd->last_type != LAST_NORM)) { /* if 判断为 0 */
        ...
    }
    err = lookup_fast(nd, &path, &inode, &seq);
    if (unlikely(err <= 0)) { /* if 判断为 0,这里我们假设相应文件的dentry已经存在与系统缓存中 */
        ...
    }

    return step_into(nd, &path, flags, inode, seq);
}
其中我们调用了 lookup_fast()函数,仍以解析第一部分"home"为例,此时该函数等价与如下:
static int lookup_fast(struct nameidata *nd,
               struct path *path, struct inode **inode,
               unsigned *seqp)
{
    struct vfsmount *mnt = nd->path.mnt;
    struct dentry *dentry, *parent = nd->path.dentry;
    int status = 1;
    int err;

    /*
     * Rename seqlock is not required here because in the off chance
     * of a false negative due to a concurrent rename, the caller is
     * going to fall back to non-racy lookup.
     */
    if (nd->flags & LOOKUP_RCU) {/* if 判断为 1 */
        unsigned seq;
        bool negative;
        dentry = __d_lookup_rcu(parent, &nd->last, &seq);
        if (unlikely(!dentry)) { /* if 判断为 0 */
            .。。
        }

        /*
         * This sequence count validates that the inode matches
         * the dentry name information from lookup.
         */
        *inode = d_backing_inode(dentry);
        negative = d_is_negative(dentry);
        if (unlikely(read_seqcount_retry(&dentry->d_seq, seq))) /* if 判断为 0 */
            ...

        if (unlikely(__read_seqcount_retry(&parent->d_seq, nd->seq))) /* if 判断为 0 */
            ...

        *seqp = seq;
        status = d_revalidate(dentry, nd->flags);
        if (likely(status > 0)) { /* if 判断为 1 */
            /*
             * Note: do negative dentry check after revalidation in
             * case that drops it.
             */
            if (unlikely(negative)) /* if 判断为 0 */
                ...
            path->mnt = mnt;
            path->dentry = dentry;
            if (likely(__follow_mount_rcu(nd, path, inode, seqp))) /* if 判断为 1 */
                return 1; /*函数由此处返回 */
        }
        ...
    } else {
        ...
    }
    ...
}
其中函数 __d_lookup_rcu()用来找到"home"对应的dentry结构体并返回其指针,具体过程此处不做分析。这里我们简单介绍 __follow_mount_rcu()函数。该函数主要用来检查路径名是否是挂载点,如果是,则找到相应挂载的文件系统,并在挂载的文件系统下继续当前的文件解析工作(而不是在旧的文件系统下继续)。在本示例中,仍以"home"为例,该函数定义:
/*
 * Try to skip to top of mountpoint pile in rcuwalk mode.  Fail if
 * we meet a managed dentry that would need blocking.
 */
static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,
                   struct inode **inode, unsigned *seqp)
{
    for (;;) {
        struct mount *mounted;
        /*
         * Don't forget we might have a non-mountpoint managed dentry
         * that wants to block transit.
         */
        switch (managed_dentry_rcu(path)) { /* 跳转至 case 0: */
        case -ECHILD:
        default:
            return false;
        case -EISDIR:
            return true;
        case 0:
            break;
        }

        if (!d_mountpoint(path->dentry)) /*if 判断为 0(对应'home'文件夹,则为1 */
            return !(path->dentry->d_flags & DCACHE_NEED_AUTOMOUNT);

        mounted = __lookup_mnt(path->mnt, path->dentry);
        if (!mounted) /* if 判断为 1 */
            break; /* 结束循环 */
        ...
    }
    return !read_seqretry(&mount_lock, nd->m_seq) &&
        !(path->dentry->d_flags & DCACHE_NEED_AUTOMOUNT); /* return 1 */
} 
其中 __lookup_mnt()函数与 __d_lookup_rcu()类似,只不过这里是找到挂载的文件系统,而不是 dentry。在这里,"home"文件夹确实是一个挂载点,但在我的系统里面,并没有在该目录下挂载任何文件系统,因此, __lookup_mnt()函数返回0(找不到挂载在此处的文件系统)。其实相当于这个for(;;)循环里面什么都没做,就退出了。最终该函数返回值为1。其实相当于这个函数什么都没做,这也合理,因为"home"下没有挂载任何文件系统,不需要 `follow mount`。现在回到 walk_component()函数,在这里面我们还需要分析另外一个函数: step_into()。这个函数主要是处理符号链接的情况,在本示例中不涉及符号链接,该函数则很简单,定义如下:
static inline int step_into(struct nameidata *nd, struct path *path,
                int flags, struct inode *inode, unsigned seq)
{
    if (!(flags & WALK_MORE) && nd->depth) /* if 判断为 0 */
        ...
    if (likely(!d_is_symlink(path->dentry)) ||
       !(flags & WALK_FOLLOW || nd->flags & LOOKUP_FOLLOW)) { /* if 判断为 1 */
        /* not a symlink or should not follow */
        path_to_nameidata(path, nd);
        nd->inode = inode;
        nd->seq = seq;
        return 0; /*函数由此处返回 */
    }
    ...
}

其中path_to_nameidata()函数也超级简单:

static inline void path_to_nameidata(const struct path *path,
                    struct nameidata *nd)
{
    if (!(nd->flags & LOOKUP_RCU)) { /* if 判断为 0 */ 
        ...
    }
    nd->path.mnt = path->mnt;
    nd->path.dentry = path->dentry;
}

那么整个step_into()函数等价与如下:

static inline int step_into(struct nameidata *nd, struct path *path,
			    int flags, struct inode *inode, unsigned seq)
{
    nd->path.mnt = path->mnt;
    nd->path.dentry = path->dentry;
    nd->inode = inode;
    return 0;
}

至此,walk_component()函数分析完毕,我们返回其调用函数link_path_walk()。我们发现walk_component()函数返回之后,link_path_walk()函数的for(;;)循环第一次循环执行完毕,也就表示对路径的第一部分"home"的解析完毕。不难想象,在第二次执行link_path_walk()函数的for(;;)循环时,将会解析下一个路径,也就是"gaobsh"。解析过程和"home"极为类似,这里不再重复分析,解析完毕之后,将解析结果同样存入 struct nameidata *nd 中,之前对"home"的解析结果则被覆盖。当第三次执行for(;;)循环时,则解析最后一部分"a.txt"。对最后一部分的解析和前面的路径名解析稍有区别,不再调用walk_component()函数,而是直接返回,将最后解析工作留给do_last()函数来完成。

现在分析do_last()函数,定义如下 (to be continued...):

static int do_last(struct nameidata *nd,
           struct file *file, const struct open_flags *op,
           int *opened)
{
    struct dentry *dir = nd->path.dentry;
    int open_flag = op->open_flag;
    bool will_truncate = (open_flag & O_TRUNC) != 0;
    bool got_write = false;
    int acc_mode = op->acc_mode;
    unsigned seq;
    struct inode *inode;
    struct path path;
    int error;

    nd->flags &= ~LOOKUP_PARENT;
    nd->flags |= op->intent;

    if (nd->last_type != LAST_NORM) { /* if 判断为 0 */
        ...
    }

    if (!(open_flag & O_CREAT)) { /* if 判断为 1 */
        if (nd->last.name[nd->last.len]) /* if 判断为 0 */
            ...
        /* we _can_ be in RCU mode here */
        error = lookup_fast(nd, &path, &inode, &seq);
        if (likely(error > 0)) /* if 判断为 1 */
            goto finish_lookup; /* 此处进行跳转 */
        ...
    } else {
        ...
    }
   ...
finish_lookup:
    error = step_into(nd, &path, 0, inode, seq);
    if (unlikely(error)) /* if 判断为 0 */
        ...
finish_open:
    /* Why this, you ask?  _Now_ we might have grown LOOKUP_JUMPED... */
    error = complete_walk(nd);
    if (error) /* if 判断为 0 */
        ...
    audit_inode(nd->name, nd->path.dentry, 0);
    error = -EISDIR;
    if ((open_flag & O_CREAT) && d_is_dir(nd->path.dentry)) /* if 判断为 0 */
        ...
    error = -ENOTDIR;
    if ((nd->flags & LOOKUP_DIRECTORY) && !d_can_lookup(nd->path.dentry)) /* if 判断为 0 */
        ...
    if (!d_is_reg(nd->path.dentry)) /* if 判断为 0 */
        ...

    if (will_truncate) { /* if 判断为 0 */
        ...
    }
finish_open_created:
    error = may_open(&nd->path, acc_mode, open_flag);
    if (error) /* if 判断为 0 */
        ...
    BUG_ON(*opened & FILE_OPENED); /* once it's opened, it's opened */
    error = vfs_open(&nd->path, file, current_cred()); /* 真正执行文件打开的操作 */
    if (error)/* if 判断为 0 */  
        ...   
    *opened |= FILE_OPENED;  
opened:  
    error = open_check_o_direct(file); /* 这里 error == 0 */  
    if (!error) /* if 判断为 1 */  
        error = ima_file_check(file, op->acc_mode, *opened); /* 这里 error == 0 */  
    if (!error && will_truncate) /* if 判断为 0 */  
        ...  
out:  
    if (unlikely(error) && (*opened & FILE_OPENED))/* if 判断为 0 */  
        ...  
    if (unlikely(error > 0)) {/* if 判断为 0 */  
        ... 
    }  
    if (got_write)/* if 判断为 0 */  
        ...  
    return error;  
}

我们看到在do_last()函数调用complete_walk()函数之前,分别调用了lookup_fast()和step_into()函数,相当于调用了walk_component()函数,找到文件路径最后一部分对应的dentry,并存入到 struct nameidata nd 中。函数定义:

static int complete_walk(struct nameidata *nd)
{
    struct dentry *dentry = nd->path.dentry;
    int status;

    if (nd->flags & LOOKUP_RCU) {/* if 判断为 1 */
        if (!(nd->flags & LOOKUP_ROOT)) /* if 判断为 1 */
            nd->root.mnt = NULL;
        if (unlikely(unlazy_walk(nd))) /* if 判断为 0 */
            ...
    }

    if (likely(!(nd->flags & LOOKUP_JUMPED))) /* if 判断为 1 */
        return 0; /* 函数返回 */
    ...
}

其中调用了unlazy_walk()这个函数。该函数定义:

static int unlazy_walk(struct nameidata *nd)
{
    struct dentry *parent = nd->path.dentry;

    BUG_ON(!(nd->flags & LOOKUP_RCU));

    nd->flags &= ~LOOKUP_RCU;
    if (unlikely(!legitimize_links(nd))) /* if 判断为 0 */
        ...
    if (unlikely(!legitimize_path(nd, &nd->path, nd->seq))) /* if 判断为 0 */
        goto out1;
    if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) { /* if 判断为 0 */
        ...
    }
    rcu_read_unlock();
    BUG_ON(nd->inode != parent->d_inode);
    return 0;
    ...
}

这里又有两个函数需要分析:legitimize_links()legitimize_path()。其中legitimize_links()是用来处理链接的,本例中不涉及链接,因此该函数相当与空函数:

static bool legitimize_links(struct nameidata *nd)
{
    int i;
    for (i = 0; i < nd->depth; i++) { /* 这里 nd->depth = 0, 循环不执行 */
        ...
    }
    return true;
}

下面来分析legitimize_path()函数。定义如下:

static bool legitimize_path(struct nameidata *nd,
                struct path *path, unsigned seq)
{
    int res = __legitimize_mnt(path->mnt, nd->m_seq);
    if (unlikely(res)) {/* if 判断为 0 */
        ...
    }
    if (unlikely(!lockref_get_not_dead(&path->dentry->d_lockref))) { /* if 判断为 0 */
        ...
    }
    return !read_seqcount_retry(&path->dentry->d_seq, seq); /* return 1 */
}

这里我们需要分析 __legitimize_mnt()函数。定义如下:

int __legitimize_mnt(struct vfsmount *bastard, unsigned seq)
{
    struct mount *mnt;
    if (read_seqretry(&mount_lock, seq))/* if 判断为 0 */
        ...
    if (bastard == NULL)/* if 判断为 0 */
        ...
    mnt = real_mount(bastard);
    mnt_add_count(mnt, 1);
    if (likely(!read_seqretry(&mount_lock, seq))) /* if 判断为 1 */
        return 0; /* 函数返回 */
    ...
}

其中real_mount()函数较为简单,是将 struct vfsmount *mnt 转化为 struct mount *mount (前者是后者的一个成员变量,通过宏container_of() 实现)。mnt_add_count()定义如下:

static inline void mnt_add_count(struct mount *mnt, int n)
{
#ifdef CONFIG_SMP
    this_cpu_add(mnt->mnt_pcp->mnt_count, n);/*略去多核cpu同步等问题,等价与 mnt->mnt_pcp->mnt_count += n */
#else
    ...
#endif
}

至此legitimize_path()函数分析完毕,好像就做了一件事:container_of(nd->path->mnt)->mnt_pcp->mnt_count++; 看起来也是关于同步问题处理相关的,就不深入分析了。分析到此,unlazy_walk()就做了一点事情:

nd->flags &= ~LOOKUP_RCU;
container_of(nd->path->mnt)->mnt_pcp->mnt_count++;
那么,整个 complete_walk()函数相当于:
static int complete_walk(struct nameidata *nd)
{
    nd->root.mnt = NULL;
    nd->flags &= ~LOOKUP_RCU;
    container_of(nd->path->mnt,struct mount, mnt)->mnt_pcp->mnt_count++;
    return 0;
}
complete_walk()执行完毕之后则进入 audit_inode()函数,这个函数好像什么都没做,而且这个函数的定义依赖于AUDIT相关的内核配置,在某些内核配置下,这个函数的定义就是一个空函数。在这里我们不进行深入分析该函数。

下面执行may_open()函数,该函数主要用来检查相应打开权限,这里我们暂不分析,简单认为权限检查没有问题。下面则进入vfs_open()函数。这个函数非常重要,可以说真正的“打开”操作是在这里进行的,前面所有的操作都是为了“找到”这个文件。这个函数的定义如下:

int vfs_open(const struct path *path, struct file *file,
         const struct cred *cred)
{
    struct dentry *dentry = d_real(path->dentry, NULL, file->f_flags, 0);
    if (IS_ERR(dentry))/* if 判断为 0 */
        ...
    file->f_path = *path;
    return do_dentry_open(file, d_backing_inode(dentry), NULL, cred);
}

其中调用do_dentry_open()函数完成文件打开的操作,在该函数中文件对象 struct file *file 的大部分成员变量被填充。该函数定义等价与如下:

static int do_dentry_open(struct file *f,
              struct inode *inode,
              int (*open)(struct inode *, struct file *),
              const struct cred *cred)
{
    static const struct file_operations empty_fops = {};
    int error;

    f->f_mode = OPEN_FMODE(f->f_flags) | FMODE_LSEEK |
                FMODE_PREAD | FMODE_PWRITE;

    path_get(&f->f_path);
    f->f_inode = inode;
    f->f_mapping = inode->i_mapping;

    /* Ensure that we skip any errors that predate opening of the file */
    f->f_wb_err = filemap_sample_wb_err(f->f_mapping);

    if (unlikely(f->f_flags & O_PATH)) {/* if 判断为 0 */
        ...
    }

    if (f->f_mode & FMODE_WRITE && !special_file(inode->i_mode)) {/* if 判断为 0 */
        ...
    }

    /* POSIX.1-2008/SUSv4 Section XSI 2.9.7 */
    if (S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)) /* if 判断为 1 */
        f->f_mode |= FMODE_ATOMIC_POS;

    f->f_op = fops_get(inode->i_fop);
    if (unlikely(WARN_ON(!f->f_op))) { /* if 判断为 0 */
        ...
    }

    error = security_file_open(f, cred);
    if (error) /* if 判断为 0 */
        ...

    error = break_lease(locks_inode(f), f->f_flags);
    if (error) /* if 判断为 0 */
        ...

    if (!open) /* if 判断为 1 */
        open = f->f_op->open;
    if (open) {/* if 判断为 1 */
        error = open(inode, f);
        if (error)/* if 判断为 0 */
            ...
    }
    if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
        i_readcount_inc(inode);
    if ((f->f_mode & FMODE_READ) &&
         likely(f->f_op->read || f->f_op->read_iter))
        f->f_mode |= FMODE_CAN_READ;
    if ((f->f_mode & FMODE_WRITE) &&
         likely(f->f_op->write || f->f_op->write_iter))
        f->f_mode |= FMODE_CAN_WRITE;

    f->f_write_hint = WRITE_LIFE_NOT_SET;
    f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);

    file_ra_state_init(&f->f_ra, f->f_mapping->host->i_mapping);

    return 0;
    ...
}

这个函数貌似很长,但逐行分析起来,每行的意思也不难理解。填充了struct file *file 的各个成员变量,并调用驱动程序中的 "open"函数。从上面的分析可以看出,驱动程序的"open()"函数是保存在 struct inode *inode 里面的,而inode又是保存在struct dentry 里面的。通过读取相应的地址信息,并到“/proc/kallsys”里面进行查找,发现本例中调用的驱动程序的“open”函数是“ext4_file_open()”。ext4_file_open()函数这里我们不再进行分析。do_dentry_open()函数在这里不再进行深入分析其中的每个函数调用。do_last()函数返回之后,回到path_openat()函数,由于do_last()函数返回值为0,因此在path_openat()函数中退出while()循环,进入到下一条语句:terminate_walk()。该函数定义如下:


static void terminate_walk(struct nameidata *nd)
{
    drop_links(nd); /
    if (!(nd->flags & LOOKUP_RCU)) {/* if 判断为 1 */
        int i;
        path_put(&nd->path);
        for (i = 0; i < nd->depth; i++) /* nd->depth == 0, 空循环 */
            ...
        if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) { /* if 判断为 0 */
            ...
        }
    } else {
        ...
    }
    nd->depth = 0;
}

在这个函数中,由于drop_links()是处理链接的情况,本例不涉及,该函数为空函数。其中,path_put()是进程同步相关操作,这里不进行分析。而 nd->depth 本来就等于零,因此这里 terminate_walk() 除了进程同步操作之外,没有做其他工作。terminate_walk()函数结束之后,整个path_openat()函数也就返回了,回到do_filp_open()函数。do_filp_open()函数在path_openat()函数执行完毕之后也就返回了。现在回到最顶层函数:do_sys_open()函数。下面将执行fsnotify_open()fd_install()函数。fsnotify_open()函数在这里暂不分析,以后再说,而fsnotify_open()函数除去各种同步控制之后,就相当于:

fd_install(fd, f); /*等价与 current->files->fdt->fd[fd] = f */

至此,整个do_sys_open()函数分析完毕。这里只以最简单的实例进行了初步分析,并发性问题的同步控制和权限检查以及错误处理都没有考虑。以后将详细分析。

walk_component()函数

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页