字符设备驱动之结构体
二.字符设备驱动的数据结构
大部分驱动程序操作都涉及到三个重要的内核数据结构,分别是file_operations、file和inode,它们的定义都在<linux/fs.h>
1.file_operations:是一个函数指针的集合
1>应用程序和VFS之间的接口是系统调用,而VFS与磁盘文件系统以及普通设备之间的接口是file_operations结构体成员函数;file_operations结构体中成员函数是字符设备驱动与内核的接口,是用户空间对Linux进行系统调用最终的落实者,这个结构体包含对文件打开,关闭,读写,控制的一系列成员函数
2>由于字符设备的上层没有磁盘文件系统,所以字符设备的file_operations成员函数就直接由设备驱动提供了,file_operations正是字符设备驱动的核心
3>而对于块设备而言,ext2,fat,jffs2等文件系统中会实现对VFS的file_operations成员函数,设备驱动层将看不到file_operations的存在。磁盘文件系统和设备驱动会将磁盘上文件的访问最终转换成对磁盘上柱面和扇区的访问
4>file_operations的成员
static const struct file_operations XXX_fops =
{
.owner = THIS_MODULE,
.llseek = XXX_llseek,
.open = XXX _open,
.read = XXX _read,
.write = XXX _write,
.ioctl = XXX _ioctl,
.release = XXX _release,
};
(1)struct module *owner
第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有程序中, 它被简单初始化为 THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.
(2)loff_t (*llseek) (struct file *, loff_t, int);
文件定位函数,合法时返回文件的当前位置,不合法返回-EINVAL
第一个参数为file指针
第二个为请求偏移量
第三个为文件定位的起始地址:一般为0或1,0表示文件开头,1表示当前位置
(3)ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
读函数,利用copy_to_user()函数让内核读取用户空间的数据,并返回访问的字节数
(4)ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
写函数利用copy_from_user()函数让用户向文件写入数据,并返回写入的字节数
(5)int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
执行I/O控制命令
(6)int (*open) (struct inode *, struct file *);
打开文件
(7)int (*release) (struct inode *, struct file *) ;
关闭文件
2. struct file:文件结构体(只介绍和字符设备有关的数据项目)
关于具体struct file请参考《VFS中的目录项对象和文件对象》
1>文件结构体代表一个打开的文件(设备对应于设备文件),系统中每个打开的文件在内核空间都有一个关联的struct file.它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构
2>字符设备驱动用到的变量,文件读写模式mode,标志f_flags都是设备驱动关心的内容,而私有数据指针private_data在设备驱动中被广泛使用,大多被用于指向设备驱动自定义的设备结构体。
3>struct file
struct file {
940 /*
941 * fu_list becomes invalid after file_free is called and queued via
942 * fu_rcuhead for RCU freeing
943 */
951 const struct file_operations *f_op;
957 unsigned int f_flags;
958 fmode_t f_mode;
959 loff_t f_pos;
960 struct fown_struct f_owner;
969 void *private_data;
970
979};
(1)fmode_t f_mode
文件模式确定文件是可读的或者是可写的(或者都是), 通过位 FMODE_READ 和 FMODE_WRITE. 你可能想在你的 open 或者 ioctl 函数中检查这个成员的读写许可, 但是你不需要检查读写许可, 因为内核在调用你的方法之前检查. 当文件还没有为那种存取而打开时读或写的企图被拒绝, 驱动甚至不知道这个情况.
(2)unsigned int f_flags
这些是文件标志, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驱动应当检查 O_NONBLOCK 标志来看是否是请求非阻塞操作;其他标志很少使用. 所有的标志在头文件 <linux/fcntl.h> 中定义.
(3)void *private_data
open 系统调用设置这个指针为 NULL, 在为驱动调用 open 方法之前. 你可自由使用这个成员或者忽略它; 你可以使用这个成员来指向分配的数据, 但是接着你必须记住在内核销毁文件结构之前, 在 release 方法中释放那个内存. private_data 是一个有用的资源, 在系统调用间保留状态信息.
(4) loff_t f_pos;
当前读写位置. loff_t 在所有平台都是 64 位( 在 gcc 术语里是 long long ). 驱动可以读这个值, 如果它需要知道文件中的当前位置, 但是正常地不应该改变它; 读和写应当使用它们作为最后参数而收到的指针来更新一个位置, 代替直接作用于 filp->f_pos. 这个规则的一个例外是在 llseek 方法中, 它的目的就是改变文件位置.
(5) struct file_operations *f_op;
和文件关联的操作. 内核安排指针作为它的 open 实现的一部分, 接着读取它当它需要分派任何的操作时. filp->f_op 中的值从不由内核保存为后面的引用; 这意味着你可改变你的文件关联的文件操作, 在你返回调用者之后新方法会起作用. 例如, 关联到主编号 1 (/dev/null, /dev/zero, 等等)的 open 代码根据打开的次编号来替代 filp->f_op 中的操作. 这个做法允许实现几种行为, 在同一个主编号下而不必在每个系统调用中引入开销. 替换文件操作的能力是面向对象编程的"方法重载"的内核对等体.
3>驱动程序中经常会使用如下类似的代码来检测用户打开文件的读写方式。
if (flie->f_mode & FMODE_WRITE)//用户要求可写
{
}
4>用下面的代码判断以阻塞还是非阻塞方式打开设备文件
if (file->f_flags & O_NONBLOCK)//非阻塞
{
}
else //阻塞
{
}
3.struct inode 结构
------------------------------------------------------------------------------------
此处还是主要介绍和字符设备驱动相关的变量成员,详细请参考《Linux内核编程之文件系统(一)》
------------------------------------------------------------------------------------
虚拟文件系统中的每个文件都关联到恰哈一个inode,用于管理文件的属性(包含文件访问权限,属主,组,大小,生成时间,访问时间,最后修改时间等信息,它是Linux管理文件系统的最基本单位,也是文件系统连接任何子目录,文件的桥梁)
struct inode {
741 /* RCU path lookup touches following: */
742 umode_t i_mode;
766 dev_t i_rdev;
786 struct list_head i_devices;
787 union {
788 struct pipe_inode_info *i_pipe;
789 struct block_device *i_bdev;
790 struct cdev *i_cdev;
791 };
812 void *i_private; /* fs or device private pointer */
};
1>i_mode:为唯一地标示与一个设备文件关联的设备,内核在i_mode中中存储了文件类型(面向块,或者面向字符)
2>i_rdev:中存储了主从设备号。主从设备号在内核中合并为一种变量类型dev_t.
3>i_fop:是一组函数指针的集合,包括许多文件操作(如打开,读取,写入等),这些由虚拟文件系统使用来处理块设备
4>内核会根据inode标示块设备还是字符设备,来使用i_bdev或i_cdev指向更多具体的信息。
对于代表设备文件的节点, 这个成员包含实际的设备编号.
5>struct cdev *i_cdev;
struct cdev 是内核的内部结构, 代表字符设备; 这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时.
4.cdev结构体
在Linux中使用cdev结构体描述字符设备,cdev结构体定义:
12struct cdev {
13 struct kobject kobj;
14 struct module *owner;
15 const struct file_operations *ops;
16 struct list_head list;
17 dev_t dev;
18 unsigned int count;
19};
1>kobj是一个嵌入在该结构中的内核对象。它用于该数据结构的一般管理(是一个重要的数据结构,后在后面对其进行详细的介绍)
2>owner指向提供驱动程序的模块
3>ops是一组文件操作,实现了与硬件通信的具体操作。
4>dev指定了设备号
5>count表示与该设备关联的从设备的数目
6>list用来实现一个链表,其中包含所有表示该设备的设备特殊文件的inode.