我们将设备驱动加载到内核以后,并不会在 /dev 目录下生成对应的设备节点文件,应用程序读写设备,本质就是在读写 /dev 下对应的设备节点文件。
因此,我们需要在加载到驱动的时候自动创建设备节点。这个就需要依靠 mdev 来实现,mdev 是一个简化版的udev,能够检测系统中的硬件设备状态,以此来创建或者删除设备文件。
一、设备所属类 API
1、创建类
一个设备可以有属于自己的类型,比如触摸、鼠标、键盘都属于输入设备,这就是一个最典型的类,可以在 /sys/class 看到其他类似的类。
内核中使用 class 结构体来表示一个类,创建一个类使用的原型如下(本质是一个宏)
/*
* @description: 创建一个类
* @param - owner
* @param - name
*/
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
extern struct class * __must_check __class_create(struct module *owner,
const char *name,
struct lock_class_key *key);
owner:一般为 THIS_MODULE
name:类的名字,调用成功以后,会在 /sys/class 目录下生成对应的类目录
返回值:返回一个class结构体的指针
#define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
static struct class* _class;
_class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
if (IS_ERR(_class)) // 判断指针是否有效,IS_ERR 在 #include <linux/err.h> 中定义
{
return PTR_ERR(chrdev.class);
}
2、销毁类
销毁一个类会将类目录从 /sys/class 下移除,原型如下。参数 cls 就代表要销毁的类
void class_destroy(struct class *cls);
二、设备节点 API
1、创建设备节点
创建一个设备节点会在对应的类目录下生成一个设备目录,同时也会在 /dev 目录下生成对应的设备节点文件。
内核中使用 device 结构体表示设备节点,创建设备所使用的原型如下:
struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)
class:该设备节点所属类
parent:表示父设备,一般为NULL,代表没有父设备
devt:设备号
drvdata:设备所需的数据,一般为NULL
fmt:设备名字,如果设置fmt=xxx,就会生成 /dev/xxx 这个设备文件
返回值:创建号的设备节点指针
#define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
static dev_t devid; /* 设备号(已分配过) */
static struct class* _class; /* 设备所属类 */
static struct device* deviceNode; /* 设备节点 */
_class= class_create(THIS_MODULE, CHRDEVBASE_NAME);
deviceNode = device_create(_class, NULL, devid, NULL, CHRDEVBASE_NAME);
if (IS_ERR(chrdev.deviceNode))
{
return PTR_ERR(chrdev.deviceNode);
}
2、移除设备节点
卸载驱动时需要删除掉创建的设备,/dev/xxx 下对应的设备文件也会被删除,函数原型如下:
void device_destroy(struct class *class, dev_t devt);
class:设备节点所属类
devt:设备号
三、驱动模块代码
加载模块时,无需手动创建,/dev 目录下就会自动出现我们设置的节点名称
卸载模块时,无需手动删除 /dev 目录下的节点文件
#include <linux/module.h> // MODULE_LICENSE、MODULE_AUTHOR
#include <linux/init.h> // module_init、module_exit
#include <linux/printk.h> // printk
#include <linux/kdev_t.h> // MKDEV
#include <linux/fs.h> // register_chrdev_region
#include <linux/cdev.h> // cdev_init、cdev_add
#include <linux/device.h> // class_create、device_create
#include <linux/err.h> // IS_ERR
#define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
static struct chrdev_t{
dev_t devid; /* 设备号(由主设备号和次设备号组成) */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev dev; /* 字符设备 */
struct class* class; /* 设备节点所属类 */
struct device* deviceNode; /* 字符设备节点 */
} chrdev;
/*
* 设备操作函数结构体
*/
static struct file_operations chrdevfops = {
.owner = THIS_MODULE,
// .open = chrdevbase_open, // 将chrdevbase_open的函数地址传递给 .open 成员
// .read = chrdevbase_read,
// .write = chrdevbase_write,
// .release = chrdevbase_release,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 0 成功;其他 失败
*/
static int __init chrdevbase_init(void)
{
int ret = 0;
printk("chrdevbase init!\n");
/* 1、注册设备号 */
if (!chrdev.major)
{
ret = alloc_chrdev_region(&chrdev.devid, 0, 1, CHRDEVBASE_NAME);
chrdev.major = MAJOR(chrdev.devid);
chrdev.minor = MINOR(chrdev.devid);
}
else
{
chrdev.devid = MKDEV(chrdev.major, 0);
ret = register_chrdev_region(chrdev.devid, 1, CHRDEVBASE_NAME);
}
/* 2、初始化字符设备 */
chrdev.dev.owner = THIS_MODULE;
cdev_init(&chrdev.dev, &chrdevfops);
/* 3、加载字符设备 */
ret = cdev_add(&chrdev.dev, chrdev.devid, 1);
if (ret != 0)
{
return -1;
}
/* 4、 创建设备所属类*/
chrdev.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
if (IS_ERR(chrdev.class))
{
return PTR_ERR(chrdev.class);
}
chrdev.deviceNode = device_create(chrdev.class, NULL, chrdev.devid, NULL, CHRDEVBASE_NAME);
if (IS_ERR(chrdev.deviceNode))
{
return PTR_ERR(chrdev.deviceNode);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit chrdevbase_exit(void)
{
printk("chrdevbase exit!\n");
/* 释放设备号 */
unregister_chrdev_region(chrdev.devid, 1);
/* 卸载设备号 */
cdev_del(&chrdev.dev);
/* 删除节点 */
device_destroy(chrdev.class, chrdev.devid);
/* 删除节点所属类 */
class_destroy(chrdev.class);
}
/*
* 将上面两个函数指定为驱动的入口和出口函数
*/
module_init(chrdevbase_init); // 注册 ko模块被加载到内核,系统会调用的函数
module_exit(chrdevbase_exit); // 注册 ko模块从内核卸载,系统会调用的函数
/*
* LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");
参考文章: