上一章中,讲到字符设备驱动模型,了解了字符驱动的框架。为了更好的理解驱动程序,本章讲述其中涉及的三个重要数据结构,file_operations、file和inode结构。
1.文件操作file_operations
file_operations结构是用来建立驱动程序与设备间的连接,该结构定义在<linux/fs.h>,其中包含了一组函数指针。每个打开的文件(在内部由一个file结构表示)和一组函数关联(通过包含指向一个file_operations结构的f_op字段)。这些操作主要用来实现系统调用,如open,read等。
file_operations结构或者指向这类结构的指针称为fops,在这个结构中的每一个字段都必须指向驱动程序中实现特定操作的函数,对于不支持的操作,对应的字段可置为NULL值,可以认为文件是一个“对象”,操作它的函数是“方法”。下面节选了file_operations结构中常用的成员
struct file_operations
{
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
}
在一些参数类型包含有_ _user字符串,表明指针是一个用户空间地址,因此不能被直接引用。
- struct module *owner
它是指向“拥有”该结构的模块指针。使用这个字段可以避免在模块在被使用是卸载该模块。几乎在所有情况下,该成员会被初始化为THIS MODULE。
- loff_t (*llseek) (struct file *, loff_t, int)
方法llseek用来修改文件的当前读写位置,并将新位置作为正值返回。
- ssize_t (*read)(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
用来从设备中读取数据。返回负数时,表示读取失败,返回非负数时,表示实际读取到的字节数。
- ssize_t (*write)(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
用于向设备发送数据。返回负数时,表示写入失败,返回非负数时,表示实际写入的字节数。
read和write方法完成的任务是相似的,在用户空间和内核空间之间拷贝数据,需要注意的是buf参数是用户空间的指针,因此,内核代码不能直接引用其中的内容。避免用户空间程序随意访问或修改内核中的内存,危及系统安全,因此,这种访问始终通过内核提供的专用函数完成。
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (access_ok(VERIFY_READ, from, n))
n = __copy_from_user(to, from, n);
else /* security hole - plug it */
memset(to, 0, n);
return n;
}
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (access_ok(VERIFY_WRITE, to, n))
n = __copy_to_user(to, from, n);
return n;
}
1.这两个函数的作用在用户地址和内核地址之间进行任意一段字节序数据序列的拷贝
2.它们还检查用户空间的指针是否有效,如果指针无效,就不会进行拷贝;如果在拷贝过程中遇到无效地址,则仅会复制部分数据。
- unsigned int (*poll) (struct file *, struct poll_table_struct *)
poll方法是poll、epoll和select这三个系统调用的后端实现。这三个系统调用可用来查询某个或多个文件描述符上的读取或写入是否会被阻塞。poll方法返回一个位掩码,用来指出非阻塞的读取或写入是否会被阻塞,并且也会想内核提供将调用进程置于休眠状态直到I/O变为可用时的信息。
- int (*mmap) (struct file *, struct vm_area_struct *)
mmap用于请求将设备内存映射到进程地址空间。
- int (*open) (struct inode *, struct file *)
执行对设备文件的打开操作。
- int (*release) (struct inode *, struct file *)
当file结构被释放时,将调用这个操作。注意,release并不是在进程每次调用close时都会被调用,只有那些真正释放设备数据结构的close调用才会调用这个方法。只要file结构被共享,如fork调用之后,release就会等到所有副本都关闭之后才会得到调用。
内核对每个file结构维护其被使用多少次的计数器,无论fork还是dup,都不回创建新的数据结构(仅由open创建),它们只是增加已有结构中的计数,只有在file结构的计数归0时,close系统调用才会执行release方法。
- int (*fasync) (int, struct file *, int)
这个操作用来通知设备其FASYNC标志发生变化。
2.file结构
在<linux/fs.h>中定义的struct file结构代表一个打开的文件(它并不仅仅限定于设备驱动程序,系统中每个打开的文件在内核空间都有一个对应的file结构)。它由内核在open时创建,并传递给在该文件上进行操作的所有函数,直到最后的调用close函数。在文件的所有实例都被关闭之后,内核会释放这个数据结构。
在内核源码中,指向struct file的指针通常被称为filp(文件指针),即file指的是结构体本身,filp则是指向该结构体的指针。在此,先认识几个重要的成员,后续使用中我们再详细介绍。
struct file
{
fmode_t f_mode;
loff_t f_pos;
unsigned int f_flags;
const struct file_operations *f_op;
void *private_data;
}
- fmode_t f_mode;
文件模式。它通过PMODE_READ和PMODE_WRITE位来标识文件是否是可读或可写(或可读写)
- loff_t f_pos;
当前的读/写位置。如果驱动程序需要知道文件中的当前位置,可以读取这个值,但不要去修改它,read/write会使用他们接收到的最后那个指针参数来更新这个位置,而不是直接对filp->f_ops进行操作。
- unsigned int f_flags;
文件标志,如O_NONBLOCK标志。为了检查用户请求的是否是非阻塞式操作。
- const struct file_operations *f_op;
与文件相关的操作。内核在执行open操作时对这个指针赋值,以后需要处理这些操作时就读取这个指针。
- void *private_data;
驱动程序可以将这个字段用于任何目的或者忽略这个字段。驱动程序可以用这个字段指向已分配的数据,但是一定要在内核销毁file结构前,在release方法中释放内存。
3.inode结构
内核用inode结构在内部表示文件,file表示打开的文件描述符。对单个文件,可能会有许多个打开的文件描述符file结构都指向单个inode结构。重点看一下inode结构中常用的成员。
-
struct inode { dev_t i_rdev; struct cdev *i_cdev; }
- dev_t i_rdev;
对表示设备文件的inode结构,该字段包含了真正的设备编号。
- struct cdev *i_cdev;
struct cdev是表示字段设备在内核的内部结构,当inode之指向一个文件设备文件时,该字段包含了指向struct cdev结构的指针。