【Linux驱动】字符设备驱动模板(四)—— 自动创建 / 删除设备节点

我们将设备驱动加载到内核以后,并不会在 /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");

参考文章:

Linux的设备驱动模型 - 知乎

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值