字符设备相关的知识面:已经了解了 申请设备号、注册字符设备。 已经将字符设备添加进入内核了,那么如何使用这个字符设备呢?
Linux 里面一切皆文件,对内核中的字符设备进行文件操作如打开、关闭、读、写等,但是并不支持哦。 需要相应的设备文件作为桥梁进行设备的访问,
这里需要了解知识:创建设备节点
文章目录
- 自动创建设备节点
- udev 知识了解
- 动态创建设备节点
- 自动加载驱动和触发动作
- 支持热插拔(Hotplug)
- 创建设备节点
- class_create() - 创建设备类
- device_create() - 创建设备节点
- 销毁 class_destroy
- 销毁 device_destroy
- 实验
- 源码程序 chrdev_node
- 源码分析
- Makefile 编译文件
- 运行测试
- insmod dmesg | tail
- cat proc/devices
- ls /sys/class/ ls /sys/class/class_test/
- ls /dev/device_test
- 总结
自动创建设备节点
针对手动创建设备节点,这里不概述、总结,重点是自动创建设备节点。
udev 知识了解
udev 知识了解:
在 Linux 系统中,udev(用户空间设备管理,Userspace Device Manager)是一个动态设备管理工具,负责在 /dev 目录下自动管理设备节点。它是现代 Linux 系统的核心组件,取代了传统的静态 /dev 目录管理和早期的 devfs。
udev 的核心作用:
动态创建设备节点
- 当硬件设备插入(如 USB、硬盘、网卡等)或系统启动时,udev 会根据内核发出的 uevent 动态创建对应的设备文件(如 /dev/sda、/dev/ttyUSB0)。
- 设备移除时,自动清理对应的设备节点。
自动加载驱动和触发动作
- 支持在设备插入时自动加载内核模块(驱动)。
- 触发自定义脚本(如挂载磁盘、通知用户)。
支持热插拔(Hotplug)
实时响应硬件插拔事件,适用于移动设备、外接存储等。
创建设备节点
设备文件的自动创建是利用 udev(mdev)机制来实现, 多数情况下采用自动创建设备节点
的方式。 udev(mdev)可以检测系统中硬件设备状态, 可以根据系统中硬件设备状态来创建或者
删除设备文件。 在驱动中首先使用 class_create(…)函数对 class 进行创建, 这个类存放于
/sys/class/ 目录下, 之后使用 device_create(…)函数创建相应的设备, 在进行模块加载时, 用户
空间中的 udev 会自动响应 device_create()函数, 寻找对应的类从而创建设备节点
class_create() - 创建设备类
struct class *class_create(struct module *owner, const char *name);
功能:创建一个设备类,将在/sys/class/下出现对应的目录
参数:
- owner:struct module 结构体类型的指针, 指向函数即将创建的这个 struct class 的模块。
一般赋值为 THIS_MODULE - name:类名,将出现在/sys/class/下 ;char 类型的指针, 代表即将创建的 struct class 变量的名字
- 返回:成功返回指向class结构的指针,失败返回ERR_PTR
device_create() - 创建设备节点
struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...);
功能:在指定类下创建设备,触发udev在/dev下创建设备节点
参数:
-
cls:从class_create()返回的类指针 ,指定所要创建的设备所从属的类
-
parent:父设备,通常为NULL
-
devt:设备号(主设备号+次设备号)
-
drvdata:驱动私有数据,通常为NULL
-
fmt:设备名格式字符串(类似printf)
-
返回:成功返回device指针,失败返回ERR_PTR;struct device * 类型结构体
销毁 class_destroy
用于删除设备的逻辑类, 即从 Linux 内核系统中删除设备的逻辑类。
销毁 device_destroy
extern void device_destroy(struct class *cls, dev_t devt);
用来删除 class 类中的设备属性文件, udev 会自动识别从而进行设备节点的删除。
实验
源码程序 chrdev_node
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>static dev_t dev_num;//定义dev_t类型(32位大小)的变量dev_num
struct cdev cdev_test; //定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备static struct file_operations cdev_test_ops = {.owner=THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
};//定义file_operations结构体类型的变量cdev_test_opsstruct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类static int __init chrdev_fops_init(void)//驱动入口函数
{int ret;//定义int类型的变量ret,用来判断函数返回值int major,minor;//定义int类型的主设备号major和次设备号minorret=alloc_chrdev_region(&dev_num,0,1,"chardev_num"); //通过动态方式进行设备号注册if(ret < 0){printk("alloc_chrdev_region is error\n");} printk("alloc_chrdev_region is ok\n");major=MAJOR(dev_num);//通过MAJOR()函数进行主设备号获取minor=MINOR(dev_num);//通过MINOR()函数进行次设备号获取printk("major is %d\n",major);printk("minor is %d\n",minor);使用cdev_init()函数初始化cdev_test结构体,并链接到cdev_test_ops结构体cdev_init(&cdev_test,&cdev_test_ops);cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块 ret= cdev_add(&cdev_test,dev_num,1);if(ret < 0 ){printk("cdev_add is error\n");}printk("cdev_add is ok\n");class_test = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_testdevice_create(class_test,NULL,dev_num,NULL,"device_test");//使用device_create进行设备的创建,设备名称为device_testreturn 0;
}
static void __exit chrdev_fops_exit(void)//驱动出口函数
{cdev_del(&cdev_test);//使用cdev_del()函数进行字符设备的删除unregister_chrdev_region(dev_num,1);//释放字符驱动设备号 device_destroy(class_test,dev_num);//删除创建的设备class_destroy(class_test);//删除创建的类printk("module exit \n");}
module_init(chrdev_fops_init);//注册入口函数
module_exit(chrdev_fops_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//同意GPL开源协议
MODULE_AUTHOR("wang fang chen "); //作者信息
源码分析
使用到了前面的基础知识点
- dev_t dev_num :设备号变量声明,dev_t 类型
- cdev cdev_test : 字符设备结构体声明
- file_operations cdev_test_ops :声明 定义 函数指针的集合 结构体
- alloc_chrdev_region :动态注册设备号
- MAJOR 、MINOR 通过设备号获取主设备号和次设备号
- cdev_init :初始化 字符设备
- cdev_add :字符设备添加到内核
- class_create :进行类的创建 : 位置 /sys/class/
- device_create :进行设备的创建,在声明 类 目录下生成device 设备,同步通过udev 机制,自动在 /dev 下生成设备节点
Makefile 编译文件
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += chrdev_node.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:make -C $(KDIR) M=$(PWD) modulesclean:make -C $(KDIR) M=$(PWD) clean
运行测试
insmod dmesg | tail
[root@topeet:/mnt/sdcard]# insmod chrdev_node.ko
[root@topeet:/mnt/sdcard]# dmesg | tail
[29383.739997] dwc3 fcc00000.dwc3: device reset
[29383.845540] android_work: sent uevent USB_STATE=CONNECTED
[29383.888321] configfs-gadget gadget: high-speed config #1: b
[29383.888611] android_work: sent uevent USB_STATE=CONFIGURED
[29592.130493] adbd (13142): /proc/13142/oom_adj is deprecated, please use /proc/13142/oom_score_adj instead.
[30279.336659] chrdev_node: loading out-of-tree module taints kernel.
[30279.337604] alloc_chrdev_region is ok
[30279.337626] major is 236
[30279.337643] minor is 0
[30279.337662] cdev_add is ok
程序日志,内核打印如上
cat proc/devices
结果如下,如前面 字符设备注册和设备号申请内容,说明设备已经注册到内核了。
ls /sys/class/ ls /sys/class/class_test/
结果如下,
- /sys/class/ 目录下生成了class_test 类
- /sys/class/class_test/ 目录下生成了device_test 设备
ls /dev/device_test
结果如下:在dev 下面生成了device_test 设备,通过udev 机制自动生成的。
总结
- 字符设备知识点中的 设备节点创建内容熟悉
- 结合以前的知识,字符设备设备号申请,字符设备注册内容进一步熟悉
- 设备注册、类、设备创建后 在驱动卸载的时候记得 卸载