linux設備驅動之字符設備驅動

導讀 Linux字符設備提供連續的數據流,應用程序能夠順序讀取,一般不支持隨機存取。相反,此類設備支持按字節/字符來讀寫設備。舉例來講,鍵盤,串口,調制解調器都是典型的字符設備。

衆所周知,字符設備是Linux下最基本,也是最經常使用到的設備,它是學習linux驅動入門最好的選擇。計算機的不少東西都是相通的,掌握了其中一塊,其它的就舉一反三了。在寫驅動以前,必須先搞清楚字符設備驅動的框架大概是怎樣的,必定要弄清楚流程,纔開始動手,不要一開始就動手寫代碼。html

linux系統設備分類node

linux系統將設備分爲3類:字符設備、塊設備、網絡設備。linux

linux設備驅動之字符設備驅動linux設備驅動之字符設備驅動

  • 字符設備:是指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據須要按照前後數據。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制檯和LED設備等。
  • 塊設備:是指能夠從設備的任意位置讀取必定長度數據的設備。塊設備包括硬盤、磁盤、U盤和SD卡等。

每個字符設備或塊設備都在/dev目錄下對應一個設備文件。linux用戶程序經過設備文件(或稱設備節點)來使用驅動程序操做字符設備和塊設備。網絡

字符設備、字符設備驅動與用戶空間訪問該設備的程序三者之間的關係數據結構

linux設備驅動之字符設備驅動linux設備驅動之字符設備驅動

如圖,在Linux內核中使用cdev結構體來描述字符設備,經過其成員dev_t來定義設備號(分爲主、次設備號)以肯定字符設備的惟一性。經過其成員file_operations來定義字符設備驅動提供給VFS的接口函數,如常見的open()、read()、write()等。app

在Linux字符設備驅動中,模塊加載函數經過register_chrdev_region( ) 或alloc_chrdev_region( )來靜態或者動態獲取設備號,經過cdev_init( )創建cdev與file_operations之間的鏈接,經過cdev_add( )向系統添加一個cdev以完成註冊。模塊卸載函數經過cdev_del( )來註銷cdev,經過unregister_chrdev_region( )來釋放設備號。框架

用戶空間訪問該設備的程序經過Linux系統調用,如open( )、read( )、write( ),來「調用」file_operations來定義字符設備驅動提供給VFS的接口函數。async

字符設備驅動模型函數

linux設備驅動之字符設備驅動linux設備驅動之字符設備驅動

1.驅動初始化學習

1.1. 分配cdev

在2.6的內核中使用cdev結構體來描述字符設備,在驅動中分配cdev,主要是分配一個cdev結構體與申請設備號,以按鍵驅動爲例:

/*分配cdev*/
struct cdev btn_cdev;
/*1.1 申請設備號*/
if(major){
    //靜態
    dev_id = MKDEV(major, 0);
    register_chrdev_region(dev_id, 1, "button");
    } 
else {
    //動態
    alloc_chardev_region(&dev_id, 0, 1, "button");
    major = MAJOR(dev_id);
    }

從上面的代碼能夠看出,申請設備號有動靜之分,其實設備號還有主次之分。在Linux中以主設備號用來標識與設備文件相連的驅動程序。次編號被驅動程序用來辨別操做的是哪一個設備。cdev 結構體的 dev_t 成員定義了設備號,爲 32 位,其中高 12 位爲主設備號,低20 位爲次設備號。

設備號的得到與生成:

得到:

主設備號:MAJOR(dev_t dev);
次設備號:MINOR(dev_t dev);

生成:MKDEV(int major,int minor);

設備號申請的動靜之分:

靜態:

int register_chrdev_region(dev_t from, unsigned count, const char *name);
/*功能:申請使用從from開始的count 個設備號(主設備號不變,次設備號增長)*/

靜態申請相對較簡單,可是一旦驅動被普遍使用,這個隨機選定的主設備號可能會致使設備號衝突,而使驅動程序沒法註冊。

動態:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
/*功能:請求內核動態分配count個設備號,且次設備號從baseminor開始。*/

動態申請簡單,易於驅動推廣,可是沒法在安裝驅動前建立設備文件(由於安裝前尚未分配到主設備號)。

1.2初始化cdev

void cdev_init(struct cdev *, struct file_operations *);
cdev_init()函數用於初始化 cdev 的成員,並創建 cdev 和 file_operations 之間的鏈接。

1.3註冊cdev

int cdev_add(struct cdev *, dev_t, unsigned);
cdev_add()函數向系統添加一個 cdev,完成字符設備的註冊。

1.4硬件初始化

硬件初始化主要是硬件資源的申請與配置,以TQ210的按鍵驅動爲例:

/* 1.4 硬件初始化*/
//申請GPIO資源
gpio_request(S5PV210_GPH0(0), "GPH0_0");
//配置輸入
gpio_direction_input(S5PV210_GPH0(0));

 2.實現設備操做

用戶空間的程序以訪問文件的形式訪問字符設備,一般進行open、read、write、close等系統調用。而這些系統調用的最終落實則是file_operations結構體中成員函數,它們是字符設備驅動與內核的接口。以TQ210的按鍵驅動爲例:

/*設備操做集合*/
static struct file_operations btn_fops = {
    .owner = THIS_MODULE,
    .open = button_open,
    .release = button_close,
    .read = button_read
};

上面代碼中的button_open、button_close、button_read是要在驅動中本身實現的。file_operations結構體成員函數有不少個,下面就選幾個常見的來展現:

2.1 open()函數

原型:

int(*open)(struct inode *, struct file*);
/*打開*/

2.2 read()函數

原型:

ssize_t(*read)(struct file *, char __user*, size_t, loff_t*);
/*用來從設備中讀取數據,成功時函數返回讀取的字節數,出錯時返回一個負值*/

2.3 write()函數

原型:

ssize_t(*write)(struct file *, const char__user *, size_t, loff_t*);
/*向設備發送數據,成功時該函數返回寫入的字節數。若是此函數未被實現,
當用戶進行write()系統調用時,將獲得-EINVAL返回值*/

2.4 close()函數

原型:

int(*release)(struct inode *, struct file*);
/*關閉*/

2.5補充說明

1. 在Linux字符設備驅動程序設計中,有3種很是重要的數據結構:struct file、struct inode、struct file_operations。struct file 表明一個打開的文件。系統中每一個打開的文件在內核空間都有一個關聯的struct file。它由內核在打開文件時建立, 在文件關閉後釋放。其成員loff_t f_pos 表示文件讀寫位置。struct inode 用來記錄文件的物理上的信息。所以,它和表明打開文件的file結構是不一樣的。一個文件能夠對應多個file結構,但只有一個inode結構。其成員dev_t i_rdev表示設備號。struct file_operations 一個函數指針的集合,定義能在設備上進行的操做。結構中的成員指向驅動中的函數,這些函數實現一個特別的操做, 對於不支持的操做保留爲NULL。

2. 在read( )和write( )中的buff 參數是用戶空間指針。所以,它不能被內核代碼直接引用,由於用戶空間指針在內核空間時可能根本是無效的——沒有那個地址的映射。所以,內核提供了專門的函數用於訪問用戶空間的指針:

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

3.驅動註銷

3.1 刪除cdev

在字符設備驅動模塊卸載函數中經過cdev_del()函數向系統刪除一個cdev,完成字符設備的註銷。

/*原型:*/
void cdev_del(struct cdev *);
/*例:*/
cdev_del(&btn_cdev);

3.2 釋放設備號
在調用cdev_del()函數從系統註銷字符設備以後,unregister_chrdev_region()應該被調用以釋放原先申請的設備號。

/*原型:*/
void unregister_chrdev_region(dev_t from, unsigned count);
/*例:*/
unregister_chrdev_region(MKDEV(major, 0), 1);

4.字符設備驅動程序基礎

4.1 結構體

在Linux2.6 內核中,使用cdev結構體來描述一個字符設備,cdev結構體的定義以下:

struct cdev {
    struct kobject kobj;
    struct module *owner; /*一般爲THIS_MODULE*/
    struct file_operations *ops; /*在cdev_init()這個函數裏面與cdev結構聯繫起來*/
    struct list_head list;
    dev_t dev; /*設備號*/
    unsigned int count;
};

MAJOR(dev_t dev)cdev 結構體的dev_t 成員定義了設備號,爲32位,其中12位是主設備號,20位是次設備號,咱們只需使用二個簡單的宏就能夠從dev_t 中獲取主設備號和次設備號:

MINOR(dev_t dev)

相反地,能夠經過主次設備號來生成dev_t:

MKDEV(int major,int minor)

4.2 Linux2.6內核提供一組函數用於操做cdev結構體

(1)void cdev_init(struct cdev*,struct file_operations *);
(2)struct cdev *cdev_alloc(void);
(3)int cdev_add(struct cdev *,dev_t,unsigned);
(4)void cdev_del(struct cdev *);

其中(1)用於初始化cdev結構體,並創建cdev與file_operations 之間的鏈接。(2)用於動態分配一個cdev結構,(3)向內核註冊一個cdev結構,(4)向內核註銷一個cdev結構。

4.3 Linux2.6內核分配和釋放設備號

在調用cdev_add()函數向系統註冊字符設備以前,首先應向系統申請設備號,有二種方法申請設備號,一種是靜態申請設備號:

int register_chrdev_region(dev_t from,unsigned count,const char *name)

另外一種是動態申請設備號:

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

其中,靜態申請是已知起始設備號的狀況,如先使用cat /proc/devices 命令查得哪一個設備號未事先使用(不推薦使用靜態申請);動態申請是由系統自動分配,只需設置major = 0便可。

相反地,在調用cdev_del()函數從系統中註銷字符設備以後,應該向系統申請釋放原先申請的設備號,使用:

void unregister_chrdev_region(dev_t from,unsigned count);

4.4 cdev結構的file_operations結構體

這個結構體是字符設備當中最重要的結構體之一,file_operations 結構體中的成員函數指針是字符設備驅動程序設計的主體內容,這些函數實際上在應用程序進行Linux 的 open()、read()、write()、close()、seek()、ioctl()等系統調用時最終被調用。

struct file_operations {
    /*擁有該結構的模塊計數,通常爲THIS_MODULE*/
    struct module *owner;
    /*用於修改文件當前的讀寫位置*/
    loff_t (*llseek) (struct file *, loff_t, int);
    /*從設備中同步讀取數據*/
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

    /*向設備中寫數據*/
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
   
    /*輪詢函數,判斷目前是否能夠進行非阻塞的讀取或寫入*/
    unsigned int (*poll) (struct file *, struct poll_table_struct *);

    /*執行設備的I/O命令*/
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

    /*用於請求將設備內存映射到進程地址空間*/
    int (*mmap) (struct file *, struct vm_area_struct *);
 
    /*打開設備文件*/
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
  
    /*關閉設備文件*/
    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 (*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);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);

4.5 file結構

file 結構表明一個打開的文件,它的特色是一個文件能夠對應多個file結構。它由內核再open時建立,並傳遞給在該文件上操做的全部函數,直到最後close函數,在文件的全部實例都被關閉以後,內核才釋放這個數據結構。

在內核源代碼中,指向 struct file 的指針一般比稱爲filp,file結構有如下幾個重要的成員:
內核用inode 結構在內部表示文件,它是實實在在的表示物理硬件上的某一個文件,且一個文件僅有一個inode與之對應,一樣它有二個比較重要的成員:

struct file{
    mode_t fmode; /*文件模式,如FMODE_READ,FMODE_WRITE*/
    ...
    loff_t f_pos; /*loff_t 是一個64位的數,須要時,須強制轉換爲32位*/
    unsigned int f_flags; /*文件標誌,如:O_NONBLOCK*/
    struct file_operations *f_op;
    void *private_data; /*很是重要,用於存放轉換後的設備描述結構指針*/
    ...
};

4.6 inode結構

struct inode{
    dev_t i_rdev; /*設備編號*/
    struct cdev *i_cdev; /*cdev 是表示字符設備的內核的內部結構*/
};

能夠從inode中獲取主次設備號,使用下面二個宏:

/*驅動工程師通常不關心這二個宏*/
unsigned int imajor(struct inode *inode);
unsigned int iminor(struct inode *inode);

4.7 字符設備驅動模塊加載與卸載函數

在字符設備驅動模塊加載函數中應該實現設備號的申請和cdev 結構的註冊,而在卸載函數中應該實現設備號的釋放與cdev結構的註銷。

咱們通常習慣將cdev內嵌到另一個設備相關的結構體裏面,該設備包含所涉及的cdev、私有數據及信號量等等信息。常見的設備結構體、模塊加載函數、模塊卸載函數形式以下:

/*設備結構體*/
struct xxx_dev{
    struct cdev cdev;
    char *data;
    struct semaphore sem;
    ...
};
/*模塊加載函數*/
static int __init xxx_init(void)
{
    ...
    //初始化cdev結構;
    //申請設備號;
    //註冊設備號;
    //申請分配設備結構體的內存;
}
/*模塊卸載函數*/
static void __exit xxx_exit(void)
{
    ...
    //釋放原先申請的設備號;
    //釋放原先申請的內存;
    //註銷cdev設備;
}

4.8 字符設備驅動的file_operations結構體成員函數

/*讀設備*/
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    /*使用filp->private_data獲取設備結構體指針;*/
    /*分析和獲取有效的長度;*/
    /*內核空間到用戶空間的數據傳遞*/
    copy_to_user(void __user *to, const void *from, unsigned long count);
}
/*寫設備*/
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    /*使用filp->private_data獲取設備結構體指針;*/
    /*分析和獲取有效的長度;*/ 
    /*用戶空間到內核空間的數據傳遞*/
    copy_from_user(void *to, const void __user *from, unsigned long count);
}
/*ioctl函數*/
static int xxx_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg) 
{
   switch(cmd){
      case xxx_CMD1:
         break;
      case xxx_CMD2:
         break;
      default:
         return -ENOTTY; /*不能支持的命令*/
   }
   return 0;
}

4.9 字符設備驅動文件操做結構體模板

struct file_operations xxx_fops = {
    .owner = THIS_MODULE,
    .open = xxx_open,
    .read = xxx_read,
    .write = xxx_write,
    .close = xxx_release,
    .ioctl = xxx_ioctl,
    .lseek = xxx_llseek,
};

原文來自:http://blog.jobbole.com/86531/

本文地址:http://www.linuxprobe.com/linux-device-driver.html

相關文章
相關標籤/搜索