Linux内核中的open方法

news2025/2/1 11:45:15

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

int fd = open("/dev/tty", O_RDWR, 0);

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

首先看下入口函数。

// 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方法

// 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。

// fs/internal.h
struct open_flags {
        int open_flag;
        umode_t mode;
        int acc_mode;
        int intent;
        int lookup_flags;
};
// 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;
}
【文章福利】小编推荐自己的Linux内核技术交流群:【 865977150】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送一份价值 699的内核资料包(含视频教程、电子书、实战项目及代码)

资料直通车:最新Linux内核源码资料文档+视频资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

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

// 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[];
};
// 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方法。

// 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。

// 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操作。

// 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方法。

// 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,即根目录

// 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。

// 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方法。

// 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方法。

// 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方法。

// 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逻辑就已分析完毕。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/104399.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【opencv】centos下opencv的编译(带opencv_contrib扩展包)

目录1.安装cmake2.opencv安装文件准备可选安装3.进行编译4.进行安装5.使用测试编译步骤&#xff1a;1.安装cmake 安装wget: 应该已安装gcc工具套组&#xff1a; yum install sudo yum install -y gcc gcc-c make automake yum install -y wget wget https://cmake.org/files…

【树莓派不吃灰】兄弟连篇④ Shell编程

目录1、Shell基础1.1 脚本执行方式1.1.1 echo1.1.2 脚本执行1.2 Bash基本功能1.2.1 history1.2.2 命令补全1.2.3 命令别名1.2.4 常用快捷键1.2.5 输出输入重定向1.2.6 多命令顺序执行与管道符1.2.7 通配符和特殊符号1.3 Bash变量1.3.1 用户自定义变量1.3.2 环境变量1.3.3 位置变…

Hudi入门到实战

简介 Apache Hudi&#xff08;Hadoop Upserts Delete and Incremental&#xff09;是下一代流数据湖平台。Apache Hudi将核心仓库和数据库功能直接引入数据湖。Hudi提供了表、事务、高效的upserts/delete、高级索引、流摄取服务、数据集群/压缩优化和并发&#xff0c;同时保持…

用于医学诊断的菁染料ICG-Sulfo-OSu,活性基团修饰ICG

英文名&#xff1a;ICG-Sulfo-EG4-OSu 激发波长: 780nm; 发射波长: 800nm 分子量: 1777.36 溶剂:DMSO 凯新生物描述&#xff1a; (ICG)是一种用于医学诊断的菁染料。它用于测定心输出量、肝功能和肝脏血流量&#xff0c;以及用于眼科血管造影。它的峰值光谱吸收接近800纳米…

【课程整理】随机系统期末整理

文章目录1 概率论部分 (1-4)概率空间随机变量概率分布随机变量的函数仍然是随机变量条件期望2 随机过程 (5-7)随机过程Martingale停时马尔科夫链3 参数估计 (8-10)参数估计问题充分统计量贝叶斯估计非随机估计部分思维导图如下&#xff0c;私信发送html完整版 1 概率论部分 (1-…

3.path路径模块

目录 1 路径问题 2 直接给绝对路径 3 用 __dirname 表示绝对路径 4 path模块常用方法 4.1 路径拼接 path.join() 4.2 从路径字符串中解析出文件名 path.basename() 4.3 获取路径中的扩展名 path.extname() 1 路径问题 当我们使用 ./ 或是 ../ 表示相对路径的时…

小林Coding阅读笔记:操作系统篇之硬件结构,中断问题

前言 参考/导流&#xff1a; 小林coding - 2.6 什么是软中断&#xff1f;学习意义 学习CPU与外设如何去提升处理效率的设计思想&#xff0c;异步机制的理解与借鉴掌握相关的Linux命令&#xff0c;帮助问题排查 相关说明 该篇博文是个人阅读的重要梳理&#xff0c;仅做简单参…

【修饰性PEG供应商】mPEG-DBCO_DBCO mPEG_甲氧基聚乙二醇环辛炔

【产品描述】 西安凯新生物科技有限公司是国内业PEG供应商&#xff0c;可以提供不同分子量的PEG衍生物&#xff0c;小分子PEG的循环节可以做到1-36个&#xff0c;高分子PEG分子量从1000-40000不等&#xff0c;可以修饰的基团有&#xff1a;氨基类&#xff0c;NHBOC类&#xff0…

多层板PCB设计中电源平面相对地平面为什么要进行内缩

大家是否观察过&#xff0c;有一些人绘制的PCB&#xff0c;在GND层和电源层会进行一定程度的内缩设计&#xff0c;那么大家有没有想过为什么要内缩呢。需要搞清楚这个问题&#xff0c;我们需要来先了解一个知识点&#xff0c;那就是“20H”原则&#xff1a; 20H原则主要是为了减…

MySQL的数据结构

阅读目录MySQL 数据结构用 btree 做的为什么不用红黑树叉树呢&#xff1f;什么是 B-Tree&#xff08;B-树&#xff09;&#xff1f;什么是 BTree&#xff1f;BTree 相对于 B-Tree 的几点不同MySQL 数据结构用 btree 做的 为什么不用红黑树叉树呢&#xff1f; 不用红黑树是因为…

计算机3D数学基础 旋转的三种表示方法 学习笔记

旋转的三种表示方法&#xff1a;1、矩阵 2、欧拉角 3、四元数 矩阵的缺点&#xff0c;记录旋转角度要记录9个数 欧拉角的heading就是绕着y(绿色轴)轴30 &#xff0c;容易理解 三种旋转方式的优缺点&#xff1a; 矩阵&#xff1a;缺点&#xff1a;1、上手难&#xff0c;记…

Android常用布局总结之(LinearLayout、TableLayout、GridLayout、RelativeLayout)

一、LinearLayout 线性布局 LinearLayout 是一个视图组&#xff0c;用于使所有子视图在单个方向&#xff08;垂直或水平&#xff09;保持对齐。您可以使用 android:orientation 属性指定布局方向。 android:orientation&#xff0c;指定布局方向&#xff0c;vertical-竖向布局…

在线点餐网站

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 管理员&#xff1a; 1、管理门店介绍、联系我们 2、对公告类型、公告信息增删改查 3、对菜品类型、菜品信息增册改查 4…

【计算机考研408】2023考研408相关题目预测总结

目录数据结构选择1-时间复杂度选择2-栈或者队列选择3-二叉树、树、森林选择4-并查集选择5-红黑树选择6-图的概念选择7-图的应用选择8-B树&#xff08;B-树&#xff09;选择8-B树选择8-B树与B树的区别选择8-B树与B树的相关应用选择9-查找算法选择10、11-排序算法综合应用题41-算…

UG/NX二次开发Siemens官方NXOPEN实例解析—2.3 Selection_UIStyler

列文章目录 UG/NX二次开发Siemens官方NXOPEN实例解析—2.1 AssemblyViewer UG/NX二次开发Siemens官方NXOPEN实例解析—2.2 Selection UG/NX二次开发Siemens官方NXOPEN实例解析—2.3 Selection_UIStyler 列文章目录 文章目录 前言 一、知识点提取 二、案例需求分析 三、…

大数据期末总结

文章目录一、这学期分别接触了Linux&#xff0c;hadoop&#xff0c;hbase&#xff0c;hive1、Linux2、Hadoop3、hbase4、hive二、总结一、这学期分别接触了Linux&#xff0c;hadoop&#xff0c;hbase&#xff0c;hive 1、Linux Linux是一款安全性十分良好的操作系统。不仅有用…

前端监控与前端埋点方案

前端监控与前端埋点方案 https://blog.csdn.net/sinat_36521655/article/details/114650138 ​ 用户行为数据可以通过前端数据监控的方式获得&#xff0c;除此之外&#xff0c;前端还需要实现**性能监控和异常监控。**性能监控包括首屏加载时间、白屏时间、http请求时间和htt…

软件设计师

1.在项目初期的需求并不明确&#xff0c;需要不断同用户进行交流与沟通&#xff0c;分布获取功能要求&#xff0c;在这种情况要采用敏捷开发方法最适合&#xff0c;比如极限编程 2.设计模式包括&#xff1a;创建型&#xff0c;结构型&#xff0c;行为型三大类别。 创建型模式…

UnRaid添加镜像源加速应用安装的正确方法

文章目录0、前言1、寻找适合你网络的最优镜像源1.1、首先点击下图示红框处进入UnRaid的终端1.2、输入如下代码检测每一个镜像源的速度&#xff1a;2、更改镜像源方法2.1、修改Go文件方法2.2、用户自定义脚本方式2.2.1、安装User Scripts插件2.2.2、在User Scripts插件中添加更改…

【自动驾驶环境感知项目】——基于Paddle3D的点云障碍物检测

文章目录1. 自动驾驶实战&#xff1a;基于Paddle3D的点云障碍物检测1.1 环境信息1.2 准备点云数据1.3 安装Paddle3D1.4 模型训练1.5 模型评估1.6 模型导出1.7 模型部署效果1. 自动驾驶实战&#xff1a;基于Paddle3D的点云障碍物检测 项目地址——自动驾驶实战&#xff1a;基于P…