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

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

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

文章目录

  • 前言
  • 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