一、基础概念
1、linux系统将设备分为3种类型:字符设备(如鼠标、键盘等按字节流顺序与系统通信的设备)、块设备(如硬盘、U盘等可以读取指定位置数据的设备)、网络设备(网卡)。
2、设备在系统中被当作一个文件,对设备文件进行操作即是对设备硬件的操作。
3、设备号:使用dev_t(u_long)类型存储,分为主设备号(major)和次设备号(minor)两部分。主设备号使用设备号的高12位表示,次设备号由低20位表示。
4、设备号的分配方式:在构建字符设备之前,先要向系统申请一个或者多个设备号。分配方式有两种,静态和动态。
(1)静态分配:即手动为设备分配设备号,可能造成与其它设备的设备号冲突。
int register_chrdev_region(dev_t from,unsigned count,const char *name)
from是要分配的设备号范围的起始值,一般只提供from的主设备号部分,次设备号部分常设为0;count是需要申请的连续设备号的个数;name是和该范围编号关联的设备名称,该名称不能超过64字节。
(2)动态分配:
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)
dev为输出参数,函数执行成功后分配的设备号将保存在dev中,如果分配了多个连续的设备号,则dev为分配的第一个设备号;baseminor表示要申请的第一个次设备号,通常设为0;其余参数同上。
(3)设备号的释放:使用上面两种方式分配的设备号,都应该在不使用设备时释放设备号,使用如下函数操作。
void unregister_chrdev_region(dev_t from,unsigned count)
参数意义同上。通常,在模块的卸载函数中调用该函数释放设备号。
二、两个重要的结构体:cdev和file_opterations
1、cdev结构体:linux内核中使用cdev结构体描述字符设备,是所有字符设备的抽象,包含了大量字符设备所共有的特性。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
其中kobj结构用于内核管理字符设备;ops结构是指向file_operations结构体的指针,该结构定义了操作字符设备的函数;list为一双向链表;dev为用来存储所申请的设备号;count表示目前在使用该驱动程序的设备数目,当使用rmmod卸载模块时,如果count成员不为0,则系统不允许卸载该模块。
每一个字符设备在/dev目录下都有一个设备文件,打开设备文件就相当于打开相应的设备。如应用程序打开一个设备文件A,系统就会生成一个inode结点,内核最终会调用chrdev_open()函数将inode.i_devices添加到相应cdev结构的list中,形成一个新的链表,即表示该设备使用由cdev结构定义的驱动程序。于是就可以使用对应cdev中的ops指针中的相应操作函数(open、read、write、close、seek、ioctl等)对该设备进行操作。
cdev与inode结构体的关系如图所示:
2、file_operations:是一个对设备进行操作的抽象结构体。
linux内核允许为设备建立一个设备文件,对设备文件的所有操作,就相当于对设备的操作,这样的好处是,用户程序可以使用访问普通文件的方式访问设备文件,进而访问设备。这种方法使得开发者不必去熟悉新的驱动接口就能访问设备。对普通文件的访问,常使用open()、read()、write()、close()、ioctl()等方法,对设备文件的访问,同样可以使用这些方法,这些调用最终会调用file_operations结构中对应的操作函数。对开发者来说,只要为不同的设备编写不同的操作函数就可以了。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 *);
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 *);
unsigned int (*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);
}
其中owner成员为指向拥有该结构模块的指针,该成员用来维持模块的引用计数,当模块还在使用时,不能用rmmod卸载模块。其它函数指针则顾名思义很容易理解。
cdev与file_operations结构体的关系如下图所示: