1、前言
承接上一篇文章Linux驱动(一)之最简单的驱动程序,对创建设备和节点进行优化,实现自行创建。
2、优化
使用alloc_chrdev_region动态分配字符设备号;使用cdev_add 将字符设备驱动程序注册到内核中;使用class_create 创建设备类,使用 device_create 创建设备节点。
2.1 alloc_chrdev_region
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name);
dev:用于接收分配的字符设备号。
firstminor:表示分配的字符设备号的次设备号的起始值。
count:表示要分配的字符设备号的数量。
name:表示设备的名称。
2.2 cdev_add
int cdev_add(struct cdev *p, dev_t dev, unsigned int count);
p:指向 struct cdev 结构体的指针,表示要注册的字符设备驱动程序。
dev:表示要注册的字符设备号。
count:表示设备号的数量。
cdev_add 函数在注册成功时返回 0,否则返回一个负数错误码。
2.3 class_create
struct class *class_create(struct module *owner, const char *name);
owner:指向内核模块的指针。
name:表示设备类的名称。
class_create 函数在成功创建设备类后,会返回一个指向 struct class 结构体的指针。如果创建失败,会返回一个 NULL 指针。
2.4 device_create
struct device *device_create(struct class *class, struct device *parent, dev_t dev, void *drvdata, const char *fmt, ...);
class:指向设备类的指针。
parent:指向父设备的指针。
dev:表示要创建的设备节点的设备号。
drvdata:指向设备驱动程序的私有数据的指针。
fmt:表示设备节点的名称格式。
device_create 函数在成功创建设备节点后,会返回一个指向 struct device 结构体的指针。如果创建失败,会返回一个 NULL 指针
优化后代码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
/*
1. 构造file_operations
2. 填充file_operations
3. 注册file_operations
4. 编写入口和出口
*/
dev_t dev_num; // 设备号
struct cdev char_cdev; // 字符设备的 cdev 结构
struct class *char_class; // 设备类的 class 结构
struct device *mychar_device; // 设备的 device 结构
int my_dev_open(struct inode * inode, struct file * file)
{
printk("my_dev_open\n");
return 0;
}
ssize_t my_dev_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{
printk("my_dev_write\n");
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = my_dev_open,
.write = my_dev_write
};
int my_dev_init(void)
{
int ret;
// 分配设备号
ret = alloc_chrdev_region(&dev_num, 0, 1, "chardev");
if (ret < 0)
{
printk(KERN_ERR "alloc_chrdev_region\n");
return ret;
}
// 初始化 cdev 结构
cdev_init(&char_cdev, &fops);
char_cdev.owner = THIS_MODULE;
// 添加字符设备到系统
ret = cdev_add(&char_cdev, dev_num, 1);
if (ret < 0)
{
printk(KERN_ERR "cdev_add\n");
unregister_chrdev_region(dev_num, 1);
return ret;
}
// 创建设备类
char_class = class_create(THIS_MODULE, "char_class");
if (IS_ERR(char_class))
{
printk(KERN_ERR "class_create\n");
cdev_del(&char_cdev);
unregister_chrdev_region(dev_num, 1);
return PTR_ERR(char_class);
}
// 创建设备节点
mychar_device = device_create(char_class, NULL, dev_num, NULL, "chardev");
if (IS_ERR(mychar_device))
{
printk(KERN_ERR "device_create\n");
class_destroy(char_class);
cdev_del(&char_cdev);
unregister_chrdev_region(dev_num, 1);
return PTR_ERR(mychar_device);
}
printk("my_dev_init ok\n");
return 0;
}
void my_dev_exit(void)
{
// 移除设备节点
device_destroy(char_class, dev_num);
// 移除设备类
class_destroy(char_class);
// 移除字符设备
cdev_del(&char_cdev);
// 释放设备号
unregister_chrdev_region(dev_num, 1);
printk("my_dev_exit ok\n");
return;
}
module_init(my_dev_init);
module_exit(my_dev_exit);
MODULE_LICENSE("GPL");
3、测试
加载驱动后查看节点和设备:
使用ls /sys/class命令可以查看创建的设备类:
执行上一篇文章编写的应用程序测试,在dmesg中可以查看到驱动程序执行到了open和write接口: