LDD3筆記:第三章 字符設備驅動

平凡的我們不能預見虛無且略帶迷茫的明天,唯有着眼當下做好我們手邊的事,這纔是王道。

  初來咋到不敢造次,追尋偉人,以期借肩膀一用,弱弱的陳述,積極的整理。舉網絡資源之海量,去迷茫衆生之浮躁。新人筆記懇請批評指正。在驅動界,梵高說過:得LDD精髓者得天下。恩...那就開始吧...

Chapter03:Char Drivers

  我們的最終目的是編寫一個模塊化的字符驅動, 但是我們不會在本章討論模塊化的事情.

本章展示從一個真實設備驅動提取的代碼片段: scull( Simple Character Utility for Loading Localities). scull 是一個字符驅動, 操作一塊內存區域好像它是一個設備.

  編寫驅動的第一步是定義驅動將要提供給用戶程序的能力(機制).整本書包括內核源碼一直都在強調 機制 (設備能幹什麼)與 策略 (如何使用這些功能)的分離,具體如何實現真的很難窺測到,這需要經驗的積累。

一.主設備號和次設備號 

  對字符設備的訪問是通過文件系統內的設備名(被稱爲特殊文件、設備文件或文件系統的節點)來進行的。Major num 標識與(一類)設備對應的驅動程序(一般是一對一,也有例外),而Minor num則被內核用來標識驅動程序所實現的設備,也即一個驅動可被多個設備共同享有,其實在內存中這一類設備公用一段程序代碼,只是私有一份自己的數據而已,吶,這其實是通過虛擬內存管理實現的,普通的x86上有段管理和頁管理,有資料說Linux只用頁管理且是3級管理(也有說是四級),這也會涉及到TLB(Table Lookaside Buffer)她存放最近訪問過的頁表(虛擬地址到物理地址的轉換表)。什麼是頁呢?在Linux內核當中,物理內存被描述成頁,用結構體struct page()。

  麻煩的是要分清幾個概念:程序使用的邏輯地址,進程使用的線性地址,處理器使用的物理地址,當然還有內核虛擬地址,內核邏輯地址。

邏輯地址 :你在寫程序時要在程序體當中會用到內存地址吧,程序當中都是相對某起始點的。咋說呢,好像是你寫彙編的時候,會定義一個_start吧?然後以她做偏移的不是麼?

線性地址 :與特定的處理器有關係,x86是32位地址線可尋址4G空間,IA64是64位的可以尋址...反正老大一塊了。注意哦每個進程都有這麼大的一個獨立的空間!那爲什麼能同時運行那般多的進程呢?打個比方:一個教室可以容納10個人,有十個班級(每班2人)要上課,怎麼辦呢?這時你可以理解成要上的是選修課,而同時呢就只有那麼幾個人喜歡聽這課,在大學不總流行:選修課必逃,必修課選逃麼!這樣就可以容納十個班級了,甚至還沒坐滿呢!哎,不對啊,那有一天老師突然要點名咋整啊?這樣怎麼讓老師知道十個班級20個人都來上課了呢,位置不夠啊?!呵呵,結合經驗你就該知道了:路人甲被點完名就溜出去了,這時呢路人乙只要在同學的通知下說今天要點名並能及時趕來,趁着路人甲出去,混進來了,並能及時的喊“到”不就完事了麼,假象是他們都來聽老師的課了!

Linux內核當中也是這樣,用到的時候我在把你叫到內存當中,來替換那些已經被讀過或寫過的(標記爲髒的)。

物理地址 :就是真實的32位或者64位地址了

內核邏輯地址 :內核將4G的地址空間分成了1G+3G兩部分,自己使用1G也叫內核地址空間(低端內存(16M以上896M以下)),也叫內核邏輯地址,用戶空間3G(也叫高端內存)。所以內核無法直接操作(安全需要)沒有映射進內核地址空間的內存,其他3G空間是不可能被內核直接訪問到的,故而內核要訪問超出這1G的內存只能通過映射將超出這1G的地址的內存採用映射的機制把他們一點一點的映射在1G的範圍內!通常邏輯地址與物理地址存在一個固定的偏移量。

內核虛擬地址: 內核虛擬地址類似於邏輯地址, 它們都是從內核空間地址到物理地址的映射. 內核虛擬地址不必有邏輯地址空間具備的線性的, 一對一到物理地址的映射, 但是. 所有的邏輯地址是內核虛擬地址, 但是許多內核虛擬地址不是邏輯地址. 例如, vmalloc 分配的內存有虛擬地址(但沒有直接物理映射). kmap 函數(本章稍後描述)也返回虛擬地址. 虛擬地址常常存儲爲指針變量.

當需要訪問一頁數據時時,處理器呢先去TLB轉悠轉悠如果運氣好能遇到知音呢就直接把她攬到懷裏,有點霸道了,就直接把表中的頁拿出來用了,可是呢如果沒遇到咋辦呢,就會產生一個缺頁異常,從磁盤上把頁讀進來。其實整個系統中最慢的部件就屬磁盤了,其尋道是靠機械手臂!還好現在有固態磁盤了,速度相當驚人!扯遠了,還是回到主題說字符吧

設備編號的內部表達:

  在內核中,dev_t類型(在<linux/types.h>中定義)用來保存設備編號——包括主設備號和次設備號。devt_t是一個32位數,其中12位表示Major num,20位表示Minor num。我們不能對設備編號的組織方式做任何的假設!而必須用相應的宏去獲取。<linux/kdev.h>中有關宏:

    MAJOR(dev_t);

    MINOR(dev_t);同時可以用MKDEV(int major,int minor);將主次設備號轉換成dev_t類型。

分配和釋放設備編號:

  ...

分配和釋放設備號:

在建立一個字符設備之前,驅動需要首先獲得一個或多個設備編號。

靜態方式:

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

         first:要分配的起始設備編號,經常爲0

         count:請求連續設備編號的個數

         name:則被sysfs文件系統使用

其返回0,表示分配成功

動態方式:

         intalloc_chrdev_region(dev_t *dev, unsigned int firstminor,

unsigned int count, char *name);

         dev:用於保存已分配範圍的第一個編號

         firstminor:請求的第一個次設備號,通常是0

         count:請求連續設備編號的個數

         name:則被sysfs文件系統使用

需要記住的是:內核中顯式創建的不用的資源就必須顯式的把它釋放掉!

釋放設備號:

         void unregister_chrdev_region(dev_tfrom, unsigned count);/*一般會在模塊清除函數中調用*/

         驅動程序將設備號和內部函數相連以實現設備的操作。

動態分配主設備號:

         分配主設備號的最佳方式:默認採用動態方式,同時保留在加載甚至是編譯時指定主設備號的餘地。

1.      使用一個全局變量scull_major來保存所選擇的設備號,scull_minor來放次設備號

2.      if (scull_major) {

dev = MKDEV(scull_major, scull_minor);

result = register_chrdev_region(dev,scull_nr_devs, "scull");

} else {

result = alloc_chrdev_region(&dev,scull_minor, scull_nr_devs,

"scull");

scull_major = MAJOR(dev);

}

if (result < 0) {

printk(KERN_WARNING "scull: can't get major %d\n",scull_major);

return result;

}

發佈了29 篇原創文章 · 獲贊 55 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章