从用户态的open到内核驱动实现流程

时间:2021-04-19 04:45:26

问题来源:

在讲授Linux初级驱动的时候,我发现困惑很多同学的是不真正理解从应用层到我们自己所写的驱动层的调用过程,所以写此文章来大概描述。

首先我们知道,在我们目前的Linux系统中,我们大概共约300左右个系统调用,其中syscall_table.S列出了所有的系统调用表。

在本文件中记录了所有当前平台系统中所提供的系统调用表,其中第五项就包括:

.long sys_open /* 5 */

-----------------------------
        查看sys_open() 函数,我们看到里面所完成的工作为:
        1、查看打开的是否是大文件,如果是的话,置大文件标志位:O_LARGEFILE
        2、做do_sys_open()函数调用。
        3、检查2的调用返回值ret是否有效。
        -----------------------------

-----------------------------
        查看do_sys_open()函数所完成的工作为:
        调用getname() ,getname函数主要功能是在使用文件名之前将其拷贝到内核数据区,正常结束时返回内核分配的空间首地址,出错时返回错误代码。
        取得系统中可用的文件描述符fd。
        调用do_filp_open()函数,此函数使用了一个数据结构nameidata来描述与文件相关的文件操作。

struct nameidata {
                struct dentry                *dentry;        // 目录数据
                struct vfsmount        *mnt;        // 虚拟文件挂载点数据
                struct qstr        last;        // hash值
                unsigned int        flags;        // 文件操作标识
                int last_type;        // 类型
                unsigned                depth; 
                char                        *saved_names[MAX_NESTED_LINKS + 1];
                union {
                struct open_intent open;
                } intent; // 专用数据
        };
        -----------------------------

-----------------------------
        struct file *do_filp_open(const char * filename, int flags, int mode){
                int namei_flags, error;
                struct nameidata nd;
                namei_flags = flags;
                if ((namei_flags+1) & O_ACCMODE)
                        namei_flags++;        // 如果flags有O_WRONLY,则增加O_RDONLY

                error = open_namei(filename, namei_flags, mode, &nd);
                                // open_namei函数主要执行文件操作的inode部分的打开等操作。
                if (!error)
                        return nameidata_to_filp (nd, flags);
                                // 把文件的inod相关信息转换成文件结构。
                return ERR_PTR(error);        // 返回错误代码
        }
        -----------------------------

-----------------------------
        我们下面来看这个比较关键的函数:nameidata_to_filp():
        struct file *(struct nameidata *nd, int flags)
        821 {
        822        struct file *filp;
        823 
        824        /* Pick up the filp from the open intent */
        825        filp = nd->intent.open.file;
                                                // 把相关 file结构的指针赋予 filp。 
        826        /* Has the filesystem initialised the file for us? */
        827        if (filp->f_path.dentry == NULL)
        828                filp = __dentry_open(nd->dentry, nd->mnt, flags, filp, NULL);
                                                // ***** 关键函数 ***** //
        829        else
        830                path_release(nd);
        831        return filp;
        832 }
        -----------------------------

-----------------------------
        关键函数:__dentry_open():
        static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
                                                int flags, struct file *f,
                                                int (*open)(struct inode *, struct file *))
        {
                        ......
        695        f->f_pos = 0;
        696        f->f_op = fops_get(inode->i_fop);
                                                // 在这里进行赋值,f->f_op = &def_chr_fops,注意上文inode->i_fop中的赋值。
        697        file_move(f, &inode->i_sb->s_files);
        698 
        699        if (!open && f->f_op) 
                                                // 在调用__dentry_open时open赋值为空,所以!open为真。
        700                        open = f->f_op->open;
                                                // 在这里将open赋为chrdev_open。
        701        if (open) {
        702                        error = open(inode, f);
                                                // 这里调用chrdev_open, 参照下文。
        703                        if (error)
        704                                goto cleanup_all;
                        ......
        }
        -----------------------------

-----------------------------
        在函数chrdev_open中(/fs/char_dev.v):
        int chrdev_open(struct inode * inode, struct file * filp)
        { 
                ......
                kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); 
                                                // 执行kobj_lookup函数,在cdev_map里寻找相应的inode->i_rdev设备。
                                                // cdev_map是一个256个probe结构组成的数组,用于查找具有相应设备号的设备。
                                                // inode->i_rdev为设备号。

        new = container_of(kobj, struct cdev, kobj); 
                                                //从kobj的位置倒算出cdev的内存地址,获得包含相应kobj的cdev。

        inode->i_cdev = p = new; 
                                                // 到这里p已经为我们要的设备cdev了。

        filp->f_op = fops_get(p->ops); 
                                                / /拿到 cdev操作集。
                                                // 至此以后read,write操作都通过file->f_op直接与我们要的设备操作集挂钩了。
                ......
        }
        -----------------------------

到此,系统通过file->f_op 就与我们在设备驱动里面的定义的相关操作联系起来了,我们之前在写驱动实现的功能操作就被系统通过应用层的open 一步一步的调用到我们自己的open跟相关其他的操作了。

关注嵌入式、智能硬件、物联网开发敬请关注华清远见微信二维码。从用户态的open到内核驱动实现流程

【版权与免责声明】如发现内容存在版权问题,烦请提供相关信息发邮件至1912904432@qq.com,我们将及时沟通与处理。本站内容除非来源注明华清远见,否则均为网友转载,涉及言论、版权与本站无关。

其它好的链接:

Linux open系统调用流程 http://blog.csdn.net/scdxmoe/article/details/39155985

Linux 中open系统调用实现原理http://blog.chinaunix.net/uid-25968088-id-3426026.html

http://www.cnblogs.com/sky-heaven/p/5708407.html