Linux驅動開發入門與實踐(一)

liaoxingyu1發表於2015-04-08

    最近這一段時間,把之前學的linux基礎的東西撿了回來,正式開始接觸驅動了。接觸後發現底層的東西還是相當的繁瑣,核心裡的巨集定義數不勝數,結構體,指標更是撲面而來。驅動主要分為三部分,分別是字元裝置、塊裝置以及網路介面裝置。這裡先總結下簡單字元裝置驅動相關的東西。


參考資料:1.《Linux驅動開發入門與實踐》 鄭強

                    2.《國嵌培訓資料以及視訊》


    參考資料1個人覺得不是很適合拿來上手,雖然講的點多,但是卻不細緻。而2講的點少,但比較細也比較淺顯。


字元裝置驅動:

    字元裝置理解起來可以和塊裝置進行區分理解。

  字元=只能一個一個位元組讀寫 

     塊=可以從任意位置讀取一定長度資料的裝置,不必按照先後順序。


如果對應上實物的話:字元裝置有:滑鼠、鍵盤、串列埠、控制檯···

                                        塊裝置有:SD卡、硬碟、磁碟、U盤···


Linux具體是怎樣去操作裝置驅動的?

    在Linux系統中,每個字元裝置或者塊裝置都在 /dev目錄下有對應一個裝置檔案。Linux對裝置進行操控,本質上就是通過這些檔案來操作的。這樣相當於有了一套標準,程式設計師便可以撇開裝置的差異化從而按照標準進行程式設計。

 區分一個現成的Linux系統當前的裝置檔案的屬性,只需要cd /dev 然後 ls -l 


出現以下條目:


crw-rw----+    1  root  root  14,      12       12-21    22:56     adsp


第一個字元c 就表示char 字元型裝置 ,b則表示block 塊裝置。而5、6欄位分別表示主裝置號和次裝置號。

為什麼需要主裝置號和次裝置號呢?

主裝置號用來區分不同型別的裝置,如USB裝置和串列埠裝置。次裝置號則是用來區分某一型別裝置中不同的子裝置。如串列埠裝置不止一種,所以通過此裝置號進行區分。


主裝置號和次裝置號的表示(具體程式碼實現)

Linux中用dev_t 型別來表示裝置號。其實它本質上就是一個無符號長整型,

typedef  u_long  dev_t


u_long 在32位機裡為4個位元組 ,在64位裡為8位元組。由於自己學的是ARM,以32位機為例。其中高12位表示主裝置號,低20位表示次裝置號。

裝置號的獲得與申請方式:

主要有2種,第一種為靜態申請,第二種自然就是動態申請。採用的方法不同,前期的基礎工作也就不同。


先說說靜態申請:靜態申請毫無疑問需要程式設計師自己給分配一個裝置號,那麼怎樣判斷一個裝置號是否可用呢?或者說當前系統裡沒有被其他裝置佔用呢?

方法:可以讀取 /proc/devices 檔案來獲得裝置號。

指令如下:cat /proc/devices 

 

知道當前系統佔用的裝置號後,比如要需要設定的裝置號為200,那麼怎麼構建該裝置號呢?

Linux系統採用MKDEV來實現,其中

dev_t devno=MKDEV(ma,mi);其中ma為主裝置號,mi為次裝置號。


構建完裝置號之後,還需要進行申請,靜態申請的方法為:

int  register_chrdev_region(dev_t from, unsigned count , const char *name);   標頭檔案:<fs/char_dev.c>

from 是要分配裝置號範圍起始值,count表示需要申請裝置號的個數。 name則是裝置名稱,注意不能超過64位元組。


動態分配:靜態分配由於人為因素,很可能導致衝突,所以Linux自己給自動分配一個未使用的裝置號則更加有利。

動態申請不需要自己構建裝置號,呼叫函式:

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

成功後返回的裝置號儲存在dev指向的dev_t 型別的變數裡。baseminor 為次裝置號的起始號,count為子裝置數 name為裝置名稱。


申請完後,要將字元裝置註冊到系統中,才能使用。

cdev 用來描述字元裝置。

struct cdev {

    struct kobject kobj;

    struct module *owner;/*指向包含該結構的模組的指標,用於引用計數*/

     const struct file_operations *ops;/*指向字元裝置操作函式集的指標*/ 

    struct  list_head list; /*該結構將使用該驅動的字元裝置連成一個連結串列*/

    dev_t dev; /*該字元裝置的起始裝置號*/

    unsigned int count;/*使用該字元裝置驅動的裝置數量*/

};


kobj結構用於核心管理字元裝置,驅動開發人員一般不使用。

ops是指向file_operations 操作函式結構體指標。

list為雙向連結串列,用於將其他結構體連線成一個雙向連結串列,其連線到inode結構體i_devices成員。而i_devices也是一個list_head結構。這樣使得cdev結構與inode結點組成一個雙向連結串列。


檔案系統中對字元裝置檔案的訪問

對於一個字元裝置檔案, 其inode->i_cdev 指向字元驅動物件cdev, 如果i_cdev為 NULL ,則說明該裝置檔案沒有被開啟.

由於多個裝置可以共用同一個驅動程式.所以,通過字元裝置的inode 中的i_devices 和 cdev中的list組成一個雙向連結串列。inode表示/dev下的裝置檔案。每一個字元裝置在/dev下都有一個裝置檔案,開啟裝置檔案就相當於開啟相應的字元裝置。例如應用程式開啟裝置檔案A,那麼系統就會產生一個inode結點,這樣可以通過inode結點的i_cdev欄位找到cdev字元結構體。

 

 首先,系統呼叫open開啟一個字元裝置的時候, 通過一系列呼叫,最終會執行到 chrdev_open.

  (最終是通過呼叫到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open. 這一系列的呼叫過程,本文暫不討論)

  int chrdev_open(struct inode * inode, struct file * filp)

  chrdev_open()所做的事情可以概括如下:

  1. 根據裝置號(inode->i_rdev), 在字元裝置驅動模型中查詢對應的驅動程式, 這通過kobj_lookup() 來實現, kobj_lookup()會返回對應驅動程式cdev的kobject.

  2. 設定inode->i_cdev , 指向找到的cdev.

  3. 將inode新增到cdev->list的連結串列中.

  4. 使用cdev的ops 設定file物件的f_op

  5. 如果ops中定義了open方法,則呼叫該open方法

  6. 返回.

  執行完 chrdev_open()之後,file物件的f_op指向cdev的ops,因而之後對裝置進行的read, write等操作,就會執行cdev的相應操作.



相關文章