1 嵌入式驅(qū)動開發(fā)到底學(xué)什么
嵌入式大體分為以下四個方向:
一、嵌入式硬件開發(fā):熟悉電路等知識,非常熟悉各種常用元器件,掌握模擬電路和數(shù)字電路設(shè)計的開發(fā)能力。熟練掌握嵌入式硬件知識,熟悉硬件開發(fā)模式和設(shè)計模式,熟悉ARM 32位處理器嵌入式硬件平臺開發(fā)、并具備產(chǎn)品開發(fā)經(jīng)驗。精通常用的硬件設(shè)計工具:Protel/PADS(PowerPCB)/Cadence/OrCad。一般需要有4~8層高速PCB設(shè)計經(jīng)驗。
二、嵌入式驅(qū)動開發(fā):熟練掌握Linux操作系統(tǒng)、系統(tǒng)結(jié)構(gòu)、計算機(jī)組成原理、數(shù)據(jù)結(jié)構(gòu)相關(guān)知識。熟悉嵌入式ARM開發(fā),至少掌握Linux字符驅(qū)動程序開發(fā)。具有單片機(jī)、ARM嵌入式處理器的移植開發(fā)能力,理解硬件原理圖,能獨立完成相關(guān)硬件驅(qū)動調(diào)試,具有扎實的硬件知識,能夠根據(jù)芯片手冊編寫軟件驅(qū)動程序。
三、嵌入式系統(tǒng)開發(fā):掌握Linux系統(tǒng)配置,精通處理器體系結(jié)構(gòu)、編程環(huán)境、指令集、尋址方式、調(diào)試、匯編和混合編程等方面的內(nèi)容;掌握Linux文件系統(tǒng)制作,熟悉各種文件系統(tǒng)格式(YAFFS2、JAFFS2、RAMDISK等);熟悉嵌入式Linux啟動流程,熟悉Linux配置文件的修改;掌握內(nèi)核裁減、內(nèi)核移植、交叉編譯、內(nèi)核調(diào)試、啟動程序Bootloader編寫、根文件系統(tǒng)制作和集成部署Linux系統(tǒng)等整個流程;、熟悉搭建Linux軟件開發(fā)環(huán)境(庫文件的交叉編譯及環(huán)境配置等);
四、嵌入式軟件開發(fā):精通Linux操作系統(tǒng)的概念和安裝方法、Linux下的基本命令、管理配置和編輯器,包括VI編輯器,GCC編譯器,GDB調(diào)試器和 Make 項目管理工具等知識;精通C語言的高級編程知識,包括函數(shù)與程序結(jié)構(gòu)、指針、數(shù)組、常用算法、庫函數(shù)的使用等知識、數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)內(nèi)容,包括鏈表、隊列等;掌握面向?qū)ο缶幊痰幕舅枷耄约?a href="http://www.wenjunhu.com/tags/C++/" target="_blank">C++語言的基礎(chǔ)內(nèi)容;精通嵌入式Linux下的程序設(shè)計,精通嵌入式Linux開發(fā)環(huán)境,包括系統(tǒng)編程、文件I/O、多進(jìn)程和多線程、網(wǎng)絡(luò)編程、GUI圖形界面編程、數(shù)據(jù)庫;熟悉常用的圖形庫的編程,如QT、GTK、miniGUI、fltk、nano-x等。
公司的日?;顒舆€是看公司的規(guī)模,大一點的一般只是讓你負(fù)責(zé)一個模塊,這樣你就要精通一點。若是公司比較小的話估計要你什么都做一點。還要了解點硬件的東西。
那么看了這么多,嵌入式和純軟最大的區(qū)別在于:
純軟學(xué)習(xí)的是一門語言,例如C,C++,java,甚至Python,語言說到底只是一門工具,就像學(xué)會英語法語日語一樣。
但嵌入式學(xué)習(xí)的是軟件+硬件,通俗的講,它學(xué)的是做系統(tǒng)做產(chǎn)品,講究的是除了具體的語言工具,更多的是如何將一個產(chǎn)品分解為具體可實施的軟件和硬件,以及更小的單元。
不少人問,將來就業(yè)到底是選驅(qū)動還是選應(yīng)用?只能說憑興趣,并且驅(qū)動和應(yīng)用并不是截然分開的。
▍PART 01
我們說的驅(qū)動,其實并不局限于硬件的操作,還有操作系統(tǒng)的原理、進(jìn)程的休眠喚醒調(diào)度等概念。想寫出一個好的應(yīng)用,想比較好的解決應(yīng)用碰到的問題,這些知識大家應(yīng)該都懂。
▍PART 02
做應(yīng)用的發(fā)展路徑個人認(rèn)為就是業(yè)務(wù)純熟。比如在通信行業(yè)、IPTV行業(yè)、手機(jī)行業(yè),行業(yè)需求很了解。
▍PART 03
做驅(qū)動,其實不能稱為“做驅(qū)動”,而是可以稱為“做底層系統(tǒng)”,做好了這是通殺各行業(yè)。比如一個人工作幾年,做過手機(jī)、IPTV、會議電視,但是這些產(chǎn)品對他毫無差別,因為他只做底層。當(dāng)應(yīng)用出現(xiàn)問題,解決不了時,他就可以從內(nèi)核角度給他們出主意,提供工具。做底層的發(fā)展方向,應(yīng)該是技術(shù)專家。
▍PART 04
其實,做底層還是做應(yīng)用,之間并沒有一個界線,有底層經(jīng)驗,再去做應(yīng)用,會感覺很踏實。有了業(yè)務(wù)經(jīng)驗,再了解一下底層,很快就可以組成一個團(tuán)隊。
2 嵌入式Linux底層系統(tǒng)包含哪些東西?
嵌入式LINUX里含有bootloader, 內(nèi)核, 驅(qū)動程序、根文件系統(tǒng)這4大塊。
一、bootloader
它就是一個稍微復(fù)雜的裸板程序。但是要把這裸板程序看懂寫好一點都不容易。Windows下好用的工具弱化了我們的編程能力。很多人一玩嵌入式就用ADS、KEIL。能回答這幾個問題嗎?
Q:
一上電,CPU從哪里取指令執(zhí)行?
A:
一般從Flash上指令。
Q:
但是Flash一般是只能讀不能直接寫的,如果用到全局變量,這些全局變量在哪里?
A:
全局變量應(yīng)該在內(nèi)存里。
Q:
那么誰把全局變量放到內(nèi)存里去?
A:
長期用ADS、KEIL的朋友,你能回答嗎?這需要“重定位”。在ADS或KEIL里,重定位的代碼是制作這些工具的公司幫你寫好了。你可曾去閱讀過?
Q:
內(nèi)存那么大,我怎么知道把“原來存在Flash上的內(nèi)容”讀到內(nèi)存的“哪個地址去”?
A:
這個地址用“鏈接腳本”決定,在ADS里有scatter文件,KEIL里也有類似的文件。但是,你去研究過嗎?
Q:
你說重定位是把程序從Flash復(fù)制到內(nèi)存,那么這個程序可以讀Flash?。?/p>
A:
是的,要能操作Flash。當(dāng)然不僅僅是這些,還有設(shè)置時鐘讓系統(tǒng)運行得更快等等。
先自問自答到這里吧,對于bootloader這一個裸板程序,其實有3部分要點:
①對硬件的操作
對硬件的操作,需要看原理圖、芯片手冊。這需要一定的硬件知識,不要求能設(shè)計硬件,但是至少能看懂; 不求能看懂模擬電路,但是要能看懂?dāng)?shù)字電路。這方面的能力在學(xué)校里都可以學(xué)到,微機(jī)原理、數(shù)字電路這2本書就足夠了。想速成的話,就先放掉這塊吧,不懂就GOOGLE、發(fā)貼。另外,芯片手冊是肯定要讀的,別去找中文的,就看英文的。開始是非常痛苦,以后就會發(fā)現(xiàn)那些語法、詞匯一旦熟悉后,讀任何芯片手冊都很容易。
②對ARM體系處理器的了解
對ARM體系處理器的了解,可以看杜春蕾的《ARM體系架構(gòu)與編程》,里面講有匯編指令,有異常模式、MMU等。也就這3塊內(nèi)容需要了解。
③程序的基本概念:重定位、棧、代碼段數(shù)據(jù)段BSS段等
程序的基本概念,王道當(dāng)然是去看編譯原理了。可惜,這類書絕對是天書級別的。若非超級天才還是別去看了??梢钥错f東山的《嵌入式Linux應(yīng)用開發(fā)完全手冊》。
對于bootloader,可以先看《ARM體系架構(gòu)與編程》,然后自己寫程序把各個硬件的實驗都做一遍,比如GPIO、時鐘、SDRAM、UART、NAND。把它們都弄清楚了,組臺在一起就很容易看懂u-boot了 。
總結(jié)一下,看懂硬件原理圖、看芯片手冊,這都需要自己去找資料。
二、內(nèi)核
想速成的人,先跨過內(nèi)核的學(xué)習(xí),直接學(xué)習(xí)怎么寫驅(qū)動。
想成為高手,內(nèi)核必須深刻了解。注意,是了解,要對里面的調(diào)度機(jī)制、內(nèi)存管理機(jī)制、文件管理機(jī)制等等有所了解。
推薦兩本書:
1. 通讀《linux內(nèi)核完全注釋》,請看薄的那本
2. 選讀《Linux內(nèi)核情景分析》, 想了解哪一塊就讀哪一節(jié)
三、驅(qū)動
驅(qū)動包含兩部分:硬件本身的操作、驅(qū)動程序的框架。
又是硬件,還是要看得懂原理圖、讀得懂芯片手冊,多練吧。
①硬件本身的操作
說到驅(qū)動框架,有一些書介紹一下。LDD3,即《Linux設(shè)備驅(qū)動》,老外寫的那本,里面介紹了不少概念,值得一讀。但是,它的作用 也就限于介紹概念了。入門之前可以用它來熟悉一下概念。
②驅(qū)動程序的框架
驅(qū)動方面比較全的介紹,應(yīng)該是宋寶華的《linux設(shè)備驅(qū)動開發(fā)詳解》了。要想深入了解某一塊,《Linux內(nèi)核情景分析》絕對是超5星級推薦。別指望把它讀完,1800多頁,上下兩冊呢。某一塊不清楚時,就去翻一下它。任何一部分,這書都可以講上2、3百頁,非常詳細(xì)。并且是以某個目標(biāo)來帶你分析內(nèi)核源碼。它以linux2.4為例,但是原理相通,同樣適用于其它版本的linux。
把手上的開發(fā)板所涉及的硬件,都去嘗試寫一個驅(qū)動吧。有問題就先“痛苦地思考”,思考的過程中會把很多不相關(guān)的知識串聯(lián)起來,最終貫通。
四、根文件系統(tǒng)
大家有沒有想過這2個問題:
Q:
對于Linux做出來的產(chǎn)品,有些用作監(jiān)控、有些做手機(jī)、有些做平板。那么內(nèi)核啟動后,掛載根文件系統(tǒng)后,應(yīng)該啟動哪一個應(yīng)用程序呢?
A:
內(nèi)核不知道也不管應(yīng)該啟動哪一個用戶程序。它只啟動init這一個應(yīng)用程序,它對應(yīng)/sbin/init。
顯然,這個應(yīng)用程序就要讀取配置文件,根據(jù)配置文件去啟動用戶程序(監(jiān)控、手冊界面、平板界面等等,這個問題提示我們,文件系統(tǒng)的內(nèi)容是有一些約定的,比如要有/sbin/init,要有配置文件 。
Q:
你寫的hello,world程序,有沒有想過里面用到的printf是誰實現(xiàn)的?
A:
這個函數(shù)不是你實現(xiàn)的,是庫函數(shù)實現(xiàn)的。它運行時,得找到庫。
這個問題提示我們,文件系統(tǒng)里還要有庫。
簡單的自問自答到這里,要想深入了解,可以看一下busybox的init.c,就可以知道init進(jìn)程做的事情了。
當(dāng)然,也可以看《嵌入式Linux應(yīng)用開發(fā)完全手冊》里構(gòu)建根文件系統(tǒng)那章。
3 驅(qū)動程序設(shè)計的5個方法
1.使用設(shè)計模式
設(shè)計模式是一個用來處理那些在軟件中會重復(fù)出現(xiàn)的問題的解決方案。開發(fā)人員可以選擇浪費寶貴的時間和預(yù)算從無到有地重新發(fā)明一個解決方案,也可以從他的解決方案工具箱中選擇一個最適合解決這個問題的方案。在微處理器出現(xiàn)之初,底層驅(qū)動已經(jīng)很成熟了,那么,為什么不利用現(xiàn)有的成熟的解決方案呢?
驅(qū)動程序設(shè)計模式大致分屬以下4個類別:Bit bang、輪詢、中斷驅(qū)動和直接存儲器訪問(DMA)。
Bit bang模式:當(dāng)微控制器沒有內(nèi)外設(shè)去執(zhí)行功能的時候,或者當(dāng)所有的內(nèi)外設(shè)都已經(jīng)被使用了,而此時又有一個新的請求,那么開發(fā)者就應(yīng)該選擇Bit bang設(shè)計模式。Bit bang模式的解決方案很有效率,但通常需要大量的軟件開銷來確保其實施的能力。Bit bang模式可以讓開發(fā)者手動完成通信協(xié)議或外部行為。
輪詢模式用于簡單地監(jiān)視一個輪詢調(diào)度方式中的事件。輪詢模式適用于非常簡單的系統(tǒng),但許多現(xiàn)代應(yīng)用程序都需要中斷。
中斷可以讓開發(fā)者在事件發(fā)生時進(jìn)行處理,而不用等代碼手動檢查。
DMA(直接存儲器訪問)模式允許其它外圍設(shè)備來處理數(shù)據(jù)傳輸?shù)男枨螅恍枰?qū)動的干預(yù)。
2.了解實時行為
一個實時系統(tǒng)是否能滿足實時需求取決于它的驅(qū)動程序。寫入能力差的驅(qū)動是低效的,并可能使不知情的開發(fā)者放棄系統(tǒng)的性能。設(shè)計者需要考慮驅(qū)動的兩個特點:阻塞和非阻塞。一個阻塞的驅(qū)動程序在其完成工作之前會阻止其他任何軟件執(zhí)行操作。例如,一個USART驅(qū)動程序可以把一個字符裝入傳輸緩沖區(qū),然后一直等到接收到傳輸結(jié)束標(biāo)志符才繼續(xù)執(zhí)行下一步操作。
另一方面,非阻塞驅(qū)動則是一般利用中斷來實現(xiàn)它的功能。中斷的使用可以防止驅(qū)動程序在等待一個事件發(fā)生時攔截其他軟件的執(zhí)行操作。USART的驅(qū)動程序可以將一個字符裝入傳輸緩沖區(qū)然后等主程序發(fā)布下一個指令。傳輸結(jié)束標(biāo)志符的設(shè)置會導(dǎo)致中斷結(jié)束,讓驅(qū)動進(jìn)行下一步操作。
無論哪種類型,為了保持實時性能,并防止系統(tǒng)中的故障,開發(fā)人員必須了解驅(qū)動的平均執(zhí)行時間和最壞情況下的執(zhí)行時間。一個完整的系統(tǒng)可能會因為一個潛在的風(fēng)險而造成更大的安全問題。
3. 重用設(shè)計
在時間和預(yù)算都很緊張的情況下為什么還要再造輪子呢?在驅(qū)動程序開發(fā)中,重用、便攜性和可維護(hù)性都是驅(qū)動設(shè)計的關(guān)鍵要求。這里面的許多特征可以通過硬件抽象層的設(shè)計和使用來說明。
硬件抽象層(HAL)為開發(fā)人員提供一種方式來創(chuàng)建一個標(biāo)準(zhǔn)接口去控制微控制器的外設(shè)。抽象隱藏實現(xiàn)細(xì)節(jié),取而代之的是提供了可視化功能,如 Usart_Init和Usart_Transmit。這個方法就是讓任何USART、SPI、PWM或其他外設(shè)具備所有微控制器都支持的共同特點。使用HAL隱藏底層、特定設(shè)備的細(xì)節(jié),讓應(yīng)用程序開發(fā)人員專注于應(yīng)用的需求,而不是關(guān)注底層的硬件是如何工作的。同時HAL提供了一個重用的容器。
4.參考數(shù)據(jù)手冊
微控制器在過去的幾年里變得越來越復(fù)雜。以前想要完全了解一個微控制器需要掌握由一個大約包含500頁組成的單一數(shù)據(jù)手冊。而如今,一個32位微控制器通常包含由部分的數(shù)據(jù)手冊、整個微控制器系列的資料表、每個外設(shè)數(shù)以百計的資料以及所有的勘誤表組成的數(shù)據(jù)手冊。開發(fā)人員如果想要完全掌握這部分的內(nèi)容需要了解幾千頁的文件。
不幸的是,所有這些數(shù)據(jù)手冊都是一個驅(qū)動程序能真正合理實現(xiàn)所需要的。開發(fā)人員在一開始就要對每個數(shù)據(jù)手冊中包含的信息進(jìn)行收集和排序。通常它們中的每一個都需要被訪問以使外設(shè)啟動和運行。關(guān)鍵信息被分散(或隱藏)在每種類型的數(shù)據(jù)手冊中。
5.謹(jǐn)防外設(shè)故障
最近我剛好有機(jī)會把一系列的微控制器驅(qū)動移植到其他的微處理器上。制造商和數(shù)據(jù)手冊都表明PWM外設(shè)在這兩個系列的微控制器之間是相同的。然而,實際情況卻是在運行PWM驅(qū)動器的時候兩者之間有很大的不同。該驅(qū)動程序只能在原來的微控制器工作,而在新系列的微控制器上卻無效。
在反復(fù)翻看數(shù)據(jù)手冊之后,我在數(shù)據(jù)手冊中一個完全不相關(guān)的注腳里發(fā)現(xiàn)了PWM外設(shè)上電時會處于故障狀態(tài),需要將一個隱藏在寄存器中的標(biāo)志位清零。在驅(qū)動程序?qū)崿F(xiàn)的開始,確認(rèn)外設(shè)可能出現(xiàn)的故障并查看其他看似無關(guān)的寄存器錯誤。
4 大牛對于嵌入式驅(qū)動開發(fā)的建議
1) 為了今后的發(fā)展,你除了考慮廣度以外,更重要的是注意知識的深度。
譬如,做過網(wǎng)絡(luò)驅(qū)動,那么是不是只停留在會寫驅(qū)動的表層上,有沒有對Linux內(nèi)核的網(wǎng)絡(luò)結(jié)構(gòu),TCP/IP協(xié)議作過深入的了解。
2) 在Linux下開發(fā)很多時候都要利用現(xiàn)成的東西,沒必要什么都自己搞。關(guān)鍵是變成自己的驅(qū)動后是否了解原作者編寫時背后的一些東西。你應(yīng)該不止是簡單的讓它工作。寫驅(qū)動的時候就要考慮它的性能問題,并給出測試的方法(當(dāng)然可以利用現(xiàn)成的許多工具,譬如測試網(wǎng)絡(luò)性能的netperf等)。
當(dāng)你寫過Flash驅(qū)動,可能會知道Flash的性能有時候有多重要。
3) C程序的自我修煉,是否考慮到軟件工程方面的一些東西,程序的可維護(hù)性和擴(kuò)展性,譬如LCD驅(qū)動,是不是從Sharp到NEC的只需要集中修改很少的幾個地方?
對于不同品牌的Flash,如果使得Flash的驅(qū)動做的更具有靈活性。
4) 如果有時間結(jié)余,可以關(guān)注Linux內(nèi)核的發(fā)展。譬如LCD的驅(qū)動有沒有考慮到V4L2通用架構(gòu),譬如網(wǎng)絡(luò)驅(qū)動用到了NAPI了嗎?當(dāng)然在此之前,假設(shè)已經(jīng)對LDD3, ULK2理解的比較熟了。
5) 現(xiàn)在所作的這些驅(qū)動還算不得非常核心的東西。如果你想有更好的發(fā)展,可以考慮往audio,video,net方面發(fā)展,你應(yīng)該多注意真?zhèn)€行業(yè)需要什么樣的人才,上述每一項都需要很厚的底蘊(yùn),譬如video,需要了解MPEG4, H264等,怎么也要個1到2年才能算個入行阿,所以我建議不要只顧悶頭做東西,要適當(dāng)關(guān)注目前的一些應(yīng)用。
6) 對硬件知識的補(bǔ)給,做嵌入式Linux這一行不可能不讀硬件的Spec,如果你對硬件的工作機(jī)制理解的比較透,會有助你寫出性能好的驅(qū)動程序。
順便提一點,適時的提高你的英語水平,對你的職業(yè)生涯絕對有幫助。(不要等需要的時候再補(bǔ),來不及)
7) 如果有時間,平時注意對Linux應(yīng)用程序編寫的了解/積累,也將有助于你寫出很好功能很好的驅(qū)動程序。
8) 永遠(yuǎn)不能以為自己做了很多東西,就驅(qū)動而言,像TVIN/TVOUT, USB, SDIO等等,好多未知領(lǐng)域呢。在問題還沒有解決之前很難說清是哪里不對了。
有時候是datasheet里面的一句話沒有注意,還有好幾次調(diào)不出來最后查到是PCB的問題,所以有時候特別暈。
5 嵌入式驅(qū)動自學(xué)者的感受
經(jīng)過了多年的嵌入式自學(xué),可謂是不斷在絕望中求生。性格使然,我是一個我也不知這種性格的學(xué)名叫什么,就是學(xué)習(xí)一種東西,非得想要能理解每一處的含義作用為什么,要這樣做沒有其他辦法了嗎等等問題。并且當(dāng)一個問題找不到讓我能接受的解釋時,那么我的學(xué)習(xí)路程也就幾乎要停在這里了,大概是因為我討厭一知半解。
可能是小時候被老師教導(dǎo)不要做書呆子的教育有關(guān),小時候,聽話孩子,認(rèn)真,長輩的教育對孩子的影響真的是非常的大,很多影響如果你不細(xì)心的觀察自己,你根本不能察覺這些進(jìn)入了你骨子的觀念,在我成長過程中,這些長輩的教育除了某些讓我自己經(jīng)歷到并徹底認(rèn)識到某個觀念并不正確時,我才會形成自己的觀點,自己的觀念,但這些自己的觀念在所有的價值觀中,猶如滄海一粟。
這種討厭一知半解的性格,在現(xiàn)在這個社會來說,可以說是極端的,因為現(xiàn)在你學(xué)習(xí)使用的很多東西,他都不是從零開始的,就好比,你編程使用的是高級語言而不是低級語言不是機(jī)器碼,所以我的整個學(xué)習(xí)過程是非常緩慢緩慢地進(jìn)行著,這么說吧,前面說我經(jīng)過了半年多的學(xué)習(xí),但是到現(xiàn)在為止,我接觸嵌入式已經(jīng)有兩個年頭了,也就是說,學(xué)習(xí)期間,我有一年多是在停滯著。
學(xué)習(xí)嵌入式,或者說學(xué)習(xí)現(xiàn)代的計算機(jī)編程,如果你想學(xué)好,有一個比較要求,那就是你能接受它的設(shè)定、它的模式。反過來說,當(dāng)你真正接受它的設(shè)定、它的模式,并記住它們時,我認(rèn)為,你已經(jīng)學(xué)好了。
昨天,我又置之死地而后生了一次。最近一直在搞驅(qū)動,一個LCD驅(qū)動搞得我?guī)缀跻艞壚^續(xù)走嵌入式這條路。昨夜,睡不覺,打開嵌入學(xué)習(xí)視頻,躺在已關(guān)燈很久的房間的床上,大概凌晨3,4點吧。之前我一直都是學(xué)習(xí)著驅(qū)動自編源碼的教學(xué),是那種幾乎和裸板程序沒多大區(qū)別的編程方式,只是多使用了一些向內(nèi)核注冊的接口函數(shù)。
而最近我想換一下,因為很多設(shè)備驅(qū)動,內(nèi)核都是自帶的,而且是各種平臺的設(shè)備驅(qū)動都有,我想如果能熟悉掌握內(nèi)核自帶驅(qū)動的編程,那以后要做某個設(shè)備的驅(qū)動時,我只需要在自帶驅(qū)動中修改一下便好了,通過學(xué)習(xí)LCD平臺設(shè)備的驅(qū)動,我了解了其編程想法,同時也認(rèn)同這種想法,甚至讓我疑惑,學(xué)習(xí)資料中教自編驅(qū)動的意義,為何不直接教如果修改內(nèi)核源碼驅(qū)動?
于是,繼續(xù)按著書去修改內(nèi)核驅(qū)動源碼,但問題是,書中說他們這種修改,代碼成功運行了,但我這,無論怎么調(diào)試都失敗,我反復(fù)檢測,我的修改是否與書中一致,檢測了很多遍依然沒發(fā)現(xiàn)哪一步不同,不過,有一點發(fā)現(xiàn)是,書中的內(nèi)核源碼和我內(nèi)核使用的源碼有一點點區(qū)別(當(dāng)然書里并沒有把所有的源碼都貼上,只是修改部分附近會聯(lián)帶著一些,這就是發(fā)現(xiàn),這些聯(lián)帶的沒需要修改的源碼和我的源碼有點區(qū)別,比如,我的源碼中多了一些設(shè)置(看似無關(guān)緊要的設(shè)置))。
與書核對無誤但失敗后,我又與成功運行的自編驅(qū)動核對,我陸續(xù)發(fā)現(xiàn)我修改的內(nèi)涵源碼中,沒有去啟動設(shè)備,也更沒有去點亮背光,而在顯存分配后的寄存器設(shè)置似乎也有問題,因為這里的地址使用各種宏定義不同的累加或計算,最后算得地址和我的寄存器地址也不知是否吻合,因為驅(qū)動源碼中最后計算得到的是虛擬地址。于是我對比自編驅(qū)動,一點點修改嘗試,到睡覺前都沒成功。
我是想學(xué)得理直氣壯一點的,最后是能一眼就能找到問題,并迅速輕松解決問題的,我也承認(rèn)自己確實是有些浮躁。但是經(jīng)過了昨晚床上的一點絕望的思考掙扎后,我好像想通了:為什么嵌入式學(xué)習(xí)視頻老師要教自編驅(qū)動。
下面我說下自編驅(qū)動與內(nèi)核驅(qū)動源碼各自的問題:
自便驅(qū)動:
程序簡單簡潔,它只能驅(qū)動特定的某個設(shè)備。如果設(shè)備換了需要支持另一款設(shè)備,那么你需要重新修改該驅(qū)動;如果需要系統(tǒng)同時支持兩種LCD,那么它就會變成復(fù)雜并且對于內(nèi)核驅(qū)動的簡潔優(yōu)勢會削弱不少;如果你想驅(qū)動支持多種設(shè)備,那自編驅(qū)動,相對了內(nèi)核驅(qū)動源碼的簡潔優(yōu)勢會變成了劣勢,因為編程思想的適用范圍不同而產(chǎn)生的結(jié)果。
內(nèi)核自帶驅(qū)動源碼:
①從系統(tǒng)層次去考量,變量、宏定義使用多,甚至有些宏定義的值為了方便能讓各種在不同的階段需要不同的值調(diào)用,把簡單的一個賦值調(diào)用變成了需要進(jìn)行多次運算才能檢測到該值是否滿足使用要求,因為我們不是該驅(qū)動的編碼者,不清楚這樣做的好處,也或許是內(nèi)核驅(qū)動源碼的開發(fā)者從整個系統(tǒng)的編程簡潔性去考量,這樣做或許也是為了讓整個系統(tǒng)代碼更少,簡潔的一種做法,因為每個設(shè)備你都給它賦具體的值的話,整個系統(tǒng)中有幾百種驅(qū)動設(shè)備源碼,給所有設(shè)備的這個位置參數(shù)都賦一個值的話,那各設(shè)備關(guān)于這個值的代碼就要多了幾百行了,所以還不如,讓各設(shè)備根據(jù)各種平臺去對某個宏進(jìn)行各自的計算來得到合適的值,但某些計算中相同的算法的也整合在一起,這樣就減少了系統(tǒng)不少行代碼。所以系統(tǒng)中驅(qū)動源碼是系統(tǒng)開發(fā)者對系統(tǒng)源碼的整合,是基于系統(tǒng)層的整合。所以,對于我這種對單個設(shè)備驅(qū)動編碼的人,就會覺得系統(tǒng)源碼有好多不人性化的地方,會覺得簡單的地方也被弄得很復(fù)雜。
②內(nèi)核自帶驅(qū)動還有一些代碼是為了兼容以前的版本而添加了,比如以前硬件內(nèi)存資源稀少,需要使用調(diào)色板的方法來減少程序運行時的內(nèi)存使用量,這也會真假代碼的復(fù)雜性,這一步雖不是必要的,但如果沒弄好,那LCD驅(qū)動也不能正常使用。
③程序復(fù)雜,為了適用在多種設(shè)備型號,更簡單地添加不同型號的設(shè)備驅(qū)動,內(nèi)核對驅(qū)動抽象分離,把驅(qū)動分為平臺管理部分,驅(qū)動代碼部分(與硬件無關(guān)碼),和設(shè)備代碼部分(硬件相關(guān)代碼)。用戶添加新型號設(shè)備驅(qū)動時,只需要在平臺管理部分檢查添加設(shè)備的匹配信息,和提供一個硬件設(shè)備相關(guān)的代碼(有格式)文件即可。
現(xiàn)在,站在驅(qū)動開發(fā)者而非系統(tǒng)開發(fā)者的角度去衡量。
①自編的驅(qū)動,簡潔,要點明確。這個對于驅(qū)動開發(fā)者的用處就是:無論你使用的是哪個版本的內(nèi)核,哪個芯片平臺,你可以通過自編碼比較簡單方便地就可以確認(rèn)硬件設(shè)備的情況,是否正常。如果自編碼通過,那可以試用自編碼上使用的參數(shù)去與內(nèi)核進(jìn)行核對、修改,然后再去測試。如果不成功,對于內(nèi)核中多余的設(shè)置(這些大多可能是提供內(nèi)核用做基本判斷的變量)可以先屏蔽,編譯出錯了,根據(jù)提示,找到出錯的位置修改添加。因為這些多余的設(shè)置,設(shè)置對了還行,設(shè)置錯了,你又不好去定位錯在哪。
自編的驅(qū)動在此處的用處,調(diào)試時,可以讓你排除多余的失敗可能性問題,在較少的代碼去查出錯誤位置,如果你確定你的設(shè)置滿足了該設(shè)備的必需設(shè)置,還是失敗,你可以比較放心地去懷疑是硬件問題了。如果自編碼成功,那個又可以當(dāng)做你修改內(nèi)核驅(qū)動的一個標(biāo)準(zhǔn)。
②內(nèi)核驅(qū)動源碼支持管理多種型號的設(shè)備的優(yōu)勢是我用使用它的原因。先了解本版本本平臺的設(shè)備驅(qū)動結(jié)構(gòu),如果是添加型號支持,那就根據(jù)自編驅(qū)動的參數(shù)與設(shè)置即可,如果是第一次啟動這類設(shè)備,你就還需要檢測結(jié)構(gòu)是完整性,如果結(jié)構(gòu)完整,參數(shù)無誤依舊錯誤,那就把內(nèi)核驅(qū)動源碼精簡到自編碼的簡單粗暴設(shè)置吧。最終就變成了在基于內(nèi)核驅(qū)動架構(gòu)下的自編驅(qū)動。如果還不行,那無疑是結(jié)構(gòu)性問題了。
所以自編驅(qū)動,還是有其存在價值的。內(nèi)核驅(qū)動源碼內(nèi)容會變,平臺會變,但自編驅(qū)動是變得最小的一個,也是最容易實現(xiàn)驅(qū)動目的的一個。是一碼打天下不可缺少的重要組成部分。
6 如何編寫嵌入式Linux設(shè)備驅(qū)動程序?
一、Linux device driver 的概念
系統(tǒng)調(diào)用是操作系統(tǒng)內(nèi)核和應(yīng)用程序之間的接口,設(shè)備驅(qū)動程序是操作系統(tǒng)內(nèi)核和機(jī)器硬件之間的接口。設(shè)備驅(qū)動程序為應(yīng)用程序屏蔽了硬件的細(xì)節(jié),這樣在應(yīng)用程序看來,硬件設(shè)備只是一個設(shè)備文件,應(yīng)用程序可以象操作普通文件一樣對硬件設(shè)備進(jìn)行操作。設(shè)備驅(qū)動程序是內(nèi)核的一部分,它完成以下的功能:
1、對設(shè)備初始化和釋放;
2、把數(shù)據(jù)從內(nèi)核傳送到硬件和從硬件讀取數(shù)據(jù);
3、讀取應(yīng)用程序傳送給設(shè)備文件的數(shù)據(jù)和回送應(yīng)用程序請求的數(shù)據(jù);
4、檢測和處理設(shè)備出現(xiàn)的錯誤。
在linux操作系統(tǒng)下有三類主要的設(shè)備文件類型,一是字符設(shè)備,二是塊設(shè)備,三是網(wǎng)絡(luò)設(shè)備。字符設(shè)備和塊設(shè)備的主要區(qū)別是:在對字符設(shè)備發(fā)出讀/寫請求時,實際的硬件I/O一般就緊接著發(fā)生了,塊設(shè)備則不然,它利用一塊系統(tǒng)內(nèi)存作緩沖區(qū),當(dāng)用戶進(jìn)程對設(shè)備請求能滿足用戶的要求,就返回請求的數(shù)據(jù),如果不能,就調(diào)用請求函數(shù)來進(jìn)行實際的I/O操作。塊設(shè)備是主要針對磁盤等慢速設(shè)備設(shè)計的,以免耗費過多的CPU時間來等待。
已經(jīng)提到,用戶進(jìn)程是通過設(shè)備文件來與實際的硬件打交道。每個設(shè)備文件都都有其文件屬性(c/b),表示是字符設(shè)備還是塊設(shè)備?另外每個文件都有兩個設(shè)備號,第一個是主設(shè)備號,標(biāo)識驅(qū)動程序,第二個是從設(shè)備號,標(biāo)識使用同一個設(shè)備驅(qū)動程序的不同的硬件設(shè)備,比如有兩個軟盤,就可以用從設(shè)備號來區(qū)分他們。設(shè)備文件的的主設(shè)備號必須與設(shè)備驅(qū)動程序在登記時申請的主設(shè)備號一致,否則用戶進(jìn)程將無法訪問到驅(qū)動程序。
最后必須提到的是,在用戶進(jìn)程調(diào)用驅(qū)動程序時,系統(tǒng)進(jìn)入核心態(tài),這時不再是搶先式調(diào)度。也就是說,系統(tǒng)必須在你的驅(qū)動程序的子函數(shù)返回后才能進(jìn)行其他的工作。如果你的驅(qū)動程序陷入死循環(huán),不幸的是你只有重新啟動機(jī)器了,然后就是漫長的fsck。
二、實例剖析
我們來寫一個最簡單的字符設(shè)備驅(qū)動程序。雖然它什么也不做,但是通過它可以了解Linux的設(shè)備驅(qū)動程序的工作原理。把下面的C代碼輸入機(jī)器,你就會獲得一個真正的設(shè)備驅(qū)動程序。
由于用戶進(jìn)程是通過設(shè)備文件同硬件打交道,對設(shè)備文件的操作方式不外乎就是一些系統(tǒng)調(diào)用,如 open,read,write,close…, 注意,不是fopen, fread,但是如何把系統(tǒng)調(diào)用和驅(qū)動程序關(guān)聯(lián)起來呢?這需要了解一個非常關(guān)鍵的數(shù)據(jù)結(jié)構(gòu):
struct file_operations {
int (*seek) (struct inode * ,
struct file *, off_t ,int); int (*read)
(struct inode * ,struct file *, char ,int); int (*write)
(struct inode * ,struct file *, off_t ,int); int (*readdir)
(struct inode * ,struct file *, struct dirent * ,int); int (*select)
(struct inode * ,struct file *, int ,select_table *); int (*ioctl)
(struct inode * ,struct file *, unsined int ,unsigned long); int (*mmap)
(struct inode * ,struct file *, struct vm_area_struct *); int (*open)
(struct inode * ,struct file *); int (*release) (struct inode * ,struct file *); int (*fsync)
(struct inode * ,struct file *); int (*fasync) (struct inode * ,struct file *,int); int (*check_media_change)
(struct inode * ,struct file *); int (*revalidate) (dev_t dev); }
這個結(jié)構(gòu)的每一個成員的名字都對應(yīng)著一個系統(tǒng)調(diào)用。用戶進(jìn)程利用系統(tǒng)調(diào)用在對設(shè)備文件進(jìn)行諸如read/write操作時,系統(tǒng)調(diào)用通過設(shè)備文件的主設(shè)備號找到相應(yīng)的設(shè)備驅(qū)動程序,然后讀取這個數(shù)據(jù)結(jié)構(gòu)相應(yīng)的函數(shù)指針,接著把控制權(quán)交給該函數(shù)。這是linux的設(shè)備驅(qū)動程序工作的基本原理。既然是這樣,則編寫設(shè)備驅(qū)動程序的主要工作就是編寫子函數(shù),并填充file_operations的各個域。
下面就開始寫子程序。
#include 《linux/types.h》
基本的類型定義#include 《linux/fs.h》
文件系統(tǒng)使用相關(guān)的頭文件
#include 《linux/mm.h》 #include 《linux/errno.h》 #include 《asm/segment.h》
unsigned int test_major = 0; static int read_test(struct inode *inode,struct file *file,char *buf,int count){ int left;
用戶空間和內(nèi)核空間if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT
)
return -EFAULT; for(left = count ; left 》 0 ;
left--)
{ __put_user(1,buf,1);
buf++; }
return count;
}
這個函數(shù)是為read調(diào)用準(zhǔn)備的。當(dāng)調(diào)用read時,read_test()被調(diào)用,它把用戶的緩沖區(qū)全部寫1。buf 是read調(diào)用的一個參數(shù)。它是用戶進(jìn)程空間的一個地址。但是在read_test被調(diào)用時,系統(tǒng)進(jìn)入核心態(tài)。所以不能使用buf這個地址,必須用__put_user(),這是kernel提供的一個函數(shù),用于向用戶傳送數(shù)據(jù)。另外還有很多類似功能的函數(shù)。請參考,在向用戶空間拷貝數(shù)據(jù)之前,必須驗證buf是否可用。這就用到函數(shù)verify_area。為了驗證BUF是否可以用。
static
int write_test(struct inode *inode,struct file *file,const char *buf,int count)
{ return count;
}
static int open_test(struct inode *inode,struct file *file )
{MOD_INC_USE_COUNT;
模塊計數(shù)加以,表示當(dāng)前內(nèi)核有個設(shè)備加載內(nèi)核當(dāng)中去return 0;
}
static void release_test(struct inode *inode,struct file *file
)
{ MOD_DEC_USE_COUNT;
}
這幾個函數(shù)都是空操作。實際調(diào)用發(fā)生時什么也不做,他們僅僅為下面的結(jié)構(gòu)提供函數(shù)指針。
struct file_operations
test_fops =
{? read_test,
write_test,
open_test,
release_test,
};
設(shè)備驅(qū)動程序的主體可以說是寫好了?,F(xiàn)在要把驅(qū)動程序嵌入內(nèi)核。驅(qū)動程序可以按照兩種方式編譯。一種是編譯進(jìn)kernel,另一種是編譯成模塊(modules),如果編譯進(jìn)內(nèi)核的話,會增加內(nèi)核的大小,還要改動內(nèi)核的源文件,而且不能動態(tài)的卸載,不利于調(diào)試,所以推薦使用模塊方式。
int
init_module(void){ int result;
result = register_chrdev(0, “test”, &test_fops);
對設(shè)備操作的整個接口if (result 《 0)
{ printk(KERN_INFO “test: can‘t get major number
”); return result;
}
if
(test_major == 0)
test_major = result;
/* dynamic */return 0;
}
在用insmod命令將編譯好的模塊調(diào)入內(nèi)存時,init_module 函數(shù)被調(diào)用。在這里,init_module只做了一件事,就是向系統(tǒng)的字符設(shè)備表登記了一個字符設(shè)備。register_chrdev需要三個參數(shù),參數(shù)一是希望獲得的設(shè)備號,如果是零的話,系統(tǒng)將選擇一個沒有被占用的設(shè)備號返回。參數(shù)二是設(shè)備文件名,參數(shù)三用來登記驅(qū)動程序?qū)嶋H執(zhí)行操作的函數(shù)的指針。
如果登記成功,返回設(shè)備的主設(shè)備號,不成功,返回一個負(fù)值。
void cleanup_module(void)
{
unregister_chrdev(test_major,“test”); }
在用rmmod卸載模塊時,cleanup_module函數(shù)被調(diào)用,它釋放字符設(shè)備test在系統(tǒng)字符設(shè)備表中占有的表項。
一個極其簡單的字符設(shè)備可以說寫好了,文件名就叫test.c吧。
下面編譯 :
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c –c表示輸出制定名,自動生成.o文件
得到文件test.o就是一個設(shè)備驅(qū)動程序。
如果設(shè)備驅(qū)動程序有多個文件,把每個文件按上面的命令行編譯,然后
ld ?-r ?file1.o ?file2.o ?-o ?modulename。
驅(qū)動程序已經(jīng)編譯好了,現(xiàn)在把它安裝到系統(tǒng)中去。
$ insmod ?–f ?test.o
如果安裝成功,在/proc/devices文件中就可以看到設(shè)備test,并可以看到它的主設(shè)備號。要卸載的話,運行 :
$ rmmod test
下一步要創(chuàng)建設(shè)備文件。
mknod /dev/test c major minor
c 是指字符設(shè)備,major是主設(shè)備號,就是在/proc/devices里看到的。
用shell命令
$ cat /proc/devices
就可以獲得主設(shè)備號,可以把上面的命令行加入你的shell script中去。
minor是從設(shè)備號,設(shè)置成0就可以了。
我們現(xiàn)在可以通過設(shè)備文件來訪問我們的驅(qū)動程序。寫一個小小的測試程序。
#include 《stdio.h》 #include 《sys/types.h》
#include 《sys/stat.h》 #include 《fcntl.h》 main()
{
int testdev; int i; char buf[10];
testdev = open(“/dev/test”,O_RDWR);
if
(
testdev == -1 )
{
printf(“Cann’t open file
”); exit(0); }read(testdev,buf,10);
for (i = 0; i 《 10;i++) printf(“%d
”,buf[i]);
close(testdev);
}
編譯運行,看看是不是打印出全1 ?
以上只是一個簡單的演示。真正實用的驅(qū)動程序要復(fù)雜的多,要處理如中斷,DMA,I/O port等問題:詳解STM32中的DMA原理。這些才是真正的難點。上述給出了一個簡單的字符設(shè)備驅(qū)動編寫的框架和原理,更為復(fù)雜的編寫需要去認(rèn)真研究LINUX內(nèi)核的運行機(jī)制和具體的設(shè)備運行的機(jī)制等等。希望大家好好掌握LINUX設(shè)備驅(qū)動程序編寫的方法。
7 嵌入式驅(qū)動的結(jié)構(gòu)分析
在Linux系統(tǒng)上編寫驅(qū)動程序,說簡單也簡單,說難也難。難在于對算法的編寫和設(shè)備的控制方面,是比較讓人頭疼的;說它簡單是因為在Linux下已經(jīng)有一套驅(qū)動開發(fā)的模式,編寫的時候只需要按照這個模式寫就可以了,而這個模式就是它事先定義好的一些結(jié)構(gòu)體,在驅(qū)動編寫的時候,只要對這些結(jié)構(gòu)體根據(jù)設(shè)備的需求進(jìn)行適當(dāng)?shù)奶畛?,就實現(xiàn)了驅(qū)動的編寫。
首先在Linux下,視一切事物皆為文件,它同樣把驅(qū)動設(shè)備也看成是文件,對于簡單的文件操作,無非就是open/close/read/write,在Linux對于文件的操作有一個關(guān)鍵的數(shù)據(jù)結(jié)構(gòu):file_operation,它的定義在源碼目錄下的include/linux/fs.h中,內(nèi)容如下:
[cpp] view plain copy
1. struct file_operations {
2. struct module *owner;
3. loff_t (*llseek) (struct file *, loff_t, int);
4. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
5. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
6. ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
7. ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
8. int (*readdir) (struct file *, void *, filldir_t);
9. unsigned int (*poll) (struct file *, struct poll_table_struct *);
10. int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
11. long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
12. long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
13. int (*mmap) (struct file *, struct vm_area_struct *);
14. int (*open) (struct inode *, struct file *);
15. int (*flush) (struct file *, fl_owner_t id);
16. int (*release) (struct inode *, struct file *);
17. int (*fsync) (struct file *, int datasync);
18. int (*aio_fsync) (struct kiocb *, int datasync);
19. int (*fasync) (int, struct file *, int);
20. int (*lock) (struct file *, int, struct file_lock *);
21. ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
22. unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
23. int (*check_flags)(int);
24. int (*flock) (struct file *, int, struct file_lock *);
25. ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
26. ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
27. int (*setlease)(struct file *, long, struct file_lock **);
28. };
對于這個結(jié)構(gòu)體中的元素來說,大家可以看到每個函數(shù)名前都有一個“*”,所以它們都是指向函數(shù)的指針。目前我們只需要關(guān)心
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
這幾條,因為這篇文章就叫簡單驅(qū)動。就是讀(read)、寫(write)、控制(ioctl)、打開(open)、卸載(release)。這個結(jié)構(gòu)體在驅(qū)動中的作用就是把系統(tǒng)調(diào)用和驅(qū)動程序關(guān)聯(lián)起來,它本身就是一系列指針的集合,每一個都對應(yīng)一個系統(tǒng)調(diào)用。
但是畢竟file_operation是針對文件定義的一個結(jié)構(gòu)體,所以在寫驅(qū)動時,其中有一些元素是用不到的,所以在2.6版本引入了一個針對驅(qū)動的結(jié)構(gòu)體框架:platform,它是通過結(jié)構(gòu)體platform_device來描述設(shè)備,用platform_driver描述設(shè)備驅(qū)動,它們都在源代碼目錄下的include/linux/platform_device.h中定義,內(nèi)容如下:
[cpp] view plain copy
1. struct platform_device {
2. const char * name;
3. int id;
4. struct device dev;
5. u32 num_resources;
6. struct resource * resource;
7. const struct platform_device_id *id_entry;
8. /* arch specific additions */
9. struct pdev_archdata archdata;
10. };
11. struct platform_driver {
12. int (*probe)(struct platform_device *);
13. int (*remove)(struct platform_device *);
14. void (*shutdown)(struct platform_device *);
15. int (*suspend)(struct platform_device *, pm_message_t state);
16. int (*resume)(struct platform_device *);
17. struct device_driver driver;
18. const struct platform_device_id *id_table;
19. };
對于第一個結(jié)構(gòu)體來說,它的作用就是給一個設(shè)備進(jìn)行登記作用,相當(dāng)于設(shè)備的身份證,要有姓名,身份證號,還有你的住址,當(dāng)然其他一些東西就直接從舊身份證上copy過來,這就是其中的struct device dev,這是傳統(tǒng)設(shè)備的一個封裝,基本就是copy的意思了。對于第二個結(jié)構(gòu)體,因為Linux源代碼都是C語言編寫的,對于這里它是利用結(jié)構(gòu)體和函數(shù)指針,來實現(xiàn)了C語言中沒有的“類”這一種結(jié)構(gòu),使得驅(qū)動模型成為一個面向?qū)ο蟮慕Y(jié)構(gòu)。
對于其中的struct device_driver driver,它是描述設(shè)備驅(qū)動的基本數(shù)據(jù)結(jié)構(gòu),它是在源代碼目錄下的include/linux/device.h中定義的,內(nèi)容如下:
[cpp] view plain copy
1. struct device_driver {
2. const char *name;
3. struct bus_type *bus;
4. struct module *owner;
5. const char *mod_name; /* used for built-in modules */
6. bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
7. #if defined(CONFIG_OF)
8. const struct of_device_id *of_match_table;
9. #endif
10. int (*probe) (struct device *dev);
11. int (*remove) (struct device *dev);
12. void (*shutdown) (struct device *dev);
13. int (*suspend) (struct device *dev, pm_message_t state);
14. int (*resume) (struct device *dev);
15. const struct attribute_group **groups;
16. const struct dev_pm_ops *pm;
17. struct driver_private *p;
18. };
依然全部都是以指針的形式定義的所有元素,對于驅(qū)動這一塊來說,每一項肯定都是需要一個函數(shù)來實現(xiàn)的,如果不把它們集合起來,是很難管理的,而且很容易找不到,而且對于不同的驅(qū)動設(shè)備,它的每一個功能的函數(shù)名必定是不一樣的,那么我們在開發(fā)的時候,需要用到這些函數(shù)的時候,就會很不方便,不可能在使用的時候去查找對應(yīng)的源代碼吧,所以就要進(jìn)行一個封裝,對于函數(shù)的封裝,在C語言中一個對好的辦法就是在結(jié)構(gòu)體中使用指向函數(shù)的指針,這種方法其實我們在平時的程序開發(fā)中也可以使用,原則就是體現(xiàn)出一個“類”的感覺,就是面向?qū)ο蟮乃枷搿?/p>
在Linux系統(tǒng)中,設(shè)備可以大致分為3類:字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備,而每種設(shè)備中又分為不同的子系統(tǒng),由于具有自身的一些特殊性質(zhì),所以有不能歸到某個已經(jīng)存在的子類中,所以可以說是便于管理,也可以說是為了達(dá)到同一種定義模式,所以linux系統(tǒng)把這些子系統(tǒng)歸為一個新類:misc ,以結(jié)構(gòu)體miscdevice描述,在源代碼目錄下的include/linux/miscdevice.h中定義,內(nèi)容如下:
[cpp] view plain copy
1. struct miscdevice {
2. int minor;
3. const char *name;
4. const struct file_operations *fops;
5. struct list_head list;
6. struct device *parent;
7. struct device *this_device;
8. const char *nodename;
9. mode_t mode;
10. };
對于這些設(shè)備,它們都擁有一個共同主設(shè)備號10,所以它們是以次設(shè)備號來區(qū)分的,對于它里面的元素,大應(yīng)該很眼熟吧,而且還有一個我們更熟悉的list_head的元素,這里也可以應(yīng)證我之前說的list_head就是一個橋梁的說法了。
其實對于上面介紹的結(jié)構(gòu)體,里面的元素的作用基本可以見名思意了,所以不用贅述了。其實寫一個驅(qū)動模塊就是填充上述的結(jié)構(gòu)體,根據(jù)設(shè)備的功能和用途寫相應(yīng)的函數(shù),然后對應(yīng)到結(jié)構(gòu)體中的指針,然后再寫一個入口一個出口(就是模塊編程中的init和exit)就可以了,一般情況下入口程序就是在注冊platform_device和platform_driver(當(dāng)然,這樣說是針對以platform模式編寫驅(qū)動程序)。
8 嵌入式書籍推薦
1. 硬件方面的書: 微機(jī)原理、數(shù)字電路,高校里的教材。
2. Linux方面的書:
《ARM體系架構(gòu)與編程》
《嵌入式Linux應(yīng)用開發(fā)完全手冊》
《Linux設(shè)備驅(qū)動》,老外寫的那本
《linux設(shè)備驅(qū)動開發(fā)詳解》
《linux內(nèi)核完全注釋》
《Linux內(nèi)核情景分析》
在做驅(qū)動的時候,肯定會用到與內(nèi)核相關(guān)的東西,或者需要和內(nèi)核中的某些模塊配合,這樣你也要理解內(nèi)核的某些部分是如何實現(xiàn)的,最后,你應(yīng)該可以很好的掌握linux的內(nèi)核整體框架是什么。
這些都是進(jìn)步,都是在你一次又一次的開發(fā)中需要總結(jié)的東西,如果你不總結(jié),永遠(yuǎn)都是從頭開始(或者說永遠(yuǎn)都是還沒看懂別人代碼為什么這么做的時候,就去改它,然后可以工作了),就完事了,這樣你永遠(yuǎn)也不可能提高,最后你就有了現(xiàn)在的這種感覺,覺得自己什么都不是,什么都不懂。
還有一點要說明的,現(xiàn)在有許多人搞linux開發(fā),卻不去用linux系統(tǒng)做為自己工作的平臺,在這種情況下,你很難理解linux內(nèi)核的實現(xiàn)機(jī)制,以及為什么要采用這種方式實現(xiàn)。
你都沒用過linux系統(tǒng),就想去實現(xiàn)一個與linux運行機(jī)理相符合的項目,這是不可能的。就是你這個項目成功了,它也肯定不是最優(yōu)的,或者是不符合linux的使用習(xí)慣的(包括內(nèi)核的擴(kuò)展和應(yīng)用程序的實現(xiàn))。
所以,最后想說的是,你一定要定期總結(jié),總結(jié)你這段時間做了什么,你從中得到了什么,為了你以后可以更好的做好類似的工作,你應(yīng)該去看些其它的什么東西;二是你一定至少要在工作的開發(fā)環(huán)境中使用linux作為你的平時工作平臺,而不要使用虛擬機(jī)和服務(wù)期,因為你只有完全了解了linux的使用,你才可以為它開發(fā)符合它規(guī)則的項目。
審核編輯 :李倩
-
嵌入式
+關(guān)注
關(guān)注
5091文章
19176瀏覽量
307082 -
驅(qū)動開發(fā)
+關(guān)注
關(guān)注
0文章
130瀏覽量
12101
原文標(biāo)題:長文 | 嵌入式驅(qū)動方面,這篇文章講絕了!
文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論