字符设备驱动是Linux系统中最多的一类驱动,也是较简单的一类驱动。
Linux系统根据驱动程序实现的模型框架将设备的驱动分为三类
- 字符驱动程序
设备对设局的处理是按照字节流的形式进行的,可以支持随机访问,也可以不支持随机访问,因为数据流量通常不大,一般没有高速缓存。典型的字符设备有串口(接受数据没有具体要求,可以是任意多个字符,不支持lessk操作)、键盘、帧缓存设备(显卡,可以随机访问)等。
- 块设备驱动
设备对数据的数据是按照若干个块进行的,一个块有其固定的大小。这类设备都支持随机访问,并且为了提高效率,可以将之前用过的数据缓存起来,以便下次使用。典型的块设备有硬盘、光盘、SD卡。
- 网络设备驱动
专门针对网络设备的一类驱动,主要是进行网络数据的收发。
但在现实生活中,有的设备很难被界定,甚至同时拥有两种驱动。一个设备的驱动属于哪一类,还要看具体的适用场合和最终的用途。
字符设备驱动基础
在Linux中,一切设备皆文件,设备文件通常位于/dev目录下,使用ls -l /dev可以查看设备文件及相关信息
ls -l /dev
b开头的是块设备,c开头的是字符设备,sda是整个硬盘,tty是终端设备,shell程序使用这些设备来同设备进行交互。
设备文件比普通文件多两个数字,数字分别是主设备号、次设备号。这两个号是设备在内核中的身份或标志,是内核区分不同设备的唯一信息。通常内核用主设备号区分一类设备,次设备号用于区分同意设备的不同个体或分区,路径名是用户层用于区分设备信息的。
Linux中,设备文件通常是自动创建的,也有mknod命令可以手动创建一个设备文件。
mknod命令,创建一个节点(设备文件有时又称设备节点)。在Linux中,一个节点代表一个文件,创建一个文件最主要的根本工作就是分配一个新的节点(这里是存在于磁盘上的节点,还有位于内存中的节点inode),包含节点好的分配(唯一,用于区分不同的文件),然后初始化这个新结点(文件模式、访问时间、用户ID、组ID等元数据信息,设备文件还要初始化设备号),再将这个初始化好的节点写入磁盘。还要在文件所在的目录下添加一个目录项,目录项中包含了前面分配的节点号和文件的名字,然后写入磁盘。
字符设备驱动框架
要实现一个字符设备驱动,最重要的是构造一个cdev结构对象,并让cdev同设备号与设备的操作方法集合相关联,然后将该cdev结构对象添加到内核的cdev_map散列表中。
- 在驱动中注册设备号:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"
static int __int vser_init(void){
int rev;
dev_t dev;//无符号32位整数,主设备号12位,次设备号20位
dev = MKDEV(VSER_MAJOR,VSER_MINOR);//使用MKDEV宏将主设备号和次设备号合并成一个设备号
ret = register_chrdev_region(dev,VSER_DEV_CNT,VSER_DEV_NAME);
//将构造的设备号注册到内核
if(ret)
goto reg_err;
return 0;
reg_err:
return ret;
}
static void __exit vser_exit(void){
dev_t dev;
dev = MKDEV(VSER_MAJOR,VSER_MINOR);
unregister_chrdev_region(dev,VSER_DEV_CNT);
//注销
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
register_chrdev_region函数
int register_chrdev_region(dev_t from,unsigned count ,const char *name);
该函数一次可以注册多个连续的号,由count形参指定个数,由from指定起始的设备号,name用于标记设备号的名称。调用成功返回0,失败返回负数。注册出错,使用goto语句跳转到错误处理代码执行(在驱动错误处理常见)
如果两个驱动使用了相同的设备号,那么后加载的驱动将失败,因为设备号冲突了。为了解决这个问题,可以使用动态分配设备号的函数。
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unisged count,const char *name);
count和name形参和register_chrdev_region函数的形参一样。baseminor时动