Linux内核源码分析 - open 续

接上篇,我们继续看下vfs_open方法。

// fs/open.c
int vfs_open(const struct path *path, struct file *file)
{
        file->f_path = *path;
        return do_dentry_open(file, d_backing_inode(path->dentry), NULL);
}

该方法又调用了do_dentry_open方法。

// fs/open.c
static int do_dentry_open(struct file *f,
                          struct inode *inode,
                          int (*open)(struct inode *, struct file *))
{
        ...
        f->f_inode = inode;
        ...
        f->f_op = fops_get(inode->i_fop);
        ...
        if (!open)
                open = f->f_op->open;
        if (open) {
                error = open(inode, f);
                ...
        }
        f->f_mode |= FMODE_OPENED;
        ...
        return 0;
        ...
}

该方法中,设置f->f_op的值为inode->i_fop,由于参数open为null,所以open也被重新赋值为f->f_op->open,即 inode->i_fop->open,之后再调用该open方法,继续执行open逻辑。

那inode->i_fop的值又是在哪里设置的呢?

// fs/inode.c
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
        inode->i_mode = mode;
        if (S_ISCHR(mode)) {
                inode->i_fop = &def_chr_fops;
                inode->i_rdev = rdev;
        } else if (S_ISBLK(mode)) {
                inode->i_fop = &def_blk_fops;
                inode->i_rdev = rdev;
        } else if (S_ISFIFO(mode))
                inode->i_fop = &pipefifo_fops;
        else if (S_ISSOCK(mode))
                ;       /* leave it no_open_fops */
        else
                printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
                                  " inode %s:%lu\n", mode, inode->i_sb->s_id,
                                  inode->i_ino);
}
EXPORT_SYMBOL(init_special_inode);

由上可见,是在init_special_inode方法里设置的。

由于/dev/tty是character device,所以i_fop的值为def_chr_fops。

// fs/char_dev.c
const struct file_operations def_chr_fops = {
        .open = chrdev_open,
        .llseek = noop_llseek,
};

它对应的open方法为chrdev_open。

// fs/char_dev.c
static int chrdev_open(struct inode *inode, struct file *filp)
{
        const struct file_operations *fops;
        struct cdev *p;
        ...
        p = inode->i_cdev;
        if (!p) {
                struct kobject *kobj;
                ...
                kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
                ...
                new = container_of(kobj, struct cdev, kobj);
                ...
                /* Check i_cdev again in case somebody beat us to it while
                   we dropped the lock. */
                p = inode->i_cdev;
                if (!p) {
                        inode->i_cdev = p = new;
                        ...
                } ...
        }
        ...
        fops = fops_get(p->ops);
        ...
        replace_fops(filp, fops);
        if (filp->f_op->open) {
                ret = filp->f_op->open(inode, filp);
                ...
        }

        return 0;
        ...
}

该方法先调用kobj_lookup方法,在cdev_map中找对应的cdev,找到之后把结果赋值给p。之后获取p->ops的值,赋值给fops,再之后替换filp->f_op字段的值为fops,最后检查filp->f_op的值中是否包含open方法,如果有,则调用该方法继续执行open逻辑。

我们先看下/dev/tty对应的cdev是在哪把自己注册到cdev_map里的。

// drivers/tty/tty_io.c
int __init tty_init(void)
{
        ...
        cdev_init(&tty_cdev, &tty_fops);
        if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
            register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
                panic("Couldn't register /dev/tty driver\n");
        ...
        return 0;
}

该方法先调用cdev_init,初始化tty_cdev,并将其ops字段设置为tty_fops,然后调用cdev_add、register_chrdev_region方法,注册这个cdev到cdev_map。

由上可知,/dev/tty对应的cdev就是tty_cdev,而cdev->ops就是tty_fops。、

// drivers/tty/tty_io.c
static const struct file_operations tty_fops = {
        .llseek         = no_llseek,
        .read           = tty_read,
        .write          = tty_write,
        .poll           = tty_poll,
        .unlocked_ioctl = tty_ioctl,
        .compat_ioctl   = tty_compat_ioctl,
        .open           = tty_open,
        .release        = tty_release,
        .fasync         = tty_fasync,
        .show_fdinfo    = tty_show_fdinfo,
};

由上可见,cdev->ops->open对应的方法就是tty_open,即/dev/tty的最终open逻辑。

由于此部分逻辑和open系统调用关联不是很大,在此略过。

至此,整个open逻辑就已分析完毕。

完。

原文发布于微信公众号 - Linux内核及JVM底层相关技术研究(ytcode)

原文发表时间:2019-05-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券