Linux设备驱动--(六)字符设备驱动-下

字符设备驱动-下

一、一个驱动支持多个设备

  • 我们平时写设备驱动程序的时候,肯定遇到过这种情况,一类设备有多个个体(比如系统上有两个串口)。此时,我们可能会想到多一个设备我在多写一个同样的驱动程序就好啦,或者说同一中驱动程序写两份呢?这样当然可以啦,但是呢,这样并不是最好的解决办法。如果我们一个设备上面的设备有10个八个呢,而我们的驱动程序是要编译进内核的而内核又是对内存有着比较高的要求的,同一类设备你编译进去那么多的代码自然很浪费内存又不够简洁。当然了,我们可以采用一个讨巧的办法来处理这一类问题了。那就是一个驱动程序支持多个设备
  • 这个我们应该怎么做呢?首先我们应该向内核注册多个设备号,其次就是添加cdev对象(这里为什么不说结构体呢,因为在驱动编程中我们用到了面向对象的编程方法,即把每一个设备当做一个对象来处理,为其添加属性和方法)时需要指明该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;
/*DEFINE_KFIFO结构体用于在内核中初始化一个FIFO虚拟串口,我们可以在里面读取和写入数据,关于该设备这里不多做介绍*/
static DEFINE_KFIFO(vsfifo0,char,32);
static DEFINE_FIFO(vsfifo1,char,32);

static int vser_open(struct inode *inode,struct file *filp)
{
    /*根据应用层的打开的设备文件的不同,将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 __vser *buf,size_t count,loff_t *ops)
{
    unsigned int copied = 0;
/*创建一个kfifo结构体,使其指向read所读的fifo结构体的指针*/
    struct kfifo *vsfifo = filp->private_data;
 /*此函数是内核函数,目的是将将FIFO中的数据取出来,复制给用户空间,实际放入的元素由copied带回*/
    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;
}

/*将上面的函数接口注册到file_opeations结构体中,这也是面向对象的思想的应用*/
static struct file_opeations vser_ops = {
    .owner = THIS_MODULE,
    .open = vser_open,
    .release = vser_read,
    .write = vser_write,
};

/*还记得此处__init的作用吗?请参考前几篇博客哈*/
static int __init vser_init(void)
{
    int ret;
    dev_t dev;
    
    /*该宏用于生成设备号*/
    dev = MKDEV(VSER_MAJOR,VSER_MINOR);
    /*注册设备号,注意这里的VSER_DEV_CNT 值为2,所以此处会冬分配两个设备号*/
    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("Fan <[email protected]>");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_ALIAS("virtual-serial");

二、小结

  • 到目前为止呢,我们的字符设备的驱动程序已经近乎完成,当然了,其实并不是很完善。比如我们写了一个LED的驱动,除了去进行读写,又该如何去控制其亮灭呢?再比如我们又如何去控制一个串口设备的波特率呢?等等。当然了,并不是无法解决的,只是我们的知识水平还不够。为了解决此问题呢,Linux专门开发出了ioctl函数用于做上面提到的问题。那么ioctl是什么呢?下一节我会做一个详细的介绍。
发布了49 篇原创文章 · 获赞 15 · 访问量 9254

猜你喜欢

转载自blog.csdn.net/wit_732/article/details/103109754