/************************************************************************************
*本文为个人学习记录,如有错误,欢迎指正。
* 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; };
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; };
着重关注以下结构体成员:
(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 ", 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 }