• 【linux字符设备驱动-01】创建一个字符设备驱动


    一、创建字符设备

    1、申请设备号

    方法一

    alloc_chrdev_region:在未指定设备号的情况下使用

    dev_t devid;
    alloc_chrdev_region(&devid, 0, 1, "test");
    major = MAJOR(devid); /* 获取主设备号 */
    minor = MINOR(devid); /* 获取次设备号 */
    
    • 1
    • 2
    • 3
    • 4

    方法二

    register_chrdev_region:在给定设备号的情况下使用

    int major;
    int minor;
    dev_t devid;
    devid = MKDEV(major, minor);
    register_chrdev_region(devid, 1, "test");
    
    • 1
    • 2
    • 3
    • 4
    • 5

    通 过 alloc_chrdev_region 或者 register_chrdev_region 申请的设备号,释放函数都是:unregister_chrdev_region。

    2、创建类

    方法一

    class_register:把类注册进内核,类需要提前准备好。与class_unregister配对使用。

    //用法一:先申请内存,再初始化
    struct class *my_class;
    
    my_class= kzalloc(sizeof(*my_class), GFP_KERNEL);
    my_class->name = "my_class";
    my_class->owner = THIS_MODULE;
    
    class_register(my_class);	// 会在/sys/class/下创建"my_class"目录
    
    //用法二:定义结构体
    struct class my_class= {
    	.name		= "my_class",
    	.owner 		= THIS_MODULE,
    };
    
    class_register(&my_class);	// 会在/sys/class/下创建"my_class"目录
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    方法二

    class_create:创建类,并注册进内核,返回类结构体指针。与class_destroy配对使用。

    struct class *my_class;
    my_class= class_create(THIS_MODULE, "my_class");	// 会在/sys/class/下创建"my_class"目录
    
    • 1
    • 2

    阅读class_create的源码会发现,class_create和class_register最后都调用了__class_register。两者都会在/sys/class/下创建设备类节点(是个文件夹)。

    3、创建设备

    device_create:创建设备,需要传入class和设备号,返回device结构体指针。与device_destroy配对使用。

    struct device *device;
    device = device_create(class, NULL, devid, NULL, "device_name");	// 会在/dev/下创建"device_name"设备节点
    
    • 1
    • 2

    内核源码中实际上device_create用的并不多,反而是device_register与device_add更常见,其实它们之间的调用关系是:device_create->device_registe->device_add。在较新版本的内核中是:device_create->device_create_groups_vargs->device_add。

    二、创建字符设备驱动

    通过上面的第一部分,可以完成一个字符设备的创建,并在/dev/下面生成对应的设备节点。但是此时还不能够对设备节点进行write/read/ioctl等操作,因为并没有为设备注册相应的驱动。cdev是Linux内核中的一个结构体,定义在中,代表字符设备驱动的实例。cdev结构体中包含了字符设备驱动所需的各种信息,如设备号、设备文件操作函数等。通过cdev可以将设备文件操作函数与设备驱动关联起来,实现对字符设备的操作。

    1、初始化:cdev_init

    struct cdev my_cdev;
    
    // 文件操作描述符
    static struct file_operations my_fops = {
        .owner 			= THIS_MODULE,
        .open 			= my_open,
        .release 		= my_release,
        .read 			= my_read,
        .write 			= my_write,
        .unlocked_ioctl = my_ioctl,
    };
    
    cdev_init(&my_cdev, &my_fops);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2、添加到内核:cdev_add

    cdev_add(&my_cdev, devid, 1);
    
    • 1

    cdev_init完成对cdev结构体的初始化,cdev_add将cdev注册进内核,并与设备号关联起来。当应用层对设备节点进行操作时,会根据设备节点的设备号去内核中匹配对应的文件操作描述符,之后调用其中的回调函数。
    卸载驱动时,使用cdev_del函数将字符设备驱动从内核中删除。

    三、一个完整的字符设备驱动程序

    一个字符设备驱动除了包含必要设备号dev_t、所属类class、设备device、驱动cdev这些信息,此外还会有其它私有数据。为方便管理,将所有信息定义到一个结构体中。

    1、驱动源码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define CLASS_NAME 		"my_class"
    #define DEVICE_NAME 	"my_device"
    #define BUFFER_SIZE 	1024
    #define MY_IOCTL_RESET 	_IO('M', 0)
    
    struct chardev {
    	char name[32];
    	dev_t devid;
    	struct cdev cdev;
    	struct class *class;
    	struct device *device;
    	int major;
    	int minor;
    };
    
    static char buffer[BUFFER_SIZE];
    static struct chardev my_dev;
    
    static int my_open(struct inode *inode, struct file *file)
    {
        pr_info("%s\n", __func__);
        return 0;
    }
    
    static int my_release(struct inode *inode, struct file *file)
    {
        pr_info("%s\n", __func__);
        return 0;
    }
    
    static ssize_t my_read(struct file *file, char __user *user_buffer, size_t count, loff_t *offset)
    {
        ssize_t bytes_read = 0;
        int remaining_bytes = 0;
    
        remaining_bytes = BUFFER_SIZE - *offset;
        if (remaining_bytes == 0) {
            return 0;
        }
    
        bytes_read = min_t(size_t, count, remaining_bytes);
        if (copy_to_user(user_buffer, buffer + *offset, bytes_read)) {
            return -EFAULT;
        }
    
        *offset += bytes_read;
        return bytes_read;
    }
    
    static ssize_t my_write(struct file *file, const char __user *user_buffer, size_t count, loff_t *offset)
    {
        ssize_t bytes_written = 0;
        int remaining_bytes = 0;
    
        remaining_bytes = BUFFER_SIZE - *offset;
        if (remaining_bytes == 0) {
            return -ENOSPC;
        }
    
        bytes_written = min_t(size_t, count, remaining_bytes);
        if (copy_from_user(buffer + *offset, user_buffer, bytes_written)) {
            return -EFAULT;
        }
    
        *offset += bytes_written;
        return bytes_written;
    }
    
    static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    {
        switch (cmd) {
            case MY_IOCTL_RESET:
                memset(buffer, 0, BUFFER_SIZE);
                pr_info("buffer reset\n");
                break;
            default:
                return -EINVAL;
        }
    
        return 0;
    }
    
    static struct file_operations my_fops = {
        .owner 			= THIS_MODULE,
        .open 			= my_open,
        .release 		= my_release,
        .read 			= my_read,
        .write 			= my_write,
        .unlocked_ioctl = my_ioctl,
    };
    
    static int __init chardev_init(void)
    {
    	int ret;
    	
    	// 1. 分配设备号
    	ret = alloc_chrdev_region(&my_dev.devid, 0, 1, "my_devid");
    	if (ret < 0) {
            pr_err("Failed to allocate device number: %d\n", ret);
            return ret;
        }
    	my_dev.major = MAJOR(my_dev.devid);
    	my_dev.minor = MINOR(my_dev.devid);
    	pr_info("major = %d, minor = %d\n", my_dev.major, my_dev.minor);
    
    	// 2. 初始化cdev结构体、添加到内核
        cdev_init(&my_dev.cdev, &my_fops);
        ret = cdev_add(&my_dev.cdev, my_dev.devid, 1);
        if (ret < 0) {
            pr_err("Failed to add cdev: %d\n", ret);
            unregister_chrdev_region(my_dev.devid, 1);	// 记得注销设备号
            return ret;
        }
    	
    	// 3. 创建类
    	my_dev.class = class_create(THIS_MODULE, CLASS_NAME);
    	if (IS_ERR(my_dev.class)) {
            ret = PTR_ERR(my_dev.class);
            pr_err("Failed to create class: %d\n", ret);
            return ret;
        }
    
    	// 4. 创建设备
    	my_dev.device = device_create(my_dev.class, NULL, my_dev.devid, NULL, DEVICE_NAME);
    	if (IS_ERR(my_dev.device)) {
            ret = PTR_ERR(my_dev.device);
            pr_err("Failed to create device: %d\n", ret);
            class_destroy(my_dev.class);	// 记得注销类
            return ret;
        }
        
        pr_info("%s done\n", __func__);
    	return 0;
    }
    
    static void __exit chardev_exit(void)
    {
    	device_destroy(my_dev.class, my_dev.devid);
        class_destroy(my_dev.class);
        cdev_del(&my_dev.cdev);
        unregister_chrdev_region(my_dev.devid, 1);
        
        pr_info("%s done\n", __func__);
    }
    
    module_init(chardev_init);
    module_exit(chardev_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("xxx");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159

    2、测试demo

    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define DEVICE_PATH "/dev/my_device"
    #define MY_IOCTL_RESET _IO('M', 0)
    static int fd;
    static char buffer[256];
    
    int file_write(void)
    {
        ssize_t ret;
    
        // 打开设备文件
        fd = open(DEVICE_PATH, O_RDWR);
        if (fd < 0) {
            perror("Failed to open device");
            return -1;
        }
    
        // 写入数据到设备文件
        ret = write(fd, "Hello, device!", 14);
        if (ret < 0) {
            perror("Failed to write to device");
            close(fd);
            return -1;
        }
        printf("Write data to device: Hello, device!\n");
    
        // 关闭设备文件
        close(fd);
    
        return 0;
    }
    
    int file_read(void)
    {
        ssize_t ret;
    
        // 打开设备文件
        fd = open(DEVICE_PATH, O_RDWR);
        if (fd < 0) {
            perror("Failed to open device");
            return -1;
        }
    
        // 读取设备文件中的数据
        ret = read(fd, buffer, sizeof(buffer));
        if (ret < 0) {
            perror("Failed to read from device");
            close(fd);
            return -1;
        }
        printf("Read data from device: %s\n", buffer);
    
        // 关闭设备文件
        close(fd);
    
        return 0;
    }
    
    int file_ioctl(void)
    {
        ssize_t ret;
    
        // 打开设备文件
        fd = open(DEVICE_PATH, O_RDWR);
        if (fd < 0) {
            perror("Failed to open device");
            return -1;
        }
    
        // 重置设备文件中的数据
        ret = ioctl(fd, MY_IOCTL_RESET);
        if (ret < 0) {
            perror("Failed to ioctl");
            close(fd);
            return -1;
        }
        printf("Device buffer reset\n");
    
        // 关闭设备文件
        close(fd);
    
        return 0;
    }
    
    int main(void)
    {
        file_write();
        file_read();
        file_ioctl();
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
  • 相关阅读:
    DAY29| 491.递增子序列 ,46.全排列 ,47.全排列II
    【无标题】
    一种多媒体框架中的零buffer拷贝实现机制
    [轻松学会shell编程]-5、计划任务
    Rsync+Sersync实时文件同步
    RocketMQ高性能核心原理与源码架构剖析
    网络安全攻击数据的多维度可视化分析
    一文看懂推荐系统:召回09:地理位置召回、作者召回、缓存召回
    智云通CRM:如何清除销售前被拒绝的怀疑和猜测?
    C++项目实战——基于多设计模式下的同步&异步日志系统(总集篇)
  • 原文地址:https://blog.csdn.net/rentong123/article/details/132620752