驱动是具有入口和出口的一组方法的集合,这一组方法才是驱动的核心内容。
对于字符设备驱动程序,最核心的就是 file_operation 结构,这个结构实际上是提供给虚拟文件系统 [ VFS ] 的文件接口,它的每一个成员函数一般都对应一个系统调用。用户进程利用系统调用对设备文件进行诸如读和写操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序。
理解 : 系统调用通过设备文件的主设备号找到相应的设备驱动程序
从下图中,/dev 目录下的设备文件可知,agpgart 的主设备号为 10 次设备号为 175
而且设备文件中,主设备号时可以相同的,次设备号的唯一的。
主设备号跟次设备号就可以用来识别一个设备文件。像通过身份证号码就可以识别一个人一样。
首字符c b 是标识符, c 表示字符设备, b 表示块设备
摘自 Linxu 4.17.14,,在 /include/linux/fs.h中定义。用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的 事务的函数的地址。
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 *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
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 (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
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);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,loff_t, size_t, unsigned int);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,u64);
ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,u64);
} __randomize_layout;
file_opration 简单的运用 :
应用层系统调用与驱动层 struct file_operation fops 的调用核心。
struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
{
参数 major 如果等于 0,则表示采用系统动态分配的主设备号。如果不等于 0,则需要通过查询未用的设备号来为函数传参数
}
{
struct cdev {
struct kobject kobj;
struct module *owner; //所属模块,一般为 THIS_MODULE
const struct file_operations *ops; //文件操作结构,即上文中的 file_operations 结构
struct list_head list;
dev_t dev; //设备号
unsigned int count;
} __randomize_layout;
对 cdev 结构的操作,内核提供了一组函数
void cdev_init(struct cdev *, const struct file_operations *); //初始化一个 cdev 结构
struct cdev *cdev_alloc(void); //为 cdev 结构分配内存
void cdev_put(struct cdev *p); //减少使用计数
int cdev_add(struct cdev *, dev_t, unsigned); //注册设备,通常发生在驱动模块的加载函数中
void cdev_set_parent(struct cdev *p, struct kobject *kobj);
int cdev_device_add(struct cdev *cdev, struct device *dev);
void cdev_device_del(struct cdev *cdev, struct device *dev);
void cdev_del(struct cdev *); //注销设备,通常发生在驱动模块的卸载函数中
void cd_forget(struct inode *);
使用 cdev 结构注册设备的一般过程如下。
以动态分配设备号为例:
申请设备号
分配设备结构
初始化设备结构
添加字符设备
无论是用 register_chrdev 还是用 cdev 注册字符设备都是可以的。 register_chrdev机制出现在 linux 2.6之前的内核版本中,而 cdev 出现在 linux 2.6及之后的内核版本中,不过 linux 2.6及之后的内核版本依然兼容 register_chrdev 机制。
}
{
在 Linux 有个最核心的思想,一切皆为文件。字符设备依然不例外。字符设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。
最后根据 xxx_read(),xxx_write() 内容进行硬件操作。’
}