Linux系统中根据驱动程序实现的模拟框架将设备的驱动分为了三大类:
一:字符设备驱动
设备对数据的处理是按照字节流的形式进行的,可以支持随机访问,也可以不支持随机访问,因为数据流量通常不是很大,所以一般没有页高速缓存(是linux内核实现磁盘缓存。它主要用来减少对磁盘的I/O操作。具体地讲,是通过把磁盘中的数据缓存到物理内存中,把对磁盘的访问变为对物理内存的访问)。典型的字符设备有串口(串行接口)、键盘、帧缓存设备等。以串口为例,串口对收发的数据长度没有具体要求,可以是任意多个字节;串口也不支持lseek操作,即不能定位到一个具体的位置进行读写,因为串口按顺序发送或接受数据;串口的数据通常保存在一个较小的FIFO中,并且不会重复利用FIFO中的数据。帧缓存设备(就是我们通常说的显卡)也是一个字符设备,但它可以进行随机访问,这样我们就能修改某个具体位置的帧缓存数据,从而改变屏幕上的某些确定像素点的颜色。
1.1 字符设备驱动基础
使用如下命令可以看到很多设备文件及其相关的信息 : ls -l /dev
注:设备文件会比普通文件多出两个数字,这两个数字分别是主设备号和次设备号。这两个号是设备在内核中的身份或标志,是内核区分不同设备的唯一信息。通常内核用主设备号区别一类设备,次设备号用于区分同一类设备单不同个体或不同分区。而路径名则是用户层用于区别设备信息的。
mkmod命令(make node):创建了一个节点,所以设备文件有时又叫做设备节点。
在Linux系统中,一个节点代表一个文件,创建一个文件最主要的根本工作就是分配一个新的节点(存在于磁盘上的节点,之后会看到位于内存中的节点inode),包含节点号的分配(节点号在一个文件系统中是唯一的,可以以此来区别不同的文件),然后初始化好这个新节点(包含文件模式、访问时间、用户ID、组ID等元数据信息,如果是设备文件还要初始化好设备号),再将这个初始化好的节点写入磁盘。还需要在文件所在的目录下添加一个目录项,目录项中包含了前面分配的节点号和文件的名字,然后写入磁盘。存在于磁盘上的这个节点用一个结构封装。
1.2字符设备驱动框架
实现一个字符设备驱动,最重要的就是构造一个cdev对象,并让cdev同设备号和设备的操作方法集合相关联,然后将该cdev结构对象添加到内核的cdev_map散列表中。
根据inode中的设备号——>找到cdev——>根据cdev——>找到关联的操作方法集合——>调用驱动所提供的操作方法来完成对设备的具体操作
cdev 和file_operations之间的调用关系可用下图来表法
第一步:在驱动中注册设备号
①:静态注册设备号
静态申请设备号(register_chrdev_region)
int register_chrdev_region(dev_t from, unsigned count, const char *name);
/* 参数:
dev_t from - 要申请的设备号(起始)
unsigned count - 要申请的设备号数量
const char *name - 设备名
返回值:
成功:0
失败:负数(绝对值是错误码)*/
②:动态注册设备号
动态申请设备号(alloc_chrdev_region)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
/* 参数:
dev_t *dev - 用于保存分配到的第一个设备号(起始)
unsigned baseminor - 起始次设备号
unsigned count - 要分配设备号的数量
const char *name - 设备名
返回值:
成功:0
失败:负数(绝对值是错误码)*/
第二步:构造并添加cdev结构对象
①:定义一个struct cdev类型的全局变量vsdev
static struct cdev vsdev;
②:定义一个struct file_operations类型的全局变量vser_ops
static struct file_operations vser_ops = {.owner = THIS_MODULE,};
相关代码如下:
struct file_operations
{
struct module *owner;
/* 模块拥有者,一般为 THIS——MODULE */
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
/* 从设备中读取数据,成功时返回读取的字节数,出错返回负值(绝对值是错误码) */
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
/* 向设备发送数据,成功时该函数返回写入字节数。若为被实现,用户调层用write()时系统将返回 -EINVAL*/
int (*mmap) (struct file *, struct vm_area_struct *);
/* 将设备内存映射内核空间进程内存中,若未实现,用户层调用 mmap()系统将返回 -ENODEV */
long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg);
/* 提供设备相关控制命令(读写设备参数、状态,控制设备进行读写...)的实现,当调用成功时返回一个非负值 */
int (*open) (struct inode *, struct file *);
/* 打开设备 */
int (*release) (struct inode *, struct file *);
/* 关闭设备 */
int (*flush) (struct file *, fl_owner_t id);
/* 刷新设备 */
loff_t (*llseek) (struct file *, loff_t, int);
/* 用来修改文件读写位置,并将新位置返回,出错时返回一个负值 */
int (*fasync) (int, struct file *, int);
/* 通知设备 FASYNC 标志发生变化 */
unsigned int (*poll) (struct file *, struct poll_table_struct *);
/* POLL机制,用于询问设备是否可以被非阻塞地立即读写。当询问的条件未被触发时,用户空间进行select()和poll()系统调用将引起进程阻塞 */
...
};