应用程序访问设备驱动程序的原理
本文以字符设备为例,简述了应用程序访问设备驱动程序的基本原理,及其相关的数据结构struct inode、struct file、struct file_operations。。
/************************************************************************************
*本文为个人学习记录,如有错误,欢迎指正。
* 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 字符设备管理框架
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\n", 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 }