字符设备驱动详解

设备驱动的种类

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 <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#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");
test.c
#include <head.h>

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;
}
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
效果演示

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值