Linux驱动(二)之自动创建设备节点

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接口:

猜你喜欢

转载自blog.csdn.net/cesheng3410/article/details/132189521