应用程序访问设备驱动程序的原理

/************************************************************************************

*本文为个人学习记录,如有错误,欢迎指正。

*本文参考资料: 

*        http://www.169it.com/tech-qa-linux/article-5682294992603241339.html

*        https://www.cnblogs.com/yanghong-hnu/p/5699528.html

*        https://www.cnblogs.com/wanghetao/archive/2012/05/28/2521675.html

*        http://www.cnblogs.com/xiaojiang1025/p/6196198.html

*        https://www.cnblogs.com/chen-farsight/p/6177870.html

*        http://www.cnblogs.com/xiaojiang1025/p/6363626.html

************************************************************************************/

1.相关数据结构

1.1 struct inode

VFS inode包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。它是linux管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。

在Linux内核中,当创建一个文件时,就会在相应的文件系统创建一个inode与之对应,文件实体和文件的inode是一 一对应的,创建好一个inode会存在存储器中,第一次open就会将inode在内存中有一个备份,同一个文件被多次打开并不会产生多个inode,当所有被打开的文件都被close之后,inode在内存中的实例才会被释放。

当创建一个设备文件(mknod或udev)时,也会在文件系统中创建一个inode,该inode用来存储关于这个文件的静态信息,其中包括该设备文件对应的设备号、文件路径以及对应的驱动对象等。

inode结构体中包含了设备文件的大量信息,着重关心以下结构体成员即可:

(1)dev_t i_rdev:表示设备文件对应的字符设备的设备号。

(2)struct cdev *i_cdev:指向字符设备对应的cdev结构体。

(3)const struct file_operations *i_fop:文件的操作方法集,创建设备文件的时候i_fops填充的是def_chr_fops,blk_blk_fops,def_fifo_fops,bad_sock_fops之一。

//linux/fs.h
struct inode {
        struct hlist_node       i_hash;              /* 哈希表 */
        struct list_head        i_list;              /* 索引节点链表 */
        struct list_head        i_dentry;            /* 目录项链表 */
        unsigned long           i_ino;               /* 节点号 */
        atomic_t                i_count;             /* 引用记数 */
        umode_t                 i_mode;              /* 访问权限控制 */
        unsigned int            i_nlink;             /* 硬链接数 */
        uid_t                   i_uid;               /* 使用者id */
        gid_t                   i_gid;               /* 使用者id组 */
        kdev_t                  i_rdev;              /* 实设备标识符 */
        loff_t                  i_size;              /* 以字节为单位的文件大小 */
        struct timespec         i_atime;             /* 最后访问时间 */
        struct timespec         i_mtime;             /* 最后修改(modify)时间 */
        struct timespec         i_ctime;             /* 最后改变(change)时间 */
        unsigned int            i_blkbits;           /* 以位为单位的块大小 */
        unsigned long           i_blksize;           /* 以字节为单位的块大小 */
        unsigned long           i_version;           /* 版本号 */
        unsigned long           i_blocks;            /* 文件的块数 */
        unsigned short          i_bytes;             /* 使用的字节数 */
        spinlock_t              i_lock;              /* 自旋锁 */
        struct rw_semaphore     i_alloc_sem;         /* 索引节点信号量 */
        struct inode_operations *i_op;               /* 索引节点操作表 */
        struct file_operations  *i_fop;              /* 默认的索引节点操作 */
        struct super_block      *i_sb;               /* 相关的超级块 */
        struct file_lock        *i_flock;            /* 文件锁链表 */
        struct address_space    *i_mapping;          /* 相关的地址映射 */
        struct address_space    i_data;              /* 设备地址映射 */
        struct dquot            *i_dquot[MAXQUOTAS]; /* 节点的磁盘限额 */
        struct list_head        i_devices;           /* 块设备链表 */
        struct pipe_inode_info  *i_pipe;             /* 管道信息 */
        struct block_device     *i_bdev;             /* 块设备 */
        struct cdev *i_cdev;                             /* 字符设备 */
        unsigned long           i_dnotify_mask;      /* 目录通知掩码 */
        struct dnotify_struct   *i_dnotify;          /* 目录通知 */
        unsigned long           i_state;             /* 状态标志 */
        unsigned long           dirtied_when;        /* 首次修改时间 */
        unsigned int            i_flags;             /* 文件系统标志 */
        unsigned char           i_sock;              /* 可能是个套接字吧 */
        atomic_t                i_writecount;        /* 写者记数 */
        void                    *i_security;         /* 安全模块 */
        __u32                   i_generation;        /* 索引节点版本号 */
        union {
                void            *generic_ip;         /* 文件特殊信息 */
        } u;
};
struct inode

1.2 struct file

 Linux内核中,使用 file结构体描述一个已经打开的文件(设备对应于设备文件),系统中的每个打开的文件在内核空间都有一个相应的struct file结构体,它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数,直至文件被关闭。如果文件被关闭,内核就会释放相应的数据结构。

Linux中同一个文件可被多个进程打开,该文件每被打开一次,内核就会在该进程中创建一个struct file来描述该文件。由此可知,一个文件在内核中可能对应多个struct file,但是该文件只有唯一一个struct inode与之对应。

struct file {
union {
    struct list_head fu_list;       //文件对象链表指针linux/include/linux/list.h
    struct rcu_head fu_rcuhead;     //RCU(Read-Copy Update)是Linux 2.6内核中新的锁机制
} f_u;
struct path f_path;                 //包含dentry和mnt两个成员,用于确定文件路径
#define f_dentry  f_path.dentry     //f_path的成员之一,当前文件的dentry结构
#define f_vfsmnt  f_path.mnt        //表示当前文件所在文件系统的挂载根目录
const struct file_operations *f_op; //与该文件相关联的操作函数
atomic_t  f_count;                  //文件的引用计数(有多少进程打开该文件)
unsigned int  f_flags;              //对应于open时指定的flag
mode_t  f_mode;                     //读写模式:open的mod_t mode参数
off_t  f_pos;                       //该文件在当前进程中的文件偏移量
struct fown_struct f_owner;         //该结构的作用是通过信号进行I/O时间通知的数据。
unsigned int  f_uid, f_gid;         //文件所有者id,所有者组id
struct file_ra_state f_ra;          //在linux/include/linux/fs.h中定义,文件预读相关
unsigned long f_version;
#ifdef CONFIG_SECURITY
void  *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
};
struct file

  着重关注以下结构体成员:

(1)struct inode *f_inode:指向该文件对应的inode。

(2)const struct file_operations *f_op:驱动提供的file_operations对象,这个对象应该在第一次open()的时候被填充(调用char_open实现),直至该文件被close。

1.3 字符设备管理框架

详见Linux字符设备:字符设备管理框架

2. 具体访问流程

(1)当一个字符设备文件被创建的时候,内核会构造相应的inode,并对其进行初始化操作。

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

//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 = &def_fifo_fops;
    else if (S_ISSOCK(mode))
        inode->i_fop = &bad_sock_fops;
    else
        printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
                  " inode %s:%lu
", mode, inode->i_sb->s_id,
                  inode->i_ino);
}

(2)chrdev_open()
--359-->尝试将inode->i_cdev(一个cdev结构指针)保存在局部变量p中;
--360-->如果p为空,即inode->i_cdev为空;
--364-->我们就根据inode->i_rdev(设备号)通过kobj_lookup()搜索cdev_map,并返回与之对应kobj
--367-->由于kobject是cdev的父类,我们根据container_of很容易找到相应的cdev结构并将其保存在inode->i_cdev中;
--374-->找到了cdev,我们就可以将inode->devices挂接到inode->i_cdev的管理链表中,这样下次就不用重新搜索;
--378-->直接cdev_get()即可;
--386-->找到了我们的cdev结构,我们就可以将其中的操作方法集inode->i_cdev->ops传递给filp->f_ops(386-390);
--392-->这样,我们就可以回调我们的设备打开字符设备fops中的char_opena函数。如果我们没有实现自己的open接口,就什么都不做,也不是错。

351 static int chrdev_open(struct inode *inode, struct file *filp)
352 {
353         const struct file_operations *fops;
354         struct cdev *p;
355         struct cdev *new = NULL;
356         int ret = 0;
            ...
359         p = inode->i_cdev;
360         if (!p) {
361                 struct kobject *kobj;
362                 int idx;
                    ...
364                 kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
                    ...
367                 new = container_of(kobj, struct cdev, kobj);
369                 /* Check i_cdev again in case somebody beat us to it while
370                    we dropped the lock. */
371                 p = inode->i_cdev;
372                 if (!p) {
373                         inode->i_cdev = p = new;
374                         list_add(&inode->i_devices, &p->list);
375                         new = NULL;
376                 } else if (!cdev_get(p))
377                         ret = -ENXIO;
378         } else if (!cdev_get(p))
379                 ret = -ENXIO;
            ...
386         fops = fops_get(p->ops);
            ...
390         replace_fops(filp, fops);
391         if (filp->f_op->open) {
392                 ret = filp->f_op->open(inode, filp);
                    ...
395         }
396 
397         return 0;
398 
399  out_cdev_put:
400         cdev_put(p);
401         return ret;
402 }
原文地址:https://www.cnblogs.com/linfeng-learning/p/9309833.html