file_operations结构体
file_operations结构体是字符设备驱动的核心。对设备文件任何文件式的操作都会转换为对file_operations结构体中的成员函数的调用。 C语言结构体没有成员函数的概念,这里所谓的成员函数实际上是一些类型为函数指针的成员变量。 file_operations结构体定义于linux/fs.h中,其主要成员如下:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file * filp, loff_t pos, int);
ssize_t (*read) (struct file *filep, char __user * buf, size_t len, loff_t * pos);
ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t (*write) (struct file *filp, const char __user *buf, size_t len, loff_t * pos);
ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int cmd, unsigned long data);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area) (struct file *, unsigned long,
unsigned long, unsigned long,
unsigned long);
};
struct file和 struct inode
在介绍file_operations之前先介绍两个结构体,struct file 和 struct inode, 只列出可能用到的成员。
struct file {
const struct file_operations *f_op; //这个就是你传进去的file_operations结构体
void *private_data; //该成员是系统调用时保存状态信息非常有用的资源。
unsigned int f_flags; //表示文件打开时采用的标志:
//O_RDWR,O_RDONLY,O_SYNC,O_NBLOCK
mode_t f_mode; //文件权限,可读,可写,可执行
//chmod +x file时会改变该位
loff_t f_pos; //文件指针,指示当文件被读或写到什么位置了,
//除了llseek函数外,其他函数不应该改变这个字段。
struct dentry *f_dentry; //文件路径,通常驱动开发用不到它,
//唯一用到它的时候就是通过它来找到inode结构体
//filp->f_dentry->d_inode
}
struct inode {
dev_t i_rdev; //设备号,对于注册多个设备的驱动程序可以根
//子设备号来知道要访问的是哪个设备
//这个字段在不同的版本间可能会有变化,可以通过
//imajor和iminor两个函数来得到主次设备号,这样
//的代码是可移植的。
struct cdev *i_cdev; //cdev结构体提针,有些时候我们把这个cdev结构体放
//在另一个更大的mydev结构中,这时可以通过
//container_of宏来得到mydev结构
}
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
下面按常用程度的顺序依次介绍。
open
open函数有些时候并不是必须的,特别是你只有一个同类设备的时候。
int (*open) (struct inode *, struct file *);
用户打开一个设备文件时调用open,入参struct file和struct inode分别描述打开的设备文件的文件描述符(file descritpor, fd)和这个设备文件的文件信息。
read
ssize_t (*read) (struct file * filp, char _ _user * buf, size_t len, loff_t * pos);
这个函数要求驱动程序从pos偏移处读到len个字节到buf中。pos偏移具体代表什么意义由驱动开发者定义,它也可以没有意义,这时可以忽略这个参数。
write
ssize_t (*write) (struct file *filp, const char __user *buf, size_t len, loff_t * pos);
这个函数要求驱动程序将buf中的len字节数据写入到设备的pos偏移处。pos偏移具体代表什么意义由驱动开发者定义,它也可以没有意义,这时可以忽略这个参数。
ioctl
int (*ioctl) (struct inode *, struct file *, unsigned int cmd, unsigned long data);
这个接口用来发送一些read/write不太容易做到的设备相关的命令。它的后两个参数cmd和data的意义完全由驱动开发者去定义,通常cmd会传递命令类型,data会传递命令所有附带的数据。data可以是一个long型的数据,也可以传递一个用户空间的缓冲区指针。如果传入的是用户空间的缓冲区指针,则需要驱动代码中用copy_from_user和copy_to_user来对它进行操作。
llseek
loff_t (*llseek) (struct file *, loff_t offset, int whence);
该函数移动文件读写指针到指定的位置,offset是移动的长度,whence指定是相对位移还是决对位移。
whence可选值是SEEK_SET(绝对位移,移到距文件开头offset处), SEEK_CURR(相对位移,移动距当前位置offset处), SEEK_END(相对位移,移到距文件结尾-offset处, offset应为负值)。
llseek的一个实现示例如下:
loff_t scull_llseek(struct file *filp, loff_t off, int whence)
{
struct scull_dev *dev = filp->private_data;
loff_t newpos;
switch(whence)
{
case 0: /* SEEK_SET */
newpos = off;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + off;
break;
case 2: /* SEEK_END */
newpos = dev->size + off;
break;
default: /* can't happen */
return -EINVAL;
}
if (newpos < 0)
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}