1. 程式人生 > >從需求的角度去理解Linux系列:總線、設備和驅動

從需求的角度去理解Linux系列:總線、設備和驅動

電平 可移植性 需求 講解 好處 鼠標 地理 embed 博客專家

筆者成為博客專家後整理以前原創的嵌入式Linux系列博文,現推出以讓更多的讀者受益。

《從需求的角度去理解linux系列:總線、設備和驅動》是一篇有關如何學習嵌入式Linux系統的方法論文章,也是從需求的角度去理解Linux系統軟件的開篇,期待此系列文章日後會是學習嵌入式Linux的標桿!

這是作者精心撰寫的經驗總結,希望嵌入式Linux的學習者仔細領會,多讀幾遍也無妨。

一、軟件、面向對象、軟件框架

軟件是為了解決現實問題而產生的,面向對象的軟件思維是解決普遍現實問題的一種有效的抽象方法,而軟件框架指的是用面向對象的思維去解決某種特定領域的問題而專門設計的一套行之有效的解決方案。

一般地,Java/C++編程反映面向對象的軟件思維,而像Android Framework、Windows MFC和Linux的QT則代表應用層的軟件框架。前述應用框架要解決的問題包括應用消息處理、UI控件顯示和處理、資源管理等等。軟件框架帶來的好處就是對於解決某個領域問題,框架會幫你完成80%的開發工作量,而你只需要完成20%的開發工作量。

Linux平臺上的各個子系統,如設備驅動模型、input子系統、I2C總線、frame buffer驅動等等都屬於軟件框架,它是針對特定的硬件體系需求以面向對象的思維去設計的一種軟件解決方案,而且已經經過長時間的多平臺驗證。嚴格意義上,將子系統歸入軟件抽象組件會更加貼切,而軟件框架表現為一組抽象組件及其組件實例之間的交互。軟件框架和軟件組件的特點都是解決特點領域問題,可以高度重用設計。

Linux系統以C語言開發為主,C語言在教科書上會被認為是過程語言。事實上,面向對象只是一種軟件思維,並不局限於某種語言,只不過C++/JAVA在娘胎(編譯器)裏就已經得到支持,而C語言通過struct數據結構和函數指針一樣可以出色地完成面向對象抽象的工作。Linux系統絕對是利用C語言進行面向對象編程的開山鼻祖,處處洋溢著軟件藝術的光輝!

二、理解好軟件需求是學習好軟件框架的前提

對於學習著來說,軟件需求(即軟件要解決的問題)和軟件框架都已經存在。但學習者往往只關註軟件框架,因為學習的終極目標也是為了掌握軟件框架並使用它來解決自己的問題。對於一般的知識傳播者來說(例如學校老師、機構培訓師;教科書或者網絡文獻),往往也是著重於解讀軟件框架的組成和原理。

事實上,對於一個代碼量有幾萬甚至幾十萬行代碼量的軟件框架,一開始接觸就學習原理和代碼並不是好事。這種做法很像是試圖從軟件框架的學習理解中得出軟件需求,有太多的未知就接觸源碼,那理解過程會非常痛苦,往往會感到非常迷惑。

我認為,深入地理解好需求,再去理解軟件框架會事半功倍。

甚至,當達到一定的水平後,知道了需求,完全可以去猜測軟件框架的實現。

三、Linux系統的軟件需求


對於軟件需求,最容易讓人聯想到的是一種具體的業務需求,如12306購票業務等等。Linux是一種操作系統,操作系統的軟件需求是什麽?操作系統是為了給應用層提供良好的接口而進行總線設備驅動管理、內存管理、文件管理、進程管理等等。總線設備驅動管理就是我們今天要談的主題。Linux平臺有各種子系統、各種總線、各種驅動,Linux系統對它們的管理就是軟件框架的組成。我們要理解好Linux已有的框架,就要清晰地知曉其解決的問題,也就是其管理了哪些硬件設備,這些硬件設備的特點是什麽,這些設備的訪問方式是什麽。

可以說,深入地理解硬件體系是理解好Linux總線設備驅動框架的前提!從面向對象的角度,我們要弄清楚,物理意義上的硬件是什麽,而對應的軟件對象是如何表述的。

以下闡述會重點講述軟件需求,作為以後分析框架的基礎。

四、總線、驅動、設備

1.總線

總線代表著同類設備需要共同遵守的工作時序,不同的總線對於物理電平的要求是不一樣的,對於每個比特的電平維持寬度也是不一樣,而總線上傳遞的命令也會有自己的格式約束。如I2C總線、USB總線、PCI總線等等。以I2C總線為例,在同一組I2C總線上連接著不同的I2C設備。

2.設備

設備代表真實的、具體的物理器件,在軟件上用器件的獨特的參數屬性來代表該器件。如I2C總線上連接的I2C從設備都有一個標識自己的設備地址,由這個設備地址來確定主設備發過來的命令是否該由它來響應。

3.驅動

驅動代表著操作設備的方式和流程。對於應用來說,應用程序open打開設備後,接著就read訪問這個設備,驅動就是如何實現這個訪問的具體的過程。驅動主要包括兩部分,第一是通過對SOC的控制寄存器進行編程,按總線要求輸出時序和命令,成功地與外圍設備進行交互;第二是對第一步中得到的數據進行處理,並向應用層提供特定格式的數據。

a.不同總線的設備的驅動過程是不一樣的,這個很容易理解,USB鼠標的驅動和I2C EEPROM的讀時序肯定是不一樣的,訪問時序的產生和控制也是驅動的一部分。

b.同種總線不同設備類型的設備驅動也是不一樣的。如I2C電容屏設備,對於讀read來說就是在datasheet規定的地址上去讀觸摸點的X和Y坐標,而I2C EEPROM的讀操作是讀取存儲的內容,兩種設備的datasheet是不一樣的,驅動自然是不一樣的。

c.同種總線的同類設備的設備驅動也可能是不一樣的。例如對於觸摸屏,TSC2003只支持單點觸控,而FT5X06支持多點觸摸。在獲取觸控坐標時,前者只需要獲得一個點的數據就返回,而後者則需要先獲得當前有幾個點的數據,然後再把所有點的坐標都讀出來。

在驅動的操作中,一般都會用到GPIO和中斷等硬件資源,如上圖的SDA和SCL會連接到SOC芯片的具體的兩個GPIO引腳,而I2C讀寫時一般都采用中斷控制的方式(查詢讀寫是否完成比較低效,浪費CPU)。如果我們在驅動中直接針對具體的引腳來編程,那這個驅動的平臺可移植性就比較差,因為不同的產品設計可能引腳不一樣。所以,為了提高驅動的可移植性,Linux把驅動要用到的GPIO和中斷等資源剝離給設備去管理。即在設備裏面包含其自己的設備屬性,還包括了其連接到SOC所用到的資源。而驅動重點關註操作的流程和方法。

4.再談總線

第1點中談到的總線只是物理意義上的表述,總線就是在行業中制定出標準,明確規定時序的格式。我們在第3點中談到,在軟件層面上,時序的產生和控制由驅動負責。那我們要思考在軟件層面上,總線的職責是什麽?

總線在軟件層面主要是負責管理設備和驅動。

a.設備要讓系統感知自己的存在,設備需要向總線註冊自己;同樣地,驅動要讓系統感知自己的存在,也需要向總線註冊自己。設備和總線在初始化時必須要明確自己是哪種總線的,I2C設備和驅動不能向USB總線註冊吧。

b.多個設備和多個驅動都註冊到同一個總線上,那設備怎麽找到最適合自己的驅動呢,或者說驅動怎麽找到其所支持的設備呢?這個也是由總線負責,總線就像是一個紅娘,負責在設備和驅動中牽線。設備會向總線提出自己對驅動的條件(最簡單的也是最精確的就是指定對方的名字了),而驅動也會向總線告知自己能夠支持的設備的條件(一般是型號ID等,最簡單的也可以是設備的名字)。那設備在註冊的時候,總線就會遍歷註冊在它上面的驅動,找到最適合這個設備的驅動,然後填入設備的結構成員中;驅動註冊的時候,總線也會遍歷註冊在其之上的設備,找到其支持的設備(可以是多個,驅動和設備的關系是1:N),並將設備填入驅動的支持列表中。我們稱總線這個牽線的行為是match。牽好線之後,設備和驅動之間的交互紅娘可不管了。

c.總線在匹配設備和驅動之後驅動要考慮一個這樣的問題,設備對應的軟件數據結構代表著靜態的信息,真實的物理設備此時是否正常還不一定,因此驅動需要探測這個設備是否正常。我們稱這個行為為probe,至於如何探測,那是驅動才知道幹的事情,總線只管吩咐得了。所以我們可以猜測在總線的管理代碼中會有這樣的邏輯:

if(match(device, driver) == OK)

driver->probe();

5.再談驅動

假設設備正常,探測成功,這時就代表應用程序可以通過驅動來訪問操作這個設備了。事實上是這樣嗎?仔細想想還少了什麽東西。應用層通過什麽來訪問操作這個設備?想起來嗎?我們公眾號“嵌入式企鵝圈”的第一篇文章《Linux字符設備驅動剖析》中曾清晰地分析了Linux字符設備驅動的開發和訪問過程,在開篇即提到應用程序如何訪問設備:

int fd = open(“設備文件名”);

read(fd, buf, len);

write(fd, buf, len);

在這個應用程序中會涉及驅動兩個問題,一是設備文件名從何而來,二是應用層的open、read和write對應驅動哪些接口,是如何對應的。這些都是驅動要解決的問題。

a.總線匹配設備和驅動之後,驅動探測到設備正常,這時驅動是處於做好準備讓應用層來差遣了,但是設備文件名如果沒有創建,應用程序也不知從何入手。所以在驅動的probe探測成功之後,立即創建設備文件是最合適的時機。其通過sysfs文件系統、uevent事件通知機制和後臺應用服務mdev程序配合能夠成功地在/dev目錄創建對應的設備文件。

b.驅動要提供應用層open、read、write、ioctl等操作的對應接口,而且這些接口要向系統報備(註冊)自己,否則系統也不知道怎麽調用驅動,因為在上面的描述中從始至終都是設備、驅動和總線三個東西在唱戲,它們跟系統,嚴格意義是跟Linux的虛擬文件系統和設備文件系統還沒建立起關系來。即驅動要包括以下步驟:

B1.設備要提供struct file_operation結構定義的接口:

struct file_operations {

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

int (*ioctl) (struct inode *, struct file *, ...);

ssize_t (*read) (struct file *, char __user *,...);

ssize_t (*write) (struct file *, const char __user *, ...);

…}

這些接口將會對應到應用層的設備訪問操作。在這些接口中,其會根據第3點中提到的需求去完成自己的操作任務。

B2.應用層正常的訪問流程是:應用層操作->虛擬文件系統操作->具體文件系統操作->具體設備驅動的操作。虛擬文件系統VFS系統已經存在,具體文件系統操作對於字符設備來說非常簡單,我們姑且認為是字符設備文件系統devfs,此時字符設備驅動要做的是將自己的struct file_operations向devfs註冊,對應字符設備驅動是cdev_add函數。詳細的分析過程可以參考《Linux字符設備驅動剖析》。

所以我們可以想象在驅動driver的結構體中有一個probe接口,驅動要實現這個接口,而這個probe接口要完成的工作包括:

Driver->probe()

I.探測設備是否正常

II.cdev_add(struct file_operations)註冊操作接口

III. device_create()創建設備文件

6.繼續談驅動

做好以上準備即已萬事俱備的時候,等著應用程序來訪問操作了。通過《Linux字符設備驅動剖析》中open的整個過程,到最後會調用到具體驅動的open,接下來我們就要闡述一下設備驅動的struct file_operations中的接口都要做什麽。我們挑幾個主要的來講講,其余可以自己想象。

a.open一般會進行驅動的初始化,可能包括硬件的初始化和軟件的初始化。我們在第3點談驅動的時候,曾說明為了讓驅動更具移植性,會將驅動driver過程中使用到的具體GPIO和IRQ中斷等資源列入設備device的屬性內容。這時device數據結構中斷的GPIO和IRQ的標識都來源於SOC datasheet的物理地址定義。我們都知道Linux在運行過程中會使用到SOC的MMU內存管理單元來管理自己的內存,會將內存分為兩部分,內核空間(3G-4G)和用戶空間(0-3G),這兩塊地址空間都是虛擬線性地址空間,即程序編譯鏈接之後對應的地址空間,虛擬地址空間需要通過MMU和頁表來映射到實際的物理內存空間才能最終訪問到物理內存和物理IO等資源。而驅動操作硬件都處在內核空間,在open函數中主要包括以下操作:

a1.通過系統提供的資源獲取接口獲取到GPIO和IRQ等資源

a2.通過ioremap接口將GPIO和IRQ從物理地址空間映射到3G-4G中的虛擬地址空間

a3.根據具體的控制規格設置GPIO和IRQ相關的寄存器。

以上初始化的動作可能會出現在驅動probe探測的代碼中,那open的接口可以什麽都不做。

b. read:驅動的open如果成功,那整個訪問流程已經成功一大半了,因為open的流程足夠漫長和復雜。而read只是從用戶空間的fd文件句柄找到所屬進程的file文件結構,然後即可找出file_operations->read,其即是驅動的read接口。那就按著外網設備的規格和總線的時候進行操作,達到read設備的目的。Write也一樣。

c. ioctl一般是對設備進行參數設置。

隆重推薦本人以下原創文章,有助讀者系統、全面地理解嵌入式Linux的系統架構和驅動開發!

從需求的角度去理解Linux系列相關博文:

1. Linux字符設備驅動剖析

2. Linux設備文件的創建和mdev

3. 符設備驅動字、平臺設備驅動、設備驅動模型、sysfs的關系

4. Linux模塊化機制和module_init

5. Linux中斷完全分析

6. Linux input子系統分析之一:軟件層次

7. 全網絡對Linux input子系統最清晰、詳盡的分析

8. 陸續推出Framebuffer、I2C、MTD等子系統的分析

微信公眾號:嵌入式企鵝圈

1.忠於Linux源碼,百分百原創。

2.從上電第一行代碼、系統第一行代碼、模塊第一行代碼、應用第一行代碼,深入講解嵌入式軟件生命周期。
3 從需求出發,從架構著眼。

從需求的角度去理解Linux系列:總線、設備和驅動