字符设备驱动——讯为笔记
字符设备和杂项设备的区别
杂项设备的主设备号是固定的,字符设备需要分配主设备号
杂项设备自动生成设备节点,字符设备需要程序生成设备节点
所以创建字符设备会比杂项设备多两步:申请设备号、创建设备节点
申请设备号
#include <linux/fs.h>
静态分配设备号
即手动指定设备号
int register_chrdev_region(dev_t, unsigned, const char*);
//申请设备号
//参数:设备号的起始值;次设备号的个数;设备名称
//分配成功返回0,失败返回非0
void unregister_chrdev_region(dev_t, unsigned);
//注销设备号
//参数:设备号,次设备号个数
静态分配设备号时要确保指定的设备号没有被占用,cat /proc/devices可查看当前主设备号
动态分配设备号
int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char*);
//参数:要生成的设备号指针;请求的第一个次设备号,通常是0;连续申请的设备号个数;设备名称
//成功返回0,失败返回负数
//动态分配优先使用255-234
推荐使用动态分配的方式
dev_t设备号类型
#include <linux/kdev_t.h>
dev_t类型:设备号类型,是一个32位数
高12位保存主设备号,低20位保存次设备号
//Linux中的宏定义
#define MINORBITS 20 //次设备号的位数
#define MINORMASK ((1U << MINORBITS) - 1) //次设备号的掩码
#define MAJOR(dev) ((unsigned int) (dev >> MINORBITS) //从dev_t中获取主设备号
#define MINOR(dev) ((unsigned int) (dev & MINORMASK) //从dev_t中获取次设备号
#define MKDEV(ma,mi) ((ma << MINORBITS) | mi) //传入主设备号和次设备号,获得dev_t类型的设备号
注册字符类设备
#include <linux/cdev.h>
注册步骤:
- 定义一个cdev和file_operations结构体
- 使用cdev_init函数初始化cdev结构体成员变量
- 使用cdev_add函数注册到内核
使用cdev_del函数卸载
c_dev结构体
描述一个字符设备的结构体(和杂项设备的结构体类似)
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;
cdev相关函数
void cdev_init(struct cdev *, const struct file_operations *);
//参数:cdev结构体指针,文件操作集
int cdev_add(struct cdev *, dev_t, unsigned);
//参数:cdev结构体指针,设备号,次设备数量
void cdev_del(struct cdev *);
创建设备节点
命令mknod
mknod 名称 类型 主设备号 次设备号
#例如:给主设备号247次设备号0的设备,创建名为/dev/test的设备节点
mknod /dev/test c 247 0
#c代表字符类设备
驱动代码创建
创建步骤:
- 使用class_create函数创建一个class类(会在/sys/class/目录下出现)
- 使用device_create函数在创建的class类下面创建一个设备(会在/dev/目录下出现)
注销时,先创建的要后注销
相关函数
//include/linux/device.h
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
//参数:owner一般为THIS_MODULE,参数name是类名字
//返回值是指向创建的class类的指针
void class_destroy(struct class *cls);
//参数:cls是要删除的class类
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
//参数:cls指在这个类下创建;parent是父设备,一般为NULL即没有父设备
// devt是设备号;drvdata是设备可能使用的一些数据,一般为NULL
// fmt是设备节点的名字,即/dev目录下文件名
void device_destroy(struct class *cls, dev_t devt);
//删除设备
//参数:设备所属的类;要删除的设备号
代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#define MINOR_DEVICE_NUM 1 //连续注册的次设备个数
#define DEVICE_NAME "chrdev_proc" //设备号的名字
#define CLASS_NAME "chrdev_class" //设备类的名字
#define DEVICE_NODE_NAME "chrdev_taxue" //设备节点名
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TAXUE");
static dev_t devtChr;
static struct cdev cdevChr={
.owner = THIS_MODULE
};
static struct file_operations fileOperat = {
.owner = THIS_MODULE
};
static struct class * classChr=NULL;
static struct device * deviceChr=NULL;
//驱动入口
static int chrdev_init(void){
int ret;
printk("chrdev init\n");
ret = alloc_chrdev_region( &devtChr, 0, MINOR_DEVICE_NUM, DEVICE_NAME); //动态分配设备号
if (ret != 0) {
printk("alloc chrdev failed\n");
return -1;
}
printk("MAJOR=%d, ", MAJOR(devtChr)); //主设备号
printk("MINOR=%d\n", MINOR(devtChr)); //次设备号
cdev_init( &cdevChr, &fileOperat); //初始化cdev结构体
cdev_add( &cdevChr, devtChr, MINOR_DEVICE_NUM); //向内核注册设备
classChr = class_create(THIS_MODULE, CLASS_NAME); //创建类
deviceChr = device_create(classChr, NULL, devtChr, NULL, DEVICE_NODE_NAME); //创建设备节点
return 0;
}
static void chrdev_exit(void){
printk("chrdev exit\n");
device_destroy(classChr, devtChr); //一般遵循后创建的,先注销的顺序。就像先穿的衣服要最后脱
class_destroy(classChr);
cdev_del(&cdevChr);
unregister_chrdev_region( devtChr, MINOR_DEVICE_NUM);
}
module_init(chrdev_init);
module_exit(chrdev_exit);