字符設備驅動框架講解


哈哈哈哈我的號又活啦~~~距上一篇博客已經過去兩年半了QAQ...

中間畢業之后參加工作,暫時脫離碼農的世界近兩年,全世界跑了跑,發現還是敲代碼好玩哈哈哈哈啊哈哈~~

現在從事芯片底層開發,類似於微碼...最近給新員工將東西,比較基礎,也在這里貼出來,表示我又回到咱碼農行列啦~~~

 

廢話少說,先上圖:

 

 

1、cdev結構體

struct cdev {

    struct kobject kobj;

    struct module *owner;

    struct file_operations *ops;

    struct list_head list;

    dev_t dev;

    unsigned int count;

};

簡單介紹幾個重要成員:

1.1 dev_t:設備號,為32位,其中12位為主設備號,20位為次設備號;

    使用宏MAJOR(dev_t dev)和MINOR(dev_t dev)來獲取主設備號和次設備號;

    使用MKDEV(int major, int minor)來通過主設備號和次設備號生成dev_t;

1.2 file_operations:字符設備驅動提供給虛擬文件系統的接口函數;

 

Linux內核提供了一組函數來操作cdev結構體:

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

    struct cdev *cdev_alloc(void);

    void cdev_put(struct cdev *p);

    int cdev_add(struct cdev *, dev_t, unsigned);

    void cdev_del(struct cdev *);

cdev_init用於初始化cdev的成員,並建立cdev和file_operations之間的連接,所以我們通過ioctl能夠准確調用到對應字符設備並進行其他操作;

cdev_alloc用於動態申請一個cdev內存;

cdev_add和cdev_del分別向系統添加和刪除一個cdev,完成字符設備的注冊和注銷;因為cde_add通常發生在字符設備驅動模塊加載函數中,cdev_del函數通常發生在字符設備驅動模塊卸載函數中;

 

2、分配和釋放設備號

    在調用cdev_add函數向系統注冊字符設備之前,應該首先調用register_chrdev_region()或者alloc_chrdev_region()向系統申請設備號,Resister函數用於在已知起始設備的設備號的情況,而alloc用於設備號未知,向系統動態申請未被占用設備號的情況。alloc相對於register的優點是它會自動避開設備號重復的沖突。

    在cdev_del函數從系統注銷字符設備之后,unregister_chrdev_region應該被調用用以釋放原先申請的設備號;

 

3、file_operations結構體

    該結構體中的成員函數是字符設備驅動程序設計的主體內容,結構體目前已經比較龐大,它的定義這里就不細看了,簡單介紹幾個主要成員函數:

    read函數用來從設備中讀取數據,成功是返回讀取的字節數,出錯時返回負值,這個與用戶空間的ssize_t read和size_t fread對應。

    write函數用來從設備中發送數據,成功是返回寫入的字節。如果此函數未被實現,當用戶進行write操作的時候。將得到一個-EINVAL返回值,這個與用戶空間的ssize_t write和size_t write對應。

    unlocked_ioctl提供設備相關控制命令的實現(非讀寫),當調用成功是,返回一個非負值。

    在內核空間與用戶空間的界面處,內核檢查用戶空間緩沖區的合法性顯得尤為重要,非法侵入者可以偽造一片內核空間的緩沖區地址傳入系統調用的接口,讓內核對這個evil指針指向的內核空間進行填充數據。

 

4、ioctl函數

    ioctl一般對應的是file_operations結構體的unlocked_ioctl成員函數,三個入參:filp,cmd和arg,其中filp指的是文件結構體指針,cmd是設備支持的命令,arg指的是涉及到用戶數據的參數。

    重點看一下cmd的組成,Linux建議I/O控制命令由設備類型、序列號、方向和數據尺寸組成,如:

 

 設備類型  序列號  方向  數據尺寸
 8位 8位  2位  14位 

    設備類型字段為一個“幻數”,可以使0x0 ~ 0xff的值,新設備驅動定義的幻數要避免與已使用的沖突。

 

    命令碼的序列號也是8位寬,這個通常是定義給某一驅動設備支持的命令,如:0x0 0x1 0x2...

    方向字段為2位,該字段表示數據的傳送方向,可能的值為:_IOC_NONE(無數據傳輸),_IOC_READ(讀),_IOC_WRITE(寫)和_IOC_READ|_IOC_WRITE(雙向),數據傳送的方向是從應用程序的角度來看的。

    數據長度字段表示涉及的用戶數據的大小,(用於用戶數據的輸入與輸出)。

    內核定義了_IO(),_IOR(),_IOW()和_IOWR()這4個宏來輔助生成命令,定義如下:

    #define _IO(type, nr) _IOC(NONE, (type), (nr), 0)

    #define _IOR(type, nr, size) _IOC(_IOC_READ, (type), (nr), (_IOC_TYPECHECK(size)))

    #define _IOW(type, nr, size) _IOC(_IOC_WRITE, (type), (nr), (_IOC_TYPECHECK(size)))

    #define _IOWR(type, nr, size)  _IOC(_IOC_READ | _IOC_WRITE, (type), (nr), (_IOC_TYPECHECK(size)))

    這幾個宏的作用是根據傳入的type, nr, size和宏名隱藏的方向來組合生成命令碼。

    通過這些宏來聲明設備ioctl命令的好處在於:避免以單純的數字作為命令的時候容易出現不同設備使用相同命令而產生命令碼污染;在通過ioctl進入內核之后可以通過_IOC_TYPE(), _IOC_SIZE(), _IOC_NR()對用戶傳入的命令的設備類型、用戶數據尺寸和命令序列號等信息進行校驗,及時攔截非法請求;

 

    關於文件結構體file的私有數據private_data的使用,后續再講,實際上,在大多數Linux驅動中遵循一個潛規則:將文件的私有數據private_data指向設備結構體,再在訪問函數中通過private_data來訪問設備結構體,這種做法在某一類驅動不知包括一個設備,而是包括兩個及其以上的設備的時候,采用private_data的優勢就顯現出來了。

 

OK 現在再回頭看看剛才那張圖,是不是理解了一些呢?多多對比已有驅動代碼,就會更加深刻理解的,linux驅動萬變不離其宗~~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM