文章目录
- 前言
- 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