定义
udev与mdev
udev
是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。
- 设备文件通常放在
/dev
目录下。 - 使用
udev
后,在/dev
目录下就只包含系统中真正存在的设备。
mdev
是udev
的简化版本,是busybox
中所带的程序,最适合用在嵌入式系统
udev
一般用在 PC 上的linux
中,相对mdev
来说要复杂些- 所以在嵌入式
Linux
中使用mdev
来实现设备节点文件的自动创建和删除。
自动创建节点步骤
- 使用
class_create
函数创建一个类 - 使用
device_create
函数在创建的类中创建对应的设备
相关函数
创建和删除类函数
struct class
结构体用于表示一个设备类。设备类是一组具有相同属性或行为的设备的集合。
struct class
定义
#include<linux/device.h>
/**
* @brief 表示一个设备类的内核结构体
*
* 结构体 class 用于定义和管理一个设备类。设备类可以包含多个设备,这些设备
* 通常具有相似的属性或行为。例如,所有的字符设备可以属于一个名为"char"的设备类。
* 每个设备类都有一个与之关联的类名,并且可以有一个与之关联的设备模型。
*
* @note 设备类通常在驱动程序初始化时创建,并在驱动程序卸载时销毁。
*/
struct class {
const char *name; ///< 类名,用于标识设备类
struct module *owner; ///< 拥有此类的模块,用于模块归属和引用计数
struct class_attribute *class_attrs; ///< 类属性,用于定义类级别的属性
struct device *dev; ///< 类的设备实例,指向第一个设备
struct kobject kobj; ///< 内核对象,用于内核对象管理
int (*dev_release)(struct device *dev); ///< 设备释放函数,用于释放设备资源
struct attribute_group **groups; ///< 属性组,用于定义类和设备属性的集合
// 其他成员变量和函数指针...
};
内核同时提供了class_create用来创建一个类,这个类存放于sysfs下面
- 一旦创建好了这个类,再调用
device_create
在/dev
目录下创建相应的设备节点。 - 加载模块的时候,用户空间中的
udev
会自动响应device_create
,去/sysfs
下寻找对应的类从而创建设备节点。 - 在 Linux 驱动程序中一般通过
class_create
和class_destroy
来完成设备节点的创建和删除。 class_create
/**
* @brief 创建一个新的设备类
*
* 该函数用于在内核中创建一个新的设备类。设备类是具有相同特性或用途的设备的集合。
* 创建成功后,返回一个指向新创建的设备类的指针,该指针可以用于后续的设备管理操作。
*
* @param owner 指向创建该类的模块,通常为THIS_MODULE,表示该类所属的模块
* @param name 类的名称,用于唯一标识一个设备类
* @return 返回指向新创建的设备类的指针,如果创建失败则返回NULL
*/
struct class *class_create(struct module *owner, const char *name);
class_destroy
/**
* @brief 删除一个设备类
*
* 该函数用于删除一个已经存在的设备类。在驱动程序卸载时,应当使用此函数来清理
* 之前创建的设备类,释放相关资源。
*
* @param cls 指向要删除的设备类的指针,该指针通常由class_create函数返回
*/
void class_destroy(struct class *cls);
创建和删除设备函数
device_create
/**
* @brief 在指定的设备类中创建一个新的设备实例
*
* 该函数用于在指定的设备类中创建一个新的设备实例,并将其添加到内核的设备树中。
* 创建的设备实例会与提供的设备号(dev_t)关联,并可以包含一个指向驱动数据的指针。
* 此外,还可以为设备指定一个父设备,以及一个格式化的设备名称。
*
* @param class 指向设备类的指针,该类是新设备所属的类别
* @param parent 指向父设备的指针,可以为NULL,表示没有父设备
* @param devt 设备号,用于唯一标识设备
* @param drvdata 指向驱动私有数据的指针,可以为NULL
* @param fmt 设备名称的格式化字符串
* @param ... 格式化字符串的参数
* @return 返回指向新创建的设备实例的指针,如果创建失败则返回NULL
*/
struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...);
device_destroy
/**
* @brief 删除指定类中的设备实例
*
* 该函数用于删除之前通过device_create()创建的设备实例。当设备不再被使用或者
* 驱动程序卸载时,应当调用此函数来移除设备实例,并释放相关资源。
*
* @param class 指向设备所属类的指针。这个类应该与创建设备时使用的类相同。
* @param devt 设备的设备号(dev_t),用于唯一标识要删除的设备。
*/
void device_destroy(struct class *class, dev_t devt);
示例
创建类函数
- 完整代码
/*
* @Description:字符设备自动创建设备节点步骤一创建类,创建设备
*/
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/fs.h> //包含了文件操作相关struct的定义,例如大名鼎鼎的struct file_operations
#include <linux/kdev_t.h>
#include <linux/cdev.h> //对字符设备结构cdev以及一系列的操作函数的定义。//包含了cdev 结构及相关函数的定义。
#define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
#include <linux/device.h> //包含了device、class 等结构的定义
#define DEVICE_CLASS_NAME "chrdev_class"
#define DEVICE_NODE_NAME "chrdev_test" //宏定义设备节点的名字
static int major_num, minor_num; //定义主设备号和次设备号
struct class *class; /* 类 */
struct cdev cdev; //定义一个cdev结构体
module_param(major_num, int, S_IRUSR); //驱动模块传入普通参数major_num
module_param(minor_num, int, S_IRUSR); //驱动模块传入普通参数minor_num
dev_t dev_num; /* 设备号 */
/**
* @description: 打开设备
* @param {structinode} *inode:传递给驱动的 inode
* @param {structfile} *file:设备文件,file 结构体有个叫做 private_data 的成员变量,
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return: 0 成功;其他 失败
*/
int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open\n");
return 0;
}
// 设备操作函数结构体
struct file_operations chrdev_ops = {
.owner = THIS_MODULE,
.open = chrdev_open};
/**
* @description: 驱动入口函数
* @param {*}无
* @return {*} 0 成功;其他 失败
*/
static int hello_init(void)
{
int ret; //函数返回值
if (major_num)
{
/*静态注册设备号*/
printk("major_num = %d\n", major_num); //打印传入进来的主设备号
printk("minor_num = %d\n", minor_num); //打印传入进来的次设备号
dev_num = MKDEV(major_num, minor_num); //MKDEV将主设备号和次设备号合并为一个设备号
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); //注册设备号
if (ret < 0)
{
printk("register_chrdev_region error\n");
}
printk("register_chrdev_region ok\n"); //静态注册设备号成功
}
else
{
/*动态注册设备号*/
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, 1, DEVICE_ANAME);
if (ret < 0)
{
printk("alloc_chrdev_region error\n");
}
printk("alloc_chrdev_region ok\n"); //动态注册设备号成功
major_num = MAJOR(dev_num); //将主设备号取出来
minor_num = MINOR(dev_num); //将次设备号取出来
printk("major_num = %d\n", major_num); //打印传入进来的主设备号
printk("minor_num = %d\n", minor_num); //打印传入进来的次设备号
}
// 初始化 cdev
cdev.owner = THIS_MODULE;
cdev_init(&cdev, &chrdev_ops);
// 向系统注册设备
cdev_add(&cdev, dev_num, DEVICE_NUMBER);
// 创建 class 类
class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
return 0;
}
/**
* @description: 驱动出口函数
* @param {*}无
* @return {*}无
*/
static void hello_exit(void)
{
//注销设备号
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
//删除设备
cdev_del(&cdev);
//删除类
class_destroy(class);
printk("gooodbye! \n");
}
// 将上面两个函数指定为驱动的入口和出口函数
module_init(hello_init);
module_exit(hello_exit);
// LICENSE 和作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");
- makefile
obj-m += chrdev.o
KDIR:=/home/topeet/linux/linux-imx
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm64
clean:
make -C $(KDIR) M=$(PWD) clean
在创建类成功后,会在
/sys/class
生成名为chrdev_class
的类
- 使用
ls /sys/class
查看 - 使用
insmod chrdev.ko
ls /sys/class
加载驱动并查看设备类
创建设备函数
- 在创建类函数的基础上创建设备函数
/*
* @Description:字符设备自动创建设备节点步骤一创建类,创建设备
*/
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/fs.h> //包含了文件操作相关struct的定义,例如大名鼎鼎的struct file_operations
#include <linux/kdev_t.h>
#include <linux/cdev.h> //对字符设备结构cdev以及一系列的操作函数的定义。//包含了cdev 结构及相关函数的定义。
#define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
#include <linux/device.h> //包含了device、class 等结构的定义
#define DEVICE_CLASS_NAME "chrdev_class"
#define DEVICE_NODE_NAME "chrdev_test" //宏定义设备节点的名字
static int major_num, minor_num; //定义主设备号和次设备号
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct cdev cdev; //定义一个cdev结构体
module_param(major_num, int, S_IRUSR); //驱动模块传入普通参数major_num
module_param(minor_num, int, S_IRUSR); //驱动模块传入普通参数minor_num
dev_t dev_num; /* 设备号 */
/**
* @description: 打开设备
* @param {structinode} *inode:传递给驱动的 inode
* @param {structfile} *file:设备文件,file 结构体有个叫做 private_data 的成员变量,
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return: 0 成功;其他 失败
*/
int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open\n");
return 0;
}
// 设备操作函数结构体
struct file_operations chrdev_ops = {
.owner = THIS_MODULE,
.open = chrdev_open};
/**
* @description: 驱动入口函数
* @param {*}无
* @return {*} 0 成功;其他 失败
*/
static int hello_init(void)
{
int ret; //函数返回值
if (major_num)
{
/*静态注册设备号*/
printk("major_num = %d\n", major_num); //打印传入进来的主设备号
printk("minor_num = %d\n", minor_num); //打印传入进来的次设备号
dev_num = MKDEV(major_num, minor_num); //MKDEV将主设备号和次设备号合并为一个设备号
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); //注册设备号
if (ret < 0)
{
printk("register_chrdev_region error\n");
}
printk("register_chrdev_region ok\n"); //静态注册设备号成功
}
else
{
/*动态注册设备号*/
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, 1, DEVICE_ANAME);
if (ret < 0)
{
printk("alloc_chrdev_region error\n");
}
printk("alloc_chrdev_region ok\n"); //动态注册设备号成功
major_num = MAJOR(dev_num); //将主设备号取出来
minor_num = MINOR(dev_num); //将次设备号取出来
printk("major_num = %d\n", major_num); //打印传入进来的主设备号
printk("minor_num = %d\n", minor_num); //打印传入进来的次设备号
}
// 初始化 cdev
cdev.owner = THIS_MODULE;
cdev_init(&cdev, &chrdev_ops);
// 向系统注册设备
cdev_add(&cdev, dev_num, DEVICE_NUMBER);
// 创建 class 类
class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
// 在 class 类下创建设备
device = device_create(class, NULL, dev_num, NULL, DEVICE_NODE_NAME);
return 0;
}
/**
* @description: 驱动出口函数
* @param {*}无
* @return {*}无
*/
static void hello_exit(void)
{
//注销设备号
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
//删除设备
cdev_del(&cdev);
//注销设备
device_destroy(class, dev_num);
//删除类
class_destroy(class);
printk("gooodbye! \n");
}
// 将上面两个函数指定为驱动的入口和出口函数
module_init(hello_init);
module_exit(hello_exit);
// LICENSE 和作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");
添加对应的
app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd;
char buf[64] = {0};
fd = open("/dev/chrdev_test",O_RDWR); //打开设备节点
if(fd < 0)
{
perror("open error \n");
return fd;
}
//read(fd,buf,sizeof(buf)); //从文件中读取数据放入缓冲区中
close(fd);
return 0;
}
- 首先卸载驱动
insmod chrdev.ko
- 查看是否生成类
ls /sys/class/chrdev_class/
- 查看是否生成设备节点
ls /dev/chrdev_test
验证生成的设备节点是否可以使用
./app