LDD3閱讀筆記-字符設備驅動

#主要開發流程介紹node

module_init宏和module_exit宏linux

當模塊裝載時須要調用module_init宏指定的函數,卸載時須要調用
module_exit宏指定的函數

如下是簡單的init流程:express

  • 初始化設備
  • 初始化file_operation
  • 獲取字符設備號
  • 註冊字符設備

當卸載模塊時,須要釋放申請的設備號。api

#主設備號和次設備號 對字符設備的訪問是經過文件系統內的設備名稱進行的。那些名稱被稱爲特殊 文件、設備文件,或者簡單稱爲文件系統樹的節點,他們一般位於/dev目錄。安全

一般而言,主設備號表示設備對應的驅動程序。例如,/dev/null和/dev/zero 由驅動程序1管理,而虛擬控制檯和串口終端由驅動程序4管理。數據結構

現代的Linux內核容許多個驅動程序共享主設備號,但咱們看到的仍然按照」一 個主設備號對應一個驅動程序「的原則組織。架構

/proc/devices 能夠查看註冊的主設備號; /proc/modules 能夠查看正在使用模塊的進程數併發

#設備編號的內部表達 在內核中dev_t類型(在linux/types.h中定義)用來保存設備編號——包括主 設備號和次設備號。咱們的代碼不該該對設備編號的組織作任何假定,而應該始終 使用linux/kdev_t.h中定義的宏。app

MAJOR(dev_t dev);
MINOR(dev_t dev);

相反,若是要將主設備號和次設備號轉換成dev_t類型,則使用:異步

MKDEV(int major, int minor);

#分配和釋放設備編號 在創建一個字符設備以前,咱們的驅動程序首先要作的事情就是得到一個或者多個 設備編號。完成該工做的必要函數是 register_chrdev_region,該函數在linux/fd.h 中聲明:

int register_chrdev_region(dev_t first, unsigned int count, char *name);

其中 first 是要分配的設備編號範圍的起始值。first的次設備號常常被置爲0,但對 該函數不是必須的。count 是所請求的連續設備編號的個數。 name 是和該編號範圍 關聯的設備名稱,它將出如今/proc/devices和sysfs中。

若是咱們知道可用的設備編號,則 register_chrdev_region 會工做很好。可是 咱們進程不知道將要用哪些主設備號;應此提供瞭如下函數

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, 
                        unsigned int count, char *name);

dev 用於輸出參數,成功調用後保存已分配範圍的第一個編號。 firstminor 應該是 要使用的被請求的第一個次設備號,一般是0。 countname 參數和 register_chrdev_region 相同。

不管使用哪一種方法分配設備號,都應該在再也不使用它們時釋放這些設備編號。

void unregister_chrdev_region(dev_t first, unsigned int count);

一般咱們在清除模塊中調用 unregister_chrdev_region 函數。

#一些重要的數據結構

##文件操做 迄今爲止,咱們已經爲本身保留了一些設備編號,但還沒有將任何驅動程序的操做鏈接到這些 編號。file_operations結構就是用來創建這種鏈接的。

struct file_operations {
    struct module *owner;
     loff_t(*llseek) (struct file *, loff_t, int);
     ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
     ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
     ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
     ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t,
                  loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int,
              unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
     ssize_t(*readv) (struct file *, const struct iovec *, unsigned long,
              loff_t *);
     ssize_t(*writev) (struct file *, const struct iovec *, unsigned long,
               loff_t *);
     ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t,
                 void __user *);
     ssize_t(*sendpage) (struct file *, struct page *, int, size_t,
                 loff_t *, int);
    unsigned long (*get_unmapped_area) (struct file *, unsigned long,
                        unsigned long, unsigned long,
                        unsigned long);
};

struct file_operations scull_fops = {
    .owner = THIS_MODULE,
    .llseek = scull_llseek,
}; //C99 syntax

##file結構 在<linux/fs.h>中定義的struct file是設備驅動程序所使用的第二個最重要的數據結構。

注意:file結構和用戶空間程序中的FILE沒有任何關聯。FILE是C庫中的定義的結構, 而struct file是一個內核結構,不會出如今用戶程序中(文件描述符應該是指向該結構體)。

struct file {
        mode_t f_mode;      //文件模式
        loff_t f_pos;       //當前讀寫位置
        unsigned int f_flags; //文件標誌,如O_RDONLY、O_NONBLOCK和O_SYNC。
        struct file_operations *f_op;   //與文件相關的操做。
        void *private_data;     //open系統調用在調用驅動程序的open方法前將這個
                                //指針置爲NULL。
        struct dentry *f_dentry;//文件對應的目錄項(dentry)結構。
                                //filp->f_dentry->d_inode
        ……
    }

##inode結構 內核用inode結構在內部表示文件,所以它和file結構不一樣,後者表示打開的文件描述符。對 單個文件,可能會有多個表示打開的文件描述符的file結構,但它們都指向單個inode結構。

struct inode {
        dev_t i_rdev;       //對錶示設備文件的inode結構,
                            //該字段包含了真正的設備編號
        struct cdev *i_cdev;//表示字符設備的內核內部結構。
        ……
    }

i_rdev 的類型在2.5開發系列版本中發生了變化,爲了鼓勵編寫可移植性更強的代碼, 內核開發者增長了兩個新的宏。

unsigned int iminor(struct inode *inode);       //獲取次設備號
    unsigned int imajor(struct inode *inode);       //獲取主設備號

#字符設備的註冊 內核內部使用struct cdev結構來表示字符設備。在內核調用設備的操做以前,必須分配 並註冊一個或者多個上述結構。爲此,咱們的代碼須要包含linux/cdev.h,其中定義 了這個結構以及與其相關的一些輔助函數。

struct cdev {
        struct kobject kobj;
        struct module *owner;
        const struct file_operations *ops;
        struct list_head list;
        dev_t dev;
        unsigned int count;
    };

有一個老的機制能夠避免使用cdev結構,可是新代碼應該使用新技術。

struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;

咱們能夠將cdev結構嵌入到本身的設備特定結構中(有點相似派生的C版本)。 若是沒有經過 cdev_alloc 申請,則咱們須要用下面的代碼來初始化已分配的結構:

void  cdev_init(struct cdev *cdev, 
                    struct file_operations *fops);

還有一個字段須要初始化,和file_operations同樣,struct cdev也有一個全部者字段, 應設置爲THIS_MODULE。

在設置完cdev結構後,最後的步驟是告訴內核該結構的信息:

int cdev_add(struct cdev *dev, dev_t num, 
            unsigned int count);

num 是該設備對應的第一個設備編號,count是應該和該設備關聯的設備編號數量。 只要 cdev_add 成功返回,咱們的設備就要開始工做了,它的操做會被內核調用。

要從系統移除一個字符設備,作以下調用:

void cdev_del(struct cdev *dev);

在將cdev經過 cdev_del 移除後,就不該該再訪問cdev結構了。

##Scull中的設備註冊

static void scull_setup_cdev(struct scull_dev *dev, int index)
{
    int err, devno = MKDEV(scull_major, scull_minor+index);
    cdev_init(&dev->cdev, &scull_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &scull_fops;  // this expression is redundancy ?
    err = cdev_add(&dev->cdev, devno, 1);
    if(err){
        printk(KERN_NOTICE "Error %d adding scull%d", err, index);
    }
}

由於cdev結構被內嵌到了strcut scull_dev中,所以必須調用cdev_init來執行該結構 的初始化。

##早期辦法

int register_chrdev(unsigned int major, const char *name,
                        struct file_operations *fops);

register_chrdev 的調用將 爲給定的主設備號註冊0~255做爲次設備號,併爲 每一個設備創建一個對應的默認cdev結構。使用這一接口的驅動程序必須可以處理全部 256個次設備號上的 open 調用。對應的移除函數:

int unregister_chrdev(unsigned int major, const char *name);

#open和release

##open方法

open 方法提供給驅動程序以初始化的能力(和module_init的不一樣),open應完成以下 工做:

  • 檢查設備特定的錯誤(如設備未就緒或相似硬件問題)
  • 若是設備首次打開,對其進行初始化。
  • 若有必要,更新f_op指針。
  • 分配並填寫置於filp->private_data裏的數據結構。

open 方法的原型以下:

int (*open) (struct inode *inode, struct file *filp);

inode參數在i_cdev字段中包含了咱們須要的信息,即咱們先前設定的cdev結構。咱們一般須要 包含它的scull_dev結構,內核黑客爲咱們提供了此類技巧,它經過定義在linux/kernel.h 中的container_of宏實現:

container_of(pointer, container_type, container_field);

struct scull_dev *dev;
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
flip->private_data = dev;

另外一個肯定要打開的設備的方法是:檢查保存在inode中的次設備號。若是使用了 register_chrdev 註冊設備,則必須使用該技術。 通過簡化的 scull_open 代碼:

int scull_open(struct inode *inode, struct file *filp)
{
    struct scutll_dev *dev;
    dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    filp->private_data = dev;
    if((filp->f_flags&O_ACCMODE)==O_WRONLY){
        scull_trim(dev);
    }
    return 0;
}

因爲咱們沒有維護scull的打開計數,只維護模塊的使用計數,所以 也就沒有相似"首次打開時初始化設備"這類動做。

##release方法 release 方法和 open 相反,有時這個方法被稱爲 device_close

  • 釋放由 open 分配的、保存在filp->private_data中的全部內容。
  • 在最後一次關閉操做時關閉設備。

注: 後面scull_open爲每種設備都替換了不一樣的filp->f_op,因此不一樣的設備由 不一樣的函數關閉

當關閉一個設備文件的次數比打開它的次數多時,系統中會發生什麼? 答案很簡單:並非每一個close系統調用都會引發對release方法的調用。只有 真正釋放設備數據結構的 close 調用纔會調用這個方法。內核對每一個file 結構維護其被使用多少次的計數器。不管 fork 仍是 dup 都不會建立新 的數據結構(僅由open建立),他們只是增長已有結構中的計數。只有file結 構的計數歸零是,close系統調用纔會執行release方法,這隻在刪除這個結 構時纔會發生。保證了一次open只會看到一次release調用

注意:flush方法在應用程序每次調用close時都會調用。

#read和write readwrite 方法完成的任務是類似的,亦即,拷貝數據到應用程序空間, 或者反過來。

ssize_t read(struct file *filp, char __user *buff,
            size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char __user *buff,
            size_t count, loff_t *offp);

須要說明的是read和write方法的buff參數是用戶空間的指針。所以,代碼不能直接 引用其中內容。緣由以下:

  • 隨着驅動程序所運行的架構不一樣或者內核配置不一樣,在內核模式運行時,用戶 空間的指針多是無效的。
  • 即便該指針在內核空間中表明相同的東西,但用戶空間的內存是分頁的,而在系統 調用被調用時,涉及到的內存可能根本不在RAM中。對用戶空間的內存的直接引用 將致使頁錯誤,而這對內核代碼來講是不容許發生的事情。其結果多是「oops」。
  • 咱們討論的指針可能由用戶程序提供,而該程序可能存在缺陷或者是個惡意程序。

爲確保安全,須要使用下面幾個函數(由linux/uaccess.h中定義)

unsigned long copy_to_user(void __user *to,
                            const void *from,
                            unsigned long count);
unsigned long copy_from_user(void *to,
                             const void __user *from,
                             unsigned long count);

當內核空間運行的代碼訪問用戶空間的時候必須多加當心,由於被尋址的用戶頁面 可能不存在當前內存中,因而虛擬內存子系統經該進程轉入休眠,知道該頁面被加載 到指望位置。對驅動開發人員來講,這帶來的結果就是任何訪問用戶空間的函數都是 必須可重入的(異步信號安全),必須能和其餘驅動程序函數併發執行,更特別的是 必須處於可以合法休眠的狀態。 read

#readv和writev Unix系統很早就已支持兩個可選的系統調用:readv和writev。 若是驅動程序沒有提供用於處理向量操做的方法,readv和writev會經過對read和 write方法的屢次調用來實現。但在不少狀況下,直接在驅動程序中實現readv和writev 能夠得到更高的效率(收集分散的buffer直接傳給驅動讀寫,減小了內存拷貝次數)。

ssize_t (*readv) (struct file *filp, const struct iovec *iov,
                    unsigned long count, loff_t *ppos);
ssize_t (*writev) (struct file *filp, const struct iovec *iov,
                    unsigned long count, loff_t *ppos);

iovec結構定義在<linux/uio.h>中:

struct iovec {
    void __user *iov_base;
    __kernel_size_t iov_len;
}

每一個iovec結構都描述了一個用於傳輸的數據塊。函數中的count參數指明要操做多少 個iovec結構。 正確而有效率的操做常常須要驅動程序作一些更爲巧妙的事情。 例如,磁帶驅動程序的writev就應將全部iovec結構的內容做爲磁帶上的單個記錄寫入。 若是忽略他們,內核會經過read和write模擬它們。

#實例

/*
     * =====================================================================================
     *
     *       Filename:  scull.c
     *
     *    Description:  this is a first driver from ldd3
     *                  ignore mutithread race, data overflow,
     *                  just a simple example.
     *
     *        Version:  1.0
     *        Created:  11/29/2014 09:00:04 PM
     *       Revision:  none
     *       Compiler:  gcc
     *
     *         Author:  firemiles(firemiles@163.com), 
     *   Organization:  
     *
     * =====================================================================================
     */
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/uaccess.h>
    #include<linux/slab.h>

    MODULE_LICENSE("GPL");

    static long scull_ioctl (struct file *file, unsigned int cmd, unsigned long n);
    static ssize_t scull_write (struct file *file, const char __user *buff, 
                            size_t count, loff_t *f_ops );
    static ssize_t scull_read (struct file *file, char __user *buff, 
                            size_t count, loff_t *f_ops );
    static loff_t scull_llseek (struct file *file, loff_t f_ops, int count);
    static int scull_release (struct inode *inode, struct file *file );
    static int scull_open (struct inode *inode, struct file *file );

    static int scull_major = 0;
    static int scull_minor = 0;

    struct scull_dev {
        char *data;
        size_t maxlen; //buffer length
        size_t len;    //data length
        struct  cdev cdev;
    }scull_dev1;


    struct file_operations scull_fops = {
        .owner  =   THIS_MODULE,
        .llseek =   scull_llseek,
        .read   =   scull_read,
        .write  =   scull_write,
        .unlocked_ioctl  =   scull_ioctl, //new api
        .open   =   scull_open,
        .release=   scull_release,
    };

    /* 
     * ===  FUNCTION  ======================================================================
     *         Name:  scull open
     *  Description:  
     * =====================================================================================
     */
    static int scull_open (struct inode *inode, struct file *filp )
    {
        struct scull_dev *dev;
        dev = container_of(inode->i_cdev, struct scull_dev, cdev);
        filp->private_data = dev;
        if(dev->data == NULL){
            dev->data = (char *)kmalloc(1024, GFP_KERNEL);
            dev->maxlen = 1024;
            dev->len = 0;
        }
        return 0;
    }       /* -----  end of function scull open  ----- */


    /* 
     * ===  FUNCTION  ======================================================================
     *         Name:  scull_close
     *  Description:  
     * =====================================================================================
     */
    static int scull_release (struct inode *inode, struct file *filp )
    {
        return 0;
    }       /* -----  end of function scull_close  ----- */



    /* 
     * ===  FUNCTION  ======================================================================
     *         Name:  scull_llseek
     *  Description:  
     * =====================================================================================
     */
    static loff_t scull_llseek (struct file *file, loff_t f_ops, int count)
    {
        return 0;
    }       /* -----  end of function scull_llseek  ----- */

    /* 
     * ===  FUNCTION  ======================================================================
     *         Name:  scull_read
     *  Description:  
     * =====================================================================================
     */
    static ssize_t scull_read (struct file *filp, char __user *buff, size_t count, loff_t *f_ops )
    {
        int num;
        struct scull_dev *dev = filp->private_data;
        copy_to_user(buff, dev->data, dev->len); // ignore buff overflow;
        num = dev->len;
        dev->len = 0;
        return num;
    }       /* -----  end of function scull_read  ----- */


    /* 
     * ===  FUNCTION  ======================================================================
     *         Name:  scull_write
     *  Description:  
     * =====================================================================================
     */
    static ssize_t scull_write (struct file *filp, const char __user *buff, size_t count, loff_t *f_ops )
    {
        struct scull_dev *dev = filp->private_data;
        copy_from_user(dev->data, buff, count); // ignore data overflow; 
        dev->len = count;
        return count;
    }       /* -----  end of function scull_write  ----- */


    /* 
     * ===  FUNCTION  ======================================================================
     *         Name:  scull_ioctl
     *  Description:  
     * =====================================================================================
     */
    static long scull_ioctl (struct file *file, unsigned int cmd, unsigned long n)
    {
        return 0;
    }       /* -----  end of function scull_ioctl  ----- */


    /* 
     * ===  FUNCTION  ======================================================================
     *         Name:  scull_setup_cdev
     *  Description:  
     * =====================================================================================
     */
    static void scull_setup_cdev (struct scull_dev *dev, int index)
    {
        int err;
        dev_t devnum;
        char name[16];
        sprintf(name,"scull1");
        if(scull_major){
            devnum = MKDEV(scull_major, scull_minor+index);
            err = register_chrdev_region(devnum, 1, name);
        }else{
            err = alloc_chrdev_region(&devnum, scull_minor+index, 1, name);
            scull_major = MAJOR(devnum);
        }
        if(err<0){
            printk(KERN_WARNING "scull1: can't get major %d\n", scull_major);
            return;
        }
        cdev_init(&dev->cdev, &scull_fops);
        dev->cdev.owner = THIS_MODULE;
        dev->cdev.ops = &scull_fops; //nessary?
        err = cdev_add(&dev->cdev, devnum, 1);
        if(err){
            printk(KERN_NOTICE "Error %d adding scull%d", err, index);
        }
    }       /* -----  end of function scull_setup_cdev  ----- */

    /*  
     * ===  FUNCTION  ======================================================================
     *         Name:  scull_init
     *  Description:  
     * =====================================================================================
     */
    static int __init scull_init (void)
    {
        scull_setup_cdev(&scull_dev1, 1);
        printk(KERN_ALERT "scull init\n");
        return 0;
    }       /* -----  end of function scull_init  ----- */


    /* 
     * ===  FUNCTION  ======================================================================
     *         Name:  scull_exit
     *  Description:  
     * =====================================================================================
     */
    static void __exit scull_exit (void)
    {
        kfree(scull_dev1.data);
        unregister_chrdev_region(scull_dev1.cdev.dev, 1);
        cdev_del(&scull_dev1.cdev);
        printk(KERN_ALERT "scull exit\n");
    }       /* -----  end of function scull_exit  ----- */
    /* this macro told to compiler.
     * that the two function are init function and exit function
     * 
     */

    module_init(scull_init);
    module_exit(scull_exit);
相關文章
相關標籤/搜索