Linux字符设备驱动结构(三)——file、inode结构体及
chardevs数组等相关知识解析
前⾯我们学习了字符设备结构体cdev 下⾯继续学习字符设备另外⼏个重要的数据结构。
先看下⾯这张图,这是Linux 中虚拟⽂件系统、⼀般的设备⽂件与设备驱动程序值间的函数调⽤关系;
上⾯这张图展现了⼀个应⽤程序调⽤字符设备驱动的过程, 在设备驱动程序的设计中,⼀般⽽⾔,会关⼼ file 和 inode 这两个结构体
⽤户空间使⽤ open() 函数打开⼀个字符设备 fd = open("/dev/hello",O_RDWR) , 这⼀函数会调⽤两个数据结构 struct inode{...}与struct file{...} ,⼆者均在虚拟⽂件系统VFS处,下⾯对两个数据结构进⾏解析:
⼀、file ⽂件结构体
在设备驱动中,这也是个⾮常重要的数据结构,必须要注意⼀点,这⾥的file与⽤户空间程序中的FILE指针是不同的,⽤户空间FILE是定义在C库中,从来不会出现在内核中。⽽struct file,却是内核当中的数据结构,因此,它也不会出现在⽤户层程序中。
file结构体指⽰⼀个已经打开的⽂件(设备对应于设备⽂件),其实系统中的每个打开的⽂件在内核空间都有⼀个相应的struct file结构体,它由内核在打开⽂件时创建,并传递给在⽂件上进⾏操作的任何函数,直⾄⽂件被关闭。如果⽂件被关闭,内核就会释放相应的数据结构。
在内核源码中,struct file要么表⽰为file,或者为filp(意指“file pointer”), 注意区分⼀点,file指的是struct file本⾝,⽽filp是指向这个结构体的指针。
河粉的做法
下⾯是⼏个重要成员:
a -- fmode_t f_mode;
此⽂件模式通过FMODE_READ, FMODE_WRITE识别了⽂件为可读的,可写的,或者是⼆者。在open或ioctl函数中可能需要检查此域以确认⽂件的读/写权限,你不必直接去检测读或写权限,因为在进⾏octl等操作时内核本⾝就需要对其权限进⾏检测。
b -- loff_t f_pos;
word对齐
当前读写⽂件的位置。为64位。如果想知道当前⽂件当前位置在哪,驱动可以读取这个值⽽不会改变其位置。对read,write来说,当其接收到⼀个loff_t型指针作为其最后⼀个参数时,他们的读写操作便作更新⽂件的位置,⽽不需要直接执⾏filp ->f_pos操作。⽽llek⽅法的⽬的就是⽤于改变⽂件的位置。
c -- unsigne
d int f_flags;
⽂件标志,如O_RDONLY, O_NONBLOCK以及O_SYNC。在驱动中还可以检查O_NONBLOCK标志查看是否有⾮阻塞请求。其它的标志较少使⽤。特别地注意的是,读写权限的检查是使⽤f_mode⽽不是f_flog。所有的标量定义在头⽂件中
d -- struct file_operations *f_op;
与⽂件相关的各种操作。当⽂件需要迅速进⾏各种操作时,内核分配这个指针作为它实现⽂件打开,读,写等功能的⼀部分。filp->f_op 其值从未被内核保存作为下次的引⽤,即你可以改变与⽂件相关的各种操作,这种⽅式效率⾮常⾼。快天
王祥求鲤file_operation 结构体解析如下:
e -- void *private_data;
在驱动调⽤open⽅法之前,open系统调⽤设置此指针为NULL值。你可以很⾃由的将其做为你⾃⼰需要的⼀些数据域或者不管它,如,你可以将其指向⼀个分配好的数据,但是你必须记得在file struct被内核销毁之前在relea⽅法中释放这些数据的内存空间。
private_data⽤于在系统调⽤期间保存各种状态信息是⾮常有⽤的。
⼆、 inode结构体
VFS inode 包含⽂件访问权限、属主、组、⼤⼩、⽣成时间、访问时间、最后修改时间等信息。它是Linux 管理⽂件系统的最基本单位,也是⽂件系统连接任何⼦⽬录、⽂件的桥梁。
内核使⽤inode结构体在内核内部表⽰⼀个⽂件。因此,它与表⽰⼀个已经打开的⽂件描述符的结构体(即file ⽂件结构)是不同的,我们可以使⽤多个file ⽂件结构表⽰同⼀个⽂件的多个⽂件描述符,但此时,所有的这些file⽂件结构全部都必须只能指向⼀个inode结构体。
inode结构体包含了⼀⼤堆⽂件相关的信息,但是就针对驱动代码来说,我们只要关⼼其中的两个域即可:
(1) dev_t i_rdev;
表⽰设备⽂件的结点,这个域实际上包含了设备号。
(2) struct cdev *i_cdev;
struct cdev是内核的⼀个内部结构,它是⽤来表⽰字符设备的,当inode结点指向⼀个字符设备⽂件时,此域为⼀个指向inode结构的指针。
下⾯是源代码:
1. struct inode {
2. struct hlist_node i_hash;
3. struct list_head i_list;
4. struct list_head i_sb_list;
5. struct list_head i_dentry;
6. unsigned long i_ino;
7. atomic_t i_count;
8. unsigned int i_nlink;
9. uid_t i_uid;//inode拥有者id
0. gid_t i_gid;//inode所属群组id
1. dev_t i_rdev;//若是设备⽂件,表⽰记录设备的设备号
2. u64 i_version;
3. loff_t i_size;//inode所代表⼤少
4. #ifdef __NEED_I_SIZE_ORDERED
5. qcount_t i_size_qcount;
6. #endif
7. struct timespec i_atime;//inode最近⼀次的存取时间
8. struct timespec i_mtime;//inode最近⼀次修改时间
9. struct timespec i_ctime;//inode的⽣成时间
0. unsigned int i_blkbits;
1. blkcnt_t i_blocks;
2. unsigned short i_bytes;
3. umode_t i_mode;
4. spinlock_t i_lock;
5. struct mutex i_mutex;
6. struct rw_maphore i_alloc_m;
7. const struct inode_operations *i_op;
8. const struct file_operations *i_fop;
9. struct super_block *i_sb;
0. struct file_lock *i_flock;
1. struct address_space *i_mapping;
2. struct address_space i_data;
3. #ifdef CONFIG_QUOTA
4. struct dquot *i_dquot[MAXQUOTAS];
江米粥
5. #endif
6. struct list_head i_devices;
7. union {
8. struct pipe_inode_info *i_pipe;
9. struct block_device *i_bdev;
0. struct cdev *i_cdev;//若是字符设备,对应的为cdev结构体
1. };
三、chardevs 数组
从图中可以看出,通过数据结构 struct inode{...} 中的 i_cdev 成员可以找到cdev,⽽所有的字符设备都在 chrdevs 数组中
下⾯先看⼀下 chrdevs 的定义:
1. #define CHRDEV_MAJOR_HASH_SIZE 255
2. static DEFINE_MUTEX(chrdevs_lock);
3.
4. static struct char_device_struct {
5. struct char_device_struct *next; // 结构体指针
6. unsigned int major; // 主设备号
7. unsigned int baminor; // 次设备起始号
8. int minorct; // 次备号个数
9. char name[64];
0. struct cdev *cdev; /* will die */
1. } *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; // 只能挂255个字符主设备<span > </span>
可以看到全局数组 chrdevs 包含了255(CHRDEV_MAJOR_HASH_SIZE 的值)个 struct char_device_struct的元素,每⼀个对应⼀个相应的主设备号。
如果分配了⼀个设备号,就会创建⼀个 struct char_device_struct 的对象,并将其添加到 chrdevs 中;这样,通过chrdevs数组,我们就可以知道分配了哪些设备号。蒸鸡的做法
相关函数,(这些函数在上篇已经介绍过,现在回顾⼀下:
register_chrdev_region( ) 分配指定的设备号范围
alloc_chrdev_region( ) 动态分配设备范围
他们都主要是通过调⽤函数 __register_chrdev_region() 来实现的;要注意,这两个函数仅仅是注册设备号!如果要和cdev关联起来,还要调⽤cdev_add()。
register_chrdev( )申请指定的设备号,并且将其注册到字符设备驱动模型中.
它所做的事情为:
a -- 注册设备号, 通过调⽤ __register_chrdev_region() 来实现
引体向上怎么荡b -- 分配⼀个cdev, 通过调⽤ cdev_alloc() 来实现
集体备课的五个步骤c -- 将cdev添加到驱动模型中, 这⼀步将设备号和驱动关联了起来. 通过调⽤ cdev_add() 来实现
d -- 将第⼀步中创建的 struct char_device_struct 对象的 cdev 指向第⼆步中分配的cdev. 由于register_chrdev()是⽼的接⼝,这⼀步在新的接⼝中并不需要。
四、cdev 结构体
在 有解析。
五、⽂件系统中对字符设备⽂件的访问
下⾯看⼀下上层应⽤open() 调⽤系统调⽤函数的过程
对于⼀个字符设备⽂件, 其inode->i_cdev 指向字符驱动对象cdev, 如果i_cdev为 NULL ,则说明该设备⽂件没有被打开.
由于多个设备可以共⽤同⼀个驱动程序.所以,通过字符设备的inode 中的i_devices 和 cdev中的list组成⼀个链表