嵌入式Linux(9):字符设备驱动--自动创建设备节点

news/2025/3/14 16:57:22/

文章目录

  • 前言
  • 1、怎么自动创建一个设备节点?
  • 2、什么是mdev
  • 3、什么是udev?
  • 4、怎么自动创建设备节点?
  • 5、创建和删除类函数--自动生成类
    • 代码
  • 6、创建设备函数--自动生成节点
    • 代码

前言

在上一节中,使用insmod加载模块后,还需要通过mknod命令来手动创建设备节点,这样太麻烦了。需要加入自动创建设备节点的功能。

1、怎么自动创建一个设备节点?

在嵌入式Linux中使用mdev来实现设备节点文件的自动创建和删除。

2、什么是mdev

mdev是udev的简化版本,是busybox中所带的程序,最适合在嵌入式系统。

3、什么是udev?

udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中的真正存在的设备了。udev一般用在PC上的linux中,相对mdev来说要复杂一些。

4、怎么自动创建设备节点?

自动创建设备节点分为两个步骤:

步骤一:使用class_create函数来创建一个class的类。
步骤二:使用device_create函数在我们创建的类下面创建一个设备。

5、创建和删除类函数–自动生成类

在Linux驱动程序中一般通过两个函数来完成设备节点的创建和删除。首先要创建一个class类结构体。

class类结构体定义在includ/linux/device.h里面。class_create是类创建函数,class_create是个宏,内容:

#define class_create(owner,name)\
({\
static struct lock_class_key __key;\
__class_create(owner,name,&__key);\
})
struct class* __class_create(struct module *owner,const char* name,struct lock_class_key *key);

class_create一共有两个参数,参数owner一般为THIS_MODULE,参数name是类的名字。返回值是个指向结构体class的指针,也就是创建的类。

卸载驱动程序的时候需要删除掉类,类删除函数为class_destroy,函数原型为:

void class_destroy(struct class *cls);

代码

下面的代码模块只是在/sys/class/目录下生成一个chrdev_class的类文件。

#include <linux/init.h> // 包含宏定义
#include <linux/module.h> // 包含初始化、加载模块的头文件
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>#define DEVICE_NUMBER 1
#define DEVICE_SNAME   "schrdev"
#define DEVICE_ANAME   "achrdev"#define DEVICE_MINOR_NUMBER  0
#define DEVICE_CLASS_NAME    "chrdev_class"static int major_num, minor_num;struct cdev cdev;
struct class *class;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
};module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);static int hello_init(void)
{dev_t dev_num;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);ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);if(ret < 0){printk("register_chrdev_region error\n");}elseprintk("register_chrdev_region ok\n");}else{ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);if(ret <0){printk("alloc_chrdev_region error\n");}elseprintk("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);}printk("major_num = %d, minor_num = %d\n",major_num, minor_num);cdev.owner = THIS_MODULE;cdev_init(&cdev, &chrdev_ops);cdev_add(&cdev, dev_num, DEVICE_NUMBER);class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);return 0;
}
static void hello_exit(void)
{unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);cdev_del(&cdev);class_destroy(class);printk("Bye Bye\n");
}/* 模块的入口 */
module_init(hello_init);
/* 模块的出口 */
module_exit(hello_exit);/* 模块声明 */
MODULE_LICENSE("GPL");

上面代码模块就可以在/sys/class/目录下生成一个chrdev_class的类文件了。

6、创建设备函数–自动生成节点

当使用上的函数创建完成一个类后,使用device_create函数在这个类下创建一个设备。
device_create的函数原型如下:

struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt,...)

device_create是个可变参数函数

  • 参数class 就是设备要创建的那个类;
  • 参数parent 是父设备,一般为NULL,也就是没有父设备;
  • 参数devt 是设备号;
  • 参数drvdata 是设备可能会使用的一些数据,一般为NULL;
  • 参数fmt 是设备名字,如果设置 fmt=xxx 的话,就是生成/dev/xxx这个设备文件了。
  • 返回值就是创建好的设备。

同样的,在卸载驱动的时候需要删除掉创建的设备,设备删除函数为:device_destroy 函数原型为:

void device_destroy(struct class* class, dev_t devt);

参数class是要删除的设备所处的类,参数devt是要删除的设备号。

代码

下面的代码就可以自动在/sys/class下面生成一个chrdev_class类了,并且还自动生成了设备节点/dev/chrdev_test

#include <linux/init.h> // 包含宏定义
#include <linux/module.h> // 包含初始化、加载模块的头文件
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>#define DEVICE_NUMBER 1
#define DEVICE_SNAME   "schrdev"
#define DEVICE_ANAME   "achrdev"#define DEVICE_MINOR_NUMBER  0
#define DEVICE_CLASS_NAME    "chrdev_class"
#define DEVICE_NODE_NAME     "chrdev_test"static int major_num, minor_num;
dev_t dev_num;struct cdev cdev;
struct class *class;
struct device *device;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
};module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);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);ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);if(ret < 0){printk("register_chrdev_region error\n");}elseprintk("register_chrdev_region ok\n");}else{ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);if(ret <0){printk("alloc_chrdev_region error\n");}elseprintk("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);}printk("major_num = %d, minor_num = %d\n",major_num, minor_num);cdev.owner = THIS_MODULE;cdev_init(&cdev, &chrdev_ops);cdev_add(&cdev, dev_num, DEVICE_NUMBER);class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);device = device_create(class, NULL, dev_num, NULL, DEVICE_NODE_NAME);return 0;
}
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("Bye Bye\n");
}/* 模块的入口 */
module_init(hello_init);
/* 模块的出口 */
module_exit(hello_exit);/* 模块声明 */
MODULE_LICENSE("GPL");

Makefile

# 定义内核源码的目录
KERN_DIR ?= /home/liefyuan/Linux/rk356x_linux/kernel
# 定义当前目录
PWD        := $(shell pwd)
# 要生成的内核模块
obj-m += chrdev.oall:make -C $(KERN_DIR) M=$(PWD) modulesclean:rm -rf *.order *o *.symvers *.mod.c *.mod *.ko

编译:

export ARCH=arm64 
export CROSS_COMPILE=aarch64-linux-gnu-
make

测试验证:

[root@RK356X:/opt]# insmod chrdev.ko
[34481.427842] alloc_chrdev_region ok
[34481.427948] major_num: 236
[34481.427956] mino[root@RK356X:/optr]# _num: 0
[34481.427964] major_num = 236, minor_num = 0[root@RK356X:/opt]# ls /dev/chrdev_test
/dev/chrdev_test
[root@RK356X:/opt]# ls /sys/class
android_usb    drm          misc            regulator      tpm
ata_device     extcon       mmc_host        rfkill         tpmrm
ata_link       gpio         mpp_class       rkwifi         tty
ata_port       graphics     mtd             rtc            ubi
backlight      hidraw       net             scsi_device    udc
bdi            hwmon        nvme            scsi_disk      usbmon
block          i2c-adapter  nvme-subsystem  scsi_host      vc
bluetooth      i2c-dev      pci_bus         sound          video4linux
bsg            ieee80211    phy             spi_host       vtconsole
chrdev_class   input        power_supply    spi_master     wakeup
devcoredump    iommu        ppp             spi_transport  watchdog
devfreq        leds         pps             spidev         zram-control
devfreq-event  mdio_bus     ptp             tee
dma            mem          pwm             thermal
[root@RK356X:/opt]# rmmod chrdev
[34512.952629] Bye Bye

http://www.ppmy.cn/news/47751.html

相关文章

202306读书笔记|《与诗书在一起》——质而实绮,癯而实腴

《与诗书在一起》作者叶嘉莹&#xff0c;不错的一本书&#xff0c;大多只看了诗&#xff0c;部分读着比较有感觉的看了注释。 很喜欢的节选如下: 苏轼说陶渊明的诗“质而实绮&#xff0c;癯而实腴”&#xff0c;就是看起来它很简单质朴&#xff0c;内里实在是很美丽很丰富。 …

Spring注解开发

定义bean 我们先直接通过一张图来理解注解在Spring开发中如和定义bean&#xff1a; 那么我们既然加了注解&#xff0c;相当于定义了bean可是Spring的配置文件怎么知道他的存在&#xff0c;于是我们引入component-scan进行包扫描即为了让Spring框架能够扫描到写在类上的注解&…

C语言模拟银行排队叫号(链队)

一.队列 队列是一种具有先进先出&#xff08;FIFO&#xff09;特性的线性数据结构&#xff0c;它只允许在队列的两端进行插入和删除操作。队列的一端称为队尾&#xff08;rear&#xff09;&#xff0c;另一端称为队头&#xff08;front&#xff09;。新元素总是插入在队列的队…

单例设计模式所有情况解析

单例设计模式解析 &#x1f964;概述&#xff1a;单例设计模式是一种创建型设计模式&#xff0c;旨在确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问该实例。 一、饿汉式实现 &#x1f388;介绍&#xff1a;饿汉式是一种单例设计模式的实现方式&#xff0c;…

【LeetCode: 673. 最长递增子序列的个数 | 动态规划】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

《面试1v1》HashMap

没有人比中国人更懂 HashMap 我是 javapub&#xff0c;一名 Markdown 程序员从&#x1f468;‍&#x1f4bb;&#xff0c;八股文种子选手。 面试官&#xff1a;HashMap 是Java程序员用得最频繁的集合之一,可以给我简单介绍一下它的内部实现机制吗? 候选人&#xff1a; Hash…

华为OD机试真题(Java),数组合并(100%通过+复盘思路)

一、题目描述 现在有多组整数数组&#xff0c;需要将他们合并成一个新的数组。 合并规则从每个数组里按顺序取出固定长度的内容&#xff0c;合并到新的数组&#xff0c;取完的内容会删除掉。 如果改行不足固定长度&#xff0c;或者已经为空&#xff0c;则直接取出剩余部分的内…

Axios请求(对ajax的二次封装)——Axios API、Axios实例、请求配置、Axios响应结构

axios起步——介绍和使用基本用例post请求 场景复现核心干货axios APIaxios(config)axios(url[,config])请求方式别名 axios实例创建一个axios实例axios.create([config])实例方法 axios请求配置axios响应结构 场景复现 最近学习与前端相关的小程序时&#xff0c;接触了异步请…