驱动程序(创建设备节点实验)

一、创建设备节点

在Linux 操作系统中一切皆文件,对于用来进行设备访问的文件称之为设备节点,设备节点被创建在/dev 目录下,根据设备节点的创建方式不同,分为了手动创建设备节点和自动创建设备节点,下面分别对两种设备节点创建方式进行介绍。

1、手动创建设备节点

使用mknod命令手动创建设备节点,mknod命令格式为:

cpp 复制代码
mknod NAME TYPE MAJOR MINOR

参数含义:

NAME: 要创建的节点名称
TYPE: b 表示块设备, c 表示字符设备, p 表示管道
MAJOR :要链接设备的主设备号
MINOR: 要链接设备的从设备号
例如使用以下命令创建一个名为 device_test 的字符设备节点,设备的主设备号和从设备号分别为 236 和 0 。

cpp 复制代码
mknod /dev/device_test c 236 0

2、自动创建设备节点

自动创建设备节点是利用 udev 机制来实现的。udev 通过检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。自动创建设备节点需要在驱动中首先class_create()函数创建一个类,创建的类位于于 /sys/class/ 目录下,之后使用 device_create()函数在这个类下创建相应的设备,在加载驱动模块时,用户空间中的 udev 会自动响应根据并/sys/class/ 下的信息创建设备节点。
下面对于自动创建节点中所用到的函数进行解释说明:
class_create() 函数
该函数在定义在内核源码 /include/linux/device.h 文件中,代码如下 所示:

cpp 复制代码
#define class_create(owner, name) \
({                                   \
    static struct lock_class_key __key; \ 
        __class_create(owner, name, &__key); \
})

class_create() 函数详细介绍如下:

函数作用:
用于动态创建设备类。
参数含义:
owner : struct module 结构体类型的指针。一般赋值为 THIS_MODULE 。
name : char 类型的指针,代表即将创建的 struct class 变量的名字。
返回值: struct class * 类型的结构体。
class_destroy() 函数
该函数定义在内核源码 /include/linux/device.h 文件中,代码如下 所示:

cpp 复制代码
extern void class_destroy(struct class *cls);

class_destroy() 函数详细介绍如下:

函数作用:
用于删除设备类。
参数含义:
owner : struct module 结构体类型的指针,一般赋值为 THIS_MODULE 。
name : char 类型的指针,代表即将创建的 struct class 变量的名字。
返回值:无
device_create() 函数
该函数定义在内核源码 /include/linux/device.h 文件中,代码如下 所示:

cpp 复制代码
struct device *device_create(struct class *cls, struct device *parent, 
                        dev_t devt, void *drvdata, 
                                const char *fmt, ...);

device_create() 函数详细介绍如下:

函数作用:
用来在 class 类下创建一个设备文件。
参数含义:
cls :指定所要创建的设备所从属的类。
parent: 指定该设备的父设备,如果没有就指定为 NULL 。
devt: 指定创建设备的设备号。
drvdata: 被添加到该设备回调的数据,没有则指定为 NULL 。
fmt :添加到系统的设备节点名称。
返回值: struct device * 类型结构体
device_destroy() 函数
该函数在内核源码 /include/linux/device.h 文件中引用,代码如下 所示。

cpp 复制代码
extern void device_destroy(struct class *cls, dev_t devt);

device_destroy() 函数详细介绍如下:

函数作用:
用来删除 class 类中的设备。
参数含义:
cls :指定所要创建的设备所从属的类。
devt: 指定创建设备的设备号。
返回值:无
至此,关于自动创建节点相关的函数就介绍完成了,会在下一小节中对于设备节点的自动
创建进行相应实验程序的编写。

二、实验程序的编写

本实验采用自动申请设备号的方式进行设备号的申请,并对获取的主设备号与次设备号进行打印,之后对字符设备进行注册( file_operations 结构体只填充 owner 字段即可 ) ,最后自动创建设备节点。
实验代码 chrdev_node.c 代码下 所示:

cpp 复制代码
#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("moss");//作者信息

代码在入口函数中添加了自动创建设备节点相关代码,在驱动出口函数中添相应的删除设备节点相关代码。
需要注意的是,在进行设备节点添加时,类的创建要放在设备创建之前;在进行设备节点删除时,类的删除要放在设备删除之后。

三、运行测试

1、编译驱动程序

Makefile 文件内容代码如下 所示:

cpp 复制代码
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 操作

回到存放 chrdev_node.c 和 Makefile 文件目录下,使用命令 make 编译驱动程序,编译完成。

cpp 复制代码
make

编译完生成 chrdev_node.ko 目标文件。
至此我们的驱动模块就编译成功了,下面进行驱动的运行测试。

2、运行测试

开发板启动之后,使用 insmod cdev.ko 命令编译驱动程序,编译成如图 所示:

使用 ls/sys/class/ 命令查看在 class 目录下有无创建的 class_test 类。如图 所示:

上图可以看出在驱动程序中创建的 class_test 类已经被成功创建了,然后使用 ls /sys/class/class_test/命令查看 class 目录下的设备。如下图 所示。

可以看到在驱动程序中创建的名为 device_test 的设备文件夹已经被成功创建,使用命令 ls
/dev/device_test 对查看 /dev 目录,相应的设备节点也已经被自动创建了,如图 所示:

使用 rmmodchrdevnode.ko 命令可以卸载驱动,卸载完成如下图 所示:

相关推荐
热心市民R先生2 小时前
Ubuntu 22.04 下 IGH EtherCAT 主站永久性开机自启
linux·运维·服务器
源远流长jerry2 小时前
DPDK 19.08(Ubuntu 16.04)环境搭建
linux·运维·网络·ubuntu
Ha_To2 小时前
2026.1.14 Linux计划任务与进程
linux·运维·服务器
oMcLin2 小时前
如何在CentOS 7.9上配置并优化高并发视频流平台,利用Nginx和RTMP模块确保低延迟流媒体传输?
linux·nginx·centos
知南x2 小时前
【正点原子STM32MP157学习篇】A7和M4联合调试(通过STM32CubeIDE)
stm32·嵌入式硬件·学习
Suchadar2 小时前
Linux计划任务进程
linux·运维·服务器
食咗未2 小时前
Linux microcom工具的使用
linux·运维·服务器·驱动开发·串口调试
十五年专注C++开发2 小时前
CMake基础:foreach详解
linux·c++·windows·cmake·跨平台编译
天骄t2 小时前
UART通信全解析:从原理到实战
linux·单片机