前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux内核源码分析 - open

Linux内核源码分析 - open

作者头像
KINGYT
修改2019-06-11 15:07:49
6.9K0
修改2019-06-11 15:07:49
举报

在linux下,假设我们想打开文件/dev/tty,我们可以使用系统调用open,比如:

代码语言:javascript
复制
int fd = open("/dev/tty", O_RDWR, 0);

本文将从源码角度看下,在linux内核中,open方法是如何打开文件的。

首先看下入口函数。

代码语言:javascript
复制
// fs/open.c
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
        ...
        return do_sys_open(AT_FDCWD, filename, flags, mode);
}

该方法调用了do_sys_open方法

代码语言:javascript
复制
// fs/open.c
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
        struct open_flags op;
        int fd = build_open_flags(flags, mode, &op);
        struct filename *tmp;
        ...
        tmp = getname(filename);
        ...
        fd = get_unused_fd_flags(flags);
        if (fd >= 0) {
                struct file *f = do_filp_open(dfd, tmp, &op);
                if (IS_ERR(f)) {
                        ...
                } else {
                        ...
                        fd_install(fd, f);
                }
        }
        ...
        return fd;
}

该方法大致操作为:

1. 调用build_open_flags方法,初始化struct open_flags实例op。

代码语言:javascript
复制
// fs/internal.h
struct open_flags {
        int open_flag;
        umode_t mode;
        int acc_mode;
        int intent;
        int lookup_flags;
};
代码语言:javascript
复制
// fs/open.c
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);
        ...
        if (flags & (O_CREAT | __O_TMPFILE))
                op->mode = (mode & S_IALLUGO) | S_IFREG;
        else
                op->mode = 0;
        ...
        op->open_flag = flags;
        ...
        op->acc_mode = acc_mode;

        op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;
        ...
        if (flags & O_DIRECTORY)
                lookup_flags |= LOOKUP_DIRECTORY;
        ...
        op->lookup_flags = lookup_flags;
        return 0;
}

2. 调用getname方法,分配并初始化struct filename实例tmp。

代码语言:javascript
复制
// include/linux/fs.h
struct filename {
        const char              *name;  /* pointer to actual string */
        const __user char       *uptr;  /* original userland pointer */
        int                     refcnt;
        struct audit_names      *aname;
        const char              iname[];
};
代码语言:javascript
复制
// fs/namei.c
struct filename *
getname_flags(const char __user *filename, int flags, int *empty)
{
        struct filename *result;
        char *kname;
        ...
        result = __getname(); // 分配内存
        ...
        kname = (char *)result->iname;
        result->name = kname;

        len = strncpy_from_user(kname, filename, EMBEDDED_NAME_MAX);
        ...
        result->refcnt = 1;
        ...
        result->uptr = filename;
        ...
        return result;
}

struct filename *
getname(const char __user * filename)
{
        return getname_flags(filename, 0, NULL);
}

3. 调用get_unused_fd_flags方法获取一个未被使用的文件描述符fd。

4. 调用do_filp_open方法,继续执行open操作,并将返回值赋值给类型为struct file的实例指针f。

5. 如果do_filp_open成功,则调用fd_install方法,建立从fd到struct file的对应关系。

6. 返回fd给用户。

我们再继续看下do_filp_open方法。

代码语言:javascript
复制
// fs/namei.c
struct file *do_filp_open(int dfd, struct filename *pathname,
                const struct open_flags *op)
{
        struct nameidata nd;
        int flags = op->lookup_flags;
        struct file *filp;

        set_nameidata(&nd, dfd, pathname);
        filp = path_openat(&nd, op, flags | LOOKUP_RCU);
        ...
        return filp;
}

该方法先调用set_nameidata方法,初始化struct nameidata类型实例nd。

代码语言:javascript
复制
// fs/namei.c
struct nameidata {
        struct path     path;
        struct qstr     last;
        struct path     root;
        struct inode    *inode; /* path.dentry.d_inode */
        unsigned int    flags;
        unsigned        seq, m_seq;
        int             last_type;
        unsigned        depth;
        int             total_link_count;
        struct saved {
                struct path link;
                struct delayed_call done;
                const char *name;
                unsigned seq;
        } *stack, internal[EMBEDDED_LEVELS];
        struct filename *name;
        struct nameidata *saved;
        struct inode    *link_inode;
        unsigned        root_seq;
        int             dfd;
} __randomize_layout;

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;
}

再调用path_openat方法继续执行open操作。

代码语言:javascript
复制
// fs/namei.c
static struct file *path_openat(struct nameidata *nd,
                        const struct open_flags *op, unsigned flags)
{
        struct file *file;
        int error;

        file = alloc_empty_file(op->open_flag, current_cred());
        ...
        if (unlikely(file->f_flags & __O_TMPFILE)) {
                ...
        } else {
                const char *s = path_init(nd, flags);
                while (!(error = link_path_walk(s, nd)) &&
                        (error = do_last(nd, file, op)) > 0) {
                        ...
                }
                ...
        }
        if (likely(!error)) {
                if (likely(file->f_mode & FMODE_OPENED))
                        return file;
                ...
        }
        ...
        return ERR_PTR(error);
}

该方法中,先调用alloc_empty_file方法,分配一个空的struct file实例,再调用path_init、link_path_walk、do_last等方法执行后续的open操作,如果都成功了,返回file给上层。

先看下path_init方法。

代码语言:javascript
复制
// fs/namei.c
static const char *path_init(struct nameidata *nd, unsigned flags)
{
        const char *s = nd->name->name;
        ...
        nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT;
        nd->depth = 0;
        ...
        nd->root.mnt = NULL;
        nd->path.mnt = NULL;
        nd->path.dentry = NULL;
        ...
        if (*s == '/') {
                set_root(nd);
                if (likely(!nd_jump_root(nd)))
                        return s;
                return ERR_PTR(-ECHILD);
        }
        ...
}

假设我们要open的路径为/dev/tty,该方法在进行一些初始化赋值之后,会调用set_root方法,设置nd->root字段为fs->root,即根目录

代码语言:javascript
复制
// fs/namei.c
static void set_root(struct nameidata *nd)
{
        struct fs_struct *fs = current->fs;

        if (nd->flags & LOOKUP_RCU) {
                ...
                do {
                        ...
                        nd->root = fs->root;
                        ...
                } while (read_seqcount_retry(&fs->seq, seq));
        } else {
                ...
        }
}

再调用nd_jump_root方法,设置nd->path字段为nd->root,nd->inode字段为nd->root->d_inode。

代码语言:javascript
复制
// fs/namei.c
static int nd_jump_root(struct nameidata *nd)
{
        if (nd->flags & LOOKUP_RCU) {
                struct dentry *d;
                nd->path = nd->root;
                d = nd->path.dentry;
                nd->inode = d->d_inode;
                ...
        } else {
                ...
        }
        nd->flags |= LOOKUP_JUMPED;
        return 0;
}

如果上述方法都没有问题,最后返回s给上层,至此,path_init方法结束。

由上可见,path_init方法主要是用来初始化struct nameidata实例中的path、root、inode等字段。

我们再来看下link_path_walk方法。

代码语言:javascript
复制
// fs/namei.c
static int link_path_walk(const char *name, struct nameidata *nd)
{
        ...  
        while (*name=='/')
                name++;
        ...
        /* At this point we know we have a real path component. */
        for(;;) {
                u64 hash_len;
                int type;
                ...

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

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

                name += hashlen_len(hash_len);
                if (!*name)
                        goto OK;
                do {
                        name++;
                } while (unlikely(*name == '/'));
                if (unlikely(!*name)) {
OK:
                        /* pathname body, done */
                        
                        if (!nd->depth)
                                return 0;
                        ...
                } else {
                        /* not the last component */
                        err = walk_component(nd, WALK_FOLLOW | WALK_MORE);
                }
                ...
        }
}                        

该方法的大致操作为:

1. 跳过开始的‘/’字符。

2. 调用hash_name方法,获取下一个path component的hash和len,并复制给hash_len。

path component就是以‘/’字符分割的路径的各个部分。

3. 将该path component的信息赋值给nd->last字段。

4. 修改name的值,使其指向path的下一个component。

5. 如果下一个component为空,则goto到OK这个label,执行一些操作之后,最后return 0给上层。

6. 如果下一个component不为空,则执行walk_component方法,找到nd->last字段指向的component对应的dentry、inode等信息,并更新nd->path、nd->inode等字段,使其指向新的路径。

以open /dev/tty为例,该方法最终的结果是,更新struct nameidata实例指针nd中的path、inode字段,使其指向路径/dev/,更新nd中的last值,使其为tty。

最后,再来看下do_last方法。

代码语言:javascript
复制
// fs/namei.c
static int do_last(struct nameidata *nd,
                   struct file *file, const struct open_flags *op)
{
        ...
        if (!(open_flag & O_CREAT)) {
                ...
                error = lookup_fast(nd, &path, &inode, &seq);
                if (likely(error > 0))
                        goto finish_lookup;
                ...
        } else {
                ...
        }
        ...
finish_lookup:
        error = step_into(nd, &path, 0, inode, seq);
        ...
        error = vfs_open(&nd->path, file);
        ...
        return error;
}   

该方法中,先调用lookup_fast,找路径中的最后一个component,如果成功,就会跳到finish_lookup对应的label,然后执行step_into方法,更新nd中的path、inode等信息,使其指向目标路径。

之后,调用vfs_open方法,继续执行open操作。

最后,返回error给上层,如果成功,error为0。

限于篇幅原因,本文暂且分析到这,下一篇继续分析vfs_open方法。

完。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-05-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档