瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】824412014(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第二篇 字符设备基础_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
第11章 创建设备节点实验
在上两个章节的学习中,我们已经成功的申请了设备号并且注册了相应的字符设备。系统通过设备号对设备进行查找,而字符设备注册到内核之后,并不能直接进行设备文件操作命令(打开、关闭、读、写等),需要相应的设备文件作为桥梁以此来进行设备的访问,在本章节将对如何创建设备节点进行学习。
11.1 创建设备节点
在Linux操作系统中一切皆文件,设备访问也是通过文件的方式来进行的,对于用来进行设备访问的文件称之为设备节点,设备节点被创建在/dev目录下,将内核中注册的设备与用户层进行链接,这样应用程序才能对设备进行访问。
根据设备节点的创建方式不同,分为了手动创建设备节点和自动创建设备节点,下面对两种设备节点创建方式进行介绍。
11.1.1 手动创建设备节点
使用mknod命令手动创建设备节点,mknod 命令格式为:
mknod NAME TYPE MAJOR MINOR
参数含义:
- NAME: 要创建的节点名称
- TYPE: b表示块设备,c表示字符设备,p表示管道
- MAJOR:要链接设备的主设备号
- MINOR: 要链接设备的从设备号
例如使用以下命令创建一个名为device_test的字符设备节点,链接设备的主设备号和从设备号分别为236和0:
mknod /dev/device_test c 236 0
11.1.2 自动创建设备节点
设备文件的自动创建是利用udev(mdev)机制来实现,多数情况下采用自动创建设备节点的方式。udev(mdev)可以检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。在驱动中首先使用class_create(…)函数对class进行创建,这个类存放于/sys/class/ 目录下,之后使用device_create(…)函数创建相应的设备,在进行模块加载时,用户空间中的udev会自动响应device_create()函数,寻找对应的类从而创建设备节点。
下面对于自动创建节点中所用到的函数进行解释说明:
class_create(…)函数
该函数在“内核源码/include/linux/device.h”文件中所引用(由于上一小节中引用的cdev.h文件已包含device.h,所以不需要再重复引用),如下(图11-1)所示:
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
函数作用:
用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进Linux内核系统。
参数含义:
owner:struct module结构体类型的指针,指向函数即将创建的这个struct
class的模块。一般赋值为THIS_MODULE。 name:char类型的指针,代表即将创建的struct class变量的名字。
返回值:struct class * 类型的结构体。
class_destroy(…)函数
该函数在“内核源码/include/linux/device.h”文件中所引用,如下(图11-2)所示:
extern void class_destroy(struct class *cls);
图 11-2
函数作用:
用于删除设备的逻辑类,即从Linux内核系统中删除设备的逻辑类。
参数含义:
owner:struct module结构体类型的指针,指向函数即将创建的这个struct
class的模块。一般赋值为THIS_MODULE。 name:char类型的指针,代表即将创建的struct class变量的名字。
返回值:无
device_create(…)函数
该函数在“内核源码/include/linux/device.h”文件中所引用,如下(图11-3)所示:
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
函数作用:
用来在class类中下创建一个设备属性文件,udev会自动识别从而进行设备节点的创建。
参数含义:
cls:指定所要创建的设备所从属的类。
parent:指定该设备的父设备,如果没有就指定为NULL。
devt:指定创建设备的设备号。
drvdata:被添加到该设备回调的数据,没有则指定为NULL。
fmt:添加到系统的设备节点名称。
返回值:struct device * 类型结构体
device_destroy(…)函数
在“内核源码/include/linux/device.h”文件中所引用,如下(图11-4)所示:
extern void device_destroy(struct class *cls, dev_t devt);
函数作用:
用来删除class类中的设备属性文件,udev会自动识别从而进行设备节点的删除。
参数含义:
cls:指定所要创建的设备所从属的类。
devt:指定创建设备的设备号。
返回值:无
至此,关于自动创建节点相关的函数就介绍完成了,会在下一小节中对于设备节点的自动创建进行相应实验程序的编写。
11.2 实验程序的编写
本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\06。
本章实验将编写Linux下的自动创建设备节点实验代码,首先采用自动申请设备号的方式进行设备号的申请,并对获取的主设备号与次设备号进行打印,之后对字符设备进行注册(file_operations结构体只填充owner 字段即可,会在下个章节对file_operations结构体进行讲解),最后自动对设备节点进行创建。
编写完成的chrdev_node.c代码如下(图11-5)所示
#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类型变量dev_num来表示设备号
struct cdev cdev_test;//定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备
struct file_operations cdev_fops_test = {
.owner = THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
};//定义file_operations结构体类型的变量cdev_test_ops
struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类
static int __init chrdev_fops_init(void)//驱动入口函数
{
int ret;//定义int类型的变量ret,用来对函数返回值进行判断
int major,minor;//定义int类型的主设备号major和次设备号minor
ret = alloc_chrdev_region(&dev_num,0,1,"chrdev_name");//自动获取设备号,设备名chrdev_name
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 minor is %d \n",major,minor);
cdev_init(&cdev_test,&cdev_fops_test);//使用cdev_init()函数初始化cdev_test结构体,并链接到cdev_test_ops结构>体
cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
ret = cdev_add(&cdev_test,dev_num,1); //使用cdev_add()函数进行字符设备的添加
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_test
device_create(class_test,NULL,dev_num,NULL,"device_test");//使用device_create进行设备的创建,设备名称为device_test
return 0;
}
static void __exit chrdev_fops_exit(void)//驱动出口函数
{
cdev_del(&cdev_test);//删除添加的字符设备cdev_test
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("topeet");//作者信息
相较于上一章节实验,本章节代码在入口函数中添加了自动创建设备节点相关代码,在驱动出口函数中添加了相应的删除设备节点相关代码(相关代码已加粗)。
需要注意的是,在进行设备节点添加时,类的创建要放在设备创建之前;在进行设备节点删除时,类的删除要放在设备删除之后。
11.3 运行测试
11.3.1 编译驱动程序
在上一小节中的chrdev_node.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下(图11-6)所示:
export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += chrdev_node.o #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操作
对于Makefile的内容注释已在上图添加,保存退出之后,来到存放chrdev_node.c和Makefile文件目录下,如下图(图11-7)所示:
然后使用命令“make”进行驱动的编译,编译完成如下图(图11-8)所示:
编译完生成 chrdev_node.ko目标文件,如下图(图11-9)所示:
至此我们的驱动模块就编译成功了,下面进行驱动的运行测试。
11.3.2 运行测试
开发板启动之后,使用以下命令进行驱动模块的加载,如下图(图11-10)所示:
insmod cdev.ko
可以看到动态申请设备号成功了,主设备号为236,次设备号为0,然后使用以下命令进行注册设备号的查看,如下图(图11-11)所示:
cat /proc/devices
可以看到主设备号236的设备名为chrdev_name,和驱动程序中设置的相同,证明我们的设备号注册成功了,然后使用以下命令对class目录进行查看,如下图(图11-12)所示:
ls /sys/class/
可以看到在驱动程序中创建的class_test 类已经被成功创建了,然后使用以下命令对class_test 目录进行查看,如下图(图11-12)所示:
ls /sys/class/class_test/
可以看到在驱动程序中创建的名为device_test的设备属性文件夹也被创建了,然后使用命令“ls /dev/device_test”对/dev目录进行查看,相应的设备节点也已经被自动创建了,如下图(图11-13)所示:
最后可以使用以下命令进行驱动的卸载,卸载完成如下图(图11-14)所示:
rmmod chrdev_node.ko