• 字符设备驱动详解


    设备驱动的种类

    linux系统中的驱动分为三种:

    字符设备驱动:按照字节流来访问,只能顺序访问不能无序访问的设备

    (LED LCD(帧缓存) 鼠标 键盘 CAMERA(V4L2))
    ​ 帧缓存的每一存储单元对应屏幕上的一个像素,整个帧缓存对应一帧图像 。帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作

    V4L2 是专门为 linux 设备设计的一套视频框架,其主体框架在 linux 内核,可以理解为是整个 linux 系统上面的视频源捕获驱动框架

    块设备驱动:按照BLOCK(以块为最小单位,一块是512字节)来访问,可以顺序访问也可以无序访问的设备

    (EMMC FLASH U盘 硬盘)

    网卡设备驱动:网卡设备驱动没有设备节点,通过软件代码操作网卡硬件实现数据收发的代码

    在linux中没有关于网卡设备驱动的文件,是因为在linux源码中直接移植的其他系统的关于网卡的设备节点

    (网卡芯片DM9000)

    分步实现字符设备驱动

    register_chrdev可以实现字符设备的注册,为什么还要分步实现呢?

    这是因为register_chrdev一次申请256个设备号,我们在工作开发过程中并不会使用那么多的设备号,大大浪费了资源。分布实现可以根据自己的实际需求来申请。

    为了更好地演示现象,这里也写了一个应用程序来验证。

    mycdev.c
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define CNAME "mycdev"
    struct cdev* cdev;
    unsigned int major = 0;
    unsigned int minor = 0;
    const int count = 3;
    struct device* dev;
    struct class* cls;
    char kbuf[128] = { 0 };
    int mycdev_open(struct inode* inode, struct file* fiel)
    {
        printk("%s %s %d\n", __FILE__, __func__, __LINE__);
        return 0;
    }
    ssize_t mycdev_read(struct file* file, char __user* ubuf, size_t size, loff_t* offs)
    {
        int ret = 0;
        printk("%s %s %d\n", __FILE__, __func__, __LINE__);
        if (size > sizeof(kbuf)) {
            size = sizeof(kbuf);
        }
        ret = copy_to_user(ubuf, kbuf, size);
        if (ret) {
            printk("copy data to user error\n");
            return -EIO;
        }
        return size;
    }
    ssize_t mycdev_write(struct file* file, const char __user* ubuf,
        size_t size, loff_t* offs)
    {
        int ret = 0;
        printk("%s %s %d\n", __FILE__, __func__, __LINE__);
        if (size > sizeof(kbuf)) {
            size = sizeof(kbuf);
        }
        ret = copy_from_user(kbuf, ubuf, size);
        if (ret) {
            printk("copy data form user error\n");
            return -EIO;
        }
        return size;
    }
    int mycdev_close(struct inode* iode, struct file* file)
    {
        printk("%s %s %d\n", __FILE__, __func__, __LINE__);
        return 0;
    }
    const struct file_operations fops = {
        .open = mycdev_open,
        .read = mycdev_read,
        .write = mycdev_write,
        .release = mycdev_close,
    };
    static int __init mycdev_init(void)
    {
        // 1.分配对象
        dev_t devno;
        int ret = 0, i = 0;
        cdev = cdev_alloc();
        if (NULL == cdev) {
            printk("cdev alloc error\n");
            ret = -ENOMEM;
            goto ERR1;
        }
        // 2.对象的初始化
        cdev_init(cdev, &fops);
        // 3.申请设备号
        if (0 < major) {
            //静态指定
            ret = register_chrdev_region(MKDEV(major, minor),
                count, CNAME);
            if (ret) {
                printk("static:regiseter chrdev error\n");
                ret = -EAGAIN;
                goto ERR2;
            }
        } else {
            //动态申请
            ret = alloc_chrdev_region(&devno, minor, count, CNAME);
            if (ret) {
                printk("dynamic: register chrdev error\n");
                ret = -EAGAIN;
                goto ERR2;
            }
            major = MAJOR(devno);
            minor = MINOR(devno);
        }
        // 4.注册
        ret = cdev_add(cdev, MKDEV(major, minor), count);
        if (ret) {
            printk("register char device error\n");
            goto ERR3;
        }
        // 5.自动创建准备节点
        cls = class_create(THIS_MODULE, CNAME);
        if (IS_ERR(cls)) {
            printk("class create error\n");
            ret = PTR_ERR(cls);
            goto ERR4;
        }
        for (i = 0; i < count; i++) {
            dev = device_create(cls, NULL, MKDEV(major, i),
                NULL, "mycdev%d", i);
            if (IS_ERR(dev)) {
                printk("device create error\n");
                ret = PTR_ERR(dev);
                goto ERR5;
            }
        }
        return 0;
    ERR5:
        //防止出现中间创建失败的情况
        for (--i; i >= 0; i--) {
            device_destroy(cls, MKDEV(major, i));
        }
        class_destroy(cls);
    ERR4:
        cdev_del(cdev);
    ERR3:
        unregister_chrdev_region(MKDEV(major, minor), count);
    ERR2:
        kfree(cdev);
    ERR1:
        return ret;
    }
    
    static void __exit mycdev_exit(void)
    {
        int i = 0;
        for (i = 0; i < count; i++) {
            device_destroy(cls, MKDEV(major, i));
        }
        class_destroy(cls);
    
        cdev_del(cdev);
    
        unregister_chrdev_region(MKDEV(major, minor), count);
    
        kfree(cdev);
    }
    
    module_init(mycdev_init);
    module_exit(mycdev_exit);
    MODULE_LICENSE("GPL");
    
    • 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
    test.c
    #include 
    
    int main(int argc, const char* argv[])
    {
        int fd;
        char ubuf[128] = { 0 };
        if ((fd = open("/dev/mycdev0", O_RDWR)) == -1)
            PRINT_ERR("open error");
        while (1) {
            printf("input > ");
            fgets(ubuf, sizeof(ubuf), stdin);
            ubuf[strlen(ubuf) - 1] = '\0';
    
            if (!strcmp(ubuf, "q")) {
                break;
            }
            write(fd, ubuf, sizeof(ubuf));
    
            memset(ubuf, 0, sizeof(ubuf));
            read(fd, ubuf, sizeof(ubuf));
            printf("ubuf = %s\n", ubuf);
        }
        close(fd);
        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
    Makefile
    arch?=x86
    modname?=demo
    
    ifeq ($(arch),x86)
    KERNELDIR:= /lib/modules/$(shell uname -r)/build/   #ubuntu能够安装的驱动
    else
    KERNELDIR:= /home/linux/linux-5.10.61/             #开发板能够安装的驱动
    endif
    PWD:=$(shell pwd)
    
    all: 
    	make -C $(KERNELDIR) M=$(PWD) modules
    clean:
    	make -C $(KERNELDIR) M=$(PWD) clean
    obj-m:=$(modname).o
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    效果演示

    在这里插入图片描述

  • 相关阅读:
    STAR/star.py
    编译工具:CMake(八) | cmake 常用指令
    C++笔记之通用多态函数包装器std::function
    Rest风格操作
    【数据结构】队列的顺序表实现&&收尾栈和队列
    【mcuclub】PWM(呼吸灯、调节灯亮度)
    【c++】四种类型转换的用法
    长文多图一步步讲清楚:DDD理论、建模与代码实现全流程
    基于Pytest+Allure+Excel的接口自动化测试框架
    【C】 初识C语言
  • 原文地址:https://blog.csdn.net/l2020429/article/details/127578317