• Linux驱动—实现一个驱动支持多个设备


    前面内容:
    1 Linux驱动—内核模块基本使用

    2 Linux驱动—内核模块参数,依赖(进一步讨论)

    3 字符设备驱动

    4 虚拟串口设备驱动

    Linux驱动—实现一个驱动支持多个设备

    每个设备都写一个驱动太麻烦了,所以要Linux驱动—实现一个驱动支持多个设备。

    对于多设备引入的变化:
    我们首先要向 内核注册多个设备号
    其次就是在添加cdev对象时指明改cdev对象管理了多个设备;
    或者添加多个cdev对象,每个cdev对象管理一个设备。

    接下来最麻烦的部分在于读写操作,因为设备是多个,那么设备对应的资源也应该是多个(比如虚拟串口驱动中的FIFO)。 在读写操作时,怎么来区分究竟应该对哪个设备进行操作呢(对于虚拟串口驱动而言,就是要确定对哪个FIFO进行操作) ?

    观察读和写函数,没有发现能够区别设备的形参。
    再观察open接口,我们会发现有一个inode 形参,通过前面的内容我们知道,inode里面包含了对应设备的设备号以及所对应的cdev对象的地址。

    因此,我们可以在open接口函数中取出这些信息(inode里面包含了对应设备的设备号以及所对应的cdev对象的地址),并存放在file结构对象的某个成员中,再在读写的接口函数中获取该file结构的成员,从而可以区分出对哪个设备进行操作。

    下面首先展示用一个cdev实现对多个设备的支持

    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/module.h>

    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/kfifo.h>

    #define VSER_MAJOR 256
    #define VSER_MINOR 0
    #define VSER_DEV_CNT 2
    #define VSER_DEV_NAME "vser"

    static struct cdev vsdev;
    static DEFINE_KFIFO(vsfifo0, char, 32);
    static DEFINE_KFIFO(vsfifo1, char, 32);

    static int vser_open(struct inode *inode, struct file *filp)
    {
    switch (MINOR(inode->i_rdev)) {
    default:
    case 0:
    filp->private_data = &vsfifo0;
    break;
    case 1:
    filp->private_data = &vsfifo1;
    break;
    }
    return 0;
    }

    static int vser_release(struct inode *inode, struct file *filp)
    {
    return 0;
    }

    static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
    {
    unsigned int copied = 0;
    struct kfifo *vsfifo = filp->private_data;

    kfifo_to_user(vsfifo, buf, count, &copied);

    return copied;
    }

    static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
    {
    unsigned int copied = 0;
    struct kfifo *vsfifo = filp->private_data;

    kfifo_from_user(vsfifo, buf, count, &copied);

    return copied;
    }

    static struct file_operations vser_ops = {
    .owner = THIS_MODULE,
    .open = vser_open,
    .release = vser_release,
    .read = vser_read,
    .write = vser_write,
    };

    static int __init vser_init(void)
    {
    int ret;
    dev_t dev;

    dev = MKDEV(VSER_MAJOR, VSER_MINOR);
    ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
    if (ret)
    goto reg_err;

    cdev_init(&vsdev, &vser_ops);
    vsdev.owner = THIS_MODULE;

    ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
    if (ret)
    goto add_err;

    return 0;

    add_err:
    unregister_chrdev_region(dev, VSER_DEV_CNT);
    reg_err:
    return ret;
    }

    static void __exit vser_exit(void)
    {

    dev_t dev;

    dev = MKDEV(VSER_MAJOR, VSER_MINOR);

    cdev_del(&vsdev);
    unregister_chrdev_region(dev, VSER_DEV_CNT);
    }

    module_init(vser_init);
    module_exit(vser_exit);

    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
    MODULE_DESCRIPTION("A simple character device driver");
    MODULE_ALIAS("virtual-serial");

    来分析下代码:

    这里把CNT改成2 说明支持2个设备

    static struct cdev vsdev;
    static DEFINE_KFIFO(vsfifo0, char, 32);
    static DEFINE_KFIFO(vsfifo1, char, 32);
    1
    2
    3
    定义了二个FIFO,vsfifo0和1 (这里用的是动态分配fifo要更好,但是后面会涉及内存分配的知识,所以先用静态的)

    在open接口函数中根据次设备号的值来确定保存哪个FIFO结构的(vsfifo0还是1)地址到file结构中的private_data 的值,即FIFO结构的地址

    static int vser_open(struct inode *inode, struct file *filp)
    {
    switch (MINOR(inode->i_rdev)) {
    default:
    case 0:
    filp->private_data = &vsfifo0;
    break;
    case 1:
    filp->private_data = &vsfifo1;
    break;
    }
    return 0;
    }


    次设备号 0 选择存 vsfifo0
    次设备号 1 选择存 vsfifo1

    接下来演示如何将每一个cdev对象对应到一个设备来实现一个驱动对多个设备的支持

    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/module.h>

    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/kfifo.h>

    #define VSER_MAJOR 256
    #define VSER_MINOR 0
    #define VSER_DEV_CNT 2
    #define VSER_DEV_NAME "vser"

    static DEFINE_KFIFO(vsfifo0, char, 32);
    static DEFINE_KFIFO(vsfifo1, char, 32);

    struct vser_dev {
    struct kfifo *fifo;
    struct cdev cdev;
    };

    static struct vser_dev vsdev[2];

    static int vser_open(struct inode *inode, struct file *filp)
    {
    filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);
    return 0;
    }

    static int vser_release(struct inode *inode, struct file *filp)
    {
    return 0;
    }

    static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
    {
    unsigned int copied = 0;
    struct vser_dev *dev = filp->private_data;

    kfifo_to_user(dev->fifo, buf, count, &copied);

    return copied;
    }

    static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
    {
    unsigned int copied = 0;
    struct vser_dev *dev = filp->private_data;

    kfifo_from_user(dev->fifo, buf, count, &copied);

    return copied;
    }

    static struct file_operations vser_ops = {
    .owner = THIS_MODULE,
    .open = vser_open,
    .release = vser_release,
    .read = vser_read,
    .write = vser_write,
    };

    static int __init vser_init(void)
    {
    int i;
    int ret;
    dev_t dev;

    dev = MKDEV(VSER_MAJOR, VSER_MINOR);
    ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
    if (ret)
    goto reg_err;

    for (i = 0; i < VSER_DEV_CNT; i++) {
    cdev_init(&vsdev[i].cdev, &vser_ops);
    vsdev[i].cdev.owner = THIS_MODULE;
    vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;

    ret = cdev_add(&vsdev[i].cdev, dev + i, 1);
    if (ret)
    goto add_err;
    }

    return 0;

    add_err:
    for (--i; i > 0; --i)
    cdev_del(&vsdev[i].cdev);
    unregister_chrdev_region(dev, VSER_DEV_CNT);
    reg_err:
    return ret;
    }

    static void __exit vser_exit(void)
    {
    int i;
    dev_t dev;

    dev = MKDEV(VSER_MAJOR, VSER_MINOR);

    for (i = 0; i < VSER_DEV_CNT; i++)
    cdev_del(&vsdev[i].cdev);
    unregister_chrdev_region(dev, VSER_DEV_CNT);
    }

    module_init(vser_init);
    module_exit(vser_exit);

    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
    MODULE_DESCRIPTION("A simple character device driver");
    MODULE_ALIAS("virtual-serial");

    代码:

    struct vser_dev {
    struct kfifo *fifo;
    struct cdev cdev;
    };
    1
    2
    3
    4
    这里定义了一个结构类型 vser_dev,代表一种具体的设备类

    通常和设备有关的内容都可以跟cdev一起定义到一个结构中这样更容易

    cdev 是所有字符设备的一个抽象,是一个基类,而一个具体类型的设备应该是由该基类派生出来的一个子类,子类包含了特定设备所特有的属性,比如vser_ dev 中的fifo,这样子类就更能刻画好一类具体的设备。

    代码

    static struct vser_dev vsdev[2];
    1
    创建了两个vser_dev 类型的对象,和C++不同的是,创建这两个对象仅仅是为其分配了内存,并没有调用构造函数来初始化这两个对象,但在代码的第74行到第77行完成了这个操作。

    for (i = 0; i < VSER_DEV_CNT; i++) {
    cdev_init(&vsdev[i].cdev, &vser_ops);
    vsdev[i].cdev.owner = THIS_MODULE;
    vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;
    1
    2
    3
    4
    你看,初始化init cdev

    查看内核源码,会发现这种面向对象的思想处处可见,只能说因为语言的特性,并没有把这种形式体现得很明显而已。

    代码的第74行到第82行通过两次循环完成了两个cdev对象的初始化和添加工作,并且初始化了fifo 成员的指向。

    for (i = 0; i < VSER_DEV_CNT; i++) {
    cdev_init(&vsdev[i].cdev, &vser_ops);
    vsdev[i].cdev.owner = THIS_MODULE;
    vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;

    ret = cdev_add(&vsdev[i].cdev, dev + i, 1);
    if (ret)
    goto add_err;
    }

    如果i为0是第一个,指向vsfifo0这个结构的地址
    如果i为1是第二个,指向vsfifo1这个结构的地址

    这里需要说明的是,用DEFINE_ KFIFO 定义的FIFO,每定义一个FIFO就会新定义一种数据类型,所以严格来说vsfifo0和vsfifo1是两种不同类型的对象,但好在这里能和struct kfifo类型兼容。

    代码第26行

    filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);
    1
    用到了一个container_ of 宏,这是在Linux内核中设计得非常巧妙的一个宏,在整个Linux内核源码中几乎随处可见。

    它的作用就是**根据结构成员的地址来反向得到结构的起始地址**

    在代码中, inode->i_cdev 给出了struct vser_dev 结构类型中cdev成员的地址(见图3.2),通过container_ of宏就得到了包含该cdev的结构地址。


    使用上面两种方式都可以实现一个驱动对多个同类型设备的支持。使用下面的命令可以测试这两个驱动程序。



  • 相关阅读:
    反射(8)程序集反射 Type 类
    反射(5)CLR 运行时探测程序集引用的步骤
    反射(1)程序集基础知识
    csc.exe(C# 编译器)
    证书(1)数字签名基础知识
    反射(7)动态程序集加载Load方法
    SignTool.exe(签名工具)
    反射(3)程序集加载 Assembly类
    关于卡巴斯基安全免疫区随笔
    文本提取工具 TextHelper
  • 原文地址:https://www.cnblogs.com/wanghuaijun/p/16336302.html
Copyright © 2020-2023  润新知