驱动运行有两种方式:
这里我们采用第二种方式,方便调试,只需加载驱动,无需编译内核,等确定没问题以后再根据需要编译到内核。
目录
一、驱动模块的加载 / 卸载
二、注册字符设备
1、注册设备号
2、创建并加载字符设备
3、注销设备号、卸载字符设备
三、自动创建字符设备节点
1、创建类、设备节点
2、类目录、设备节点的移除
四、整理:module_init、module_exit 函数模板
驱动模块被加载或者卸载,可能需要有一些初始化或者收尾,这就需要内核提供的宏
// 使用 __init 修饰
static int __init chrdevbase_init(void)
{
/* 驱动入口实现 */
return 0;
}
// 使用 __exit 修饰
static void __exit chrdevbase_exit(void)
{
/* 驱动出口实现 */
}
module_init(xxx_init); //注册 “模块入口函数”
module_exit(xxx_exit); //注册 “模块出口函数”
注册字符设备相当于告诉内核,希望他帮你管理这个字符设备,应用程序读写字符设备文件时,就会自动调用该字符设备对应的 read / write 函数。
除了设备号外,后续还有字符设备注册、设备节点的创建,为了方便管理,我们把这些统一放到一个结构体中。
struct chrdev_led_t{
struct class* class; /* 设备节点所属类 */
struct device* device; /* 设备节点 */
struct cdev dev; /* 字符设备 */
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
static struct chrdev_led_t chrdev_led;
注册设备号主要是判断之前是否已经注册过设备号
if (chrdev_led.major)
{
chrdev_led.devid = MKDEV(chrdev_led.major, 0);
ret = register_chrdev_region(chrdev_led.devid, 1, CHRDEVBASE_NAME);
}
else
{
ret = alloc_chrdev_region(&chrdev_led.devid, 0, 1, CHRDEVBASE_NAME);
chrdev_led.major = MAJOR(chrdev_led.devid);
chrdev_led.minor = MINOR(chrdev_led.devid);
}
注意:在 module_init 中注册设备号,同时需要在 module_exit 中释放设备号
我们需要将字符设备以及设备号一起加载到内核,字符设备才算注册完成,在加载到内核之前,我们需要先对字符设备做一个初始化。
/*
* 设备操作函数结构体
*/
static struct file_operations chrdevbase_fops = {
.owner = THIS_MODULE,
.open = chrdevbase_open,
.read = chrdevbase_read,
.write = chrdevbase_write,
.release = chrdevbase_release,
};
/* 2. 初始化字符设备 */
chrdev_led.dev.owner = THIS_MODULE;
cdev_init(&chrdev_led.dev, &chrdevbase_fops); // 初始化字符设备
/* 3. 将字符设备添加到内核 */
cdev_add(&chrdev_led.dev, chrdev_led.devid, 1); // 将字符设备添加到内核
注意:在 module_init 中加载字符设备,同时需要在 module_exit 中卸载字符设备
当驱动被卸载时,我们当然也需要做一些收尾工作
/* 注销字符设备 */
unregister_chrdev_region(chrdev_led.devid, 1); // 注销设备号
cdev_del(&chrdev_led.dev); // 卸载字符设备
该部分涉及到的函数可参考:自动创建 / 删除设备节点
将设备驱动加载到内核以后,并不会在 /dev 目录下生成设备文件,所以需要自己实现设备文件创建的逻辑。创建设备文件包含两部分:
/* 自动创建设备节点 */
// 设备节点所属类
chrdev_led.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
if (IS_ERR(chrdev_led.class))
{
goto node_create_err;
}
// 创建设备节点
chrdev_led.device = device_create(chrdev_led.class, NULL, chrdev_led.devid, NULL, CHRDEVBASE_NAME);
if (IS_ERR(chrdev_led.device))
{
goto node_create_err;
}
卸载驱动时,要将设备目录和设备节点一并移除,删除的顺序和创建顺序相反:
device_destroy(chrdev_led.class, chrdev_led.devid); // 删除节点
class_destroy(chrdev_led.class); // 删除类
#define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
struct chrdev_led_t{
struct class* class; /* 设备节点所属类 */
struct device* device; /* 设备节点 */
struct cdev dev; /* 字符设备 */
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
static struct chrdev_led_t chrdev_led;
/*
* 设备操作函数结构体
*/
static struct file_operations chrdevbase_fops = {
.owner = THIS_MODULE,
.open = chrdevbase_open,
.read = chrdevbase_read,
.write = chrdevbase_write,
.release = chrdevbase_release,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 0 成功;其他 失败
*/
static int __init chrdevbase_init(void)
{
int ret = 0;
/* 1. 注册设备号 */
if (chrdev_led.major)
{
chrdev_led.devid = MKDEV(chrdev_led.major, 0);
ret = register_chrdev_region(chrdev_led.devid, 1, CHRDEVBASE_NAME);
}
else
{
ret = alloc_chrdev_region(&chrdev_led.devid, 0, 1, CHRDEVBASE_NAME);
chrdev_led.major = MAJOR(chrdev_led.devid);
chrdev_led.minor = MINOR(chrdev_led.devid);
}
/* 2. 初始化字符设备 */
chrdev_led.dev.owner = THIS_MODULE;
cdev_init(&chrdev_led.dev, &chrdevbase_fops); // 初始化字符设备
/* 3. 将字符设备添加到内核 */
cdev_add(&chrdev_led.dev, chrdev_led.devid, 1); // 将字符设备添加到内核
/* 自动创建设备节点 */
// 设备节点所属类
chrdev_led.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
if (IS_ERR(chrdev_led.class))
{
return PTR_ERR(chrdev_led.class);
}
// 创建设备节点
chrdev_led.device = device_create(chrdev_led.class, NULL, chrdev_led.devid, NULL, CHRDEVBASE_NAME);
if (IS_ERR(chrdev_led.device))
{
return PTR_ERR(chrdev_led.device);
}
printk("chrdevbase init!\n");
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit chrdevbase_exit(void)
{
/* 注销字符设备 */
unregister_chrdev_region(chrdev_led.devid, 1); // 注销设备号
cdev_del(&chrdev_led.dev); // 卸载字符设备
device_destroy(chrdev_led.class, chrdev_led.devid); // 删除节点
class_destroy(chrdev_led.class); // 删除类
printk("chrdevbase exit!\n");
}
/*
* 将上面两个函数指定为驱动的入口和出口函数
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
/*
* LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");