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是什麼呢?下一節我會做一個詳細的介紹。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章