1 應用程序、庫、內核、驅動程序的關系
從上到下,一個軟件系統可以分為:應用程序、庫、操作系統(內核)、驅動程序。開發人員可以專注于自己熟悉的部分,對于相鄰層,只需要了解它的接口,無需關注它的實現細節。
以點亮一個 LED 為例,這 4 層軟件的協作關系如下,如圖 1 所示
圖1
1). 應用程序使用庫提供的 open 函數打開代表 LED 的設備文件。
2). 庫根據 open 函數傳入的參數執行“SWI”指令,這條指令會引起 CPU 異常,進入內核。
3). 內核的異常處理函數根據這些參數找到相應的驅動程序,返回一個文件句柄給庫,進而返回給應用程序
4). 應用程序得到文件句柄后,使用庫提供的 write 或 ioclt 函數發出控制命令。
5). 庫根據 write 和 ioclt 函數傳人的參數執行 “swi” 指令, 這條指令會引起 CPU 異常,進入內核。
6). 內核的異常處理函數根據這些參數調用驅動程序的相關函數,點亮 LED。庫(比如 glibc)給應用程序提供的 open、read、write、ioctl、mmap 等接口函數被稱為系統調用,它們都是設置好相關寄存器后,執行某條指令引發異常進入內核。除系統調用接口外, 庫還提供其他函數, 比如字符串處理函數(strcpy、 strcmp 等)、 輸入/輸出函數(scanf、printf 等)、數學庫,還有應用程序的啟動代碼等。
在異常處理函數中,內核會根據傳入的參數執行各種操作,比如根據設備文件名找到對應的驅動程序,調用驅動程序的相關函數等。
一般來說,當應用程序調用 open、read、write、ioctl、mmap 等函數后,將會使用驅動程序中的 open、read、write、ioctl、mmap 函數來執行相關操作,比如初始化、讀、寫等。
實際上,內核和驅動程序之間并沒有界線,因為驅動程序最終是要編進內核去的:通過靜態鏈接和動態加載。
從上面操作 LED 的過程可以知道,與應用程序不同,驅動程序從不主動運行,它是被動的:根據應用程序的要求進行初始化,根據應用程序的要求進行讀寫。驅動程序加載進內核時,只是告訴內核“我在這里,我能做這些工作” ,至于這些“工作”何時開始,取決于應用程序。當然,這不是絕對的,比如用戶完全可以寫一個系統時鐘觸發的驅動程序,讓它自動點亮 LED。
在 Linux 系統中,應用程序運行于“用戶空間” ,擁有 MMU 的系統能夠限制應用程序的權限(比如將它限制于某個內存塊中),這可以避免應用程序的錯誤使整個系統崩潰。而驅動程序運行于“內核空間” ,它是系統“信任”的一部分,驅動程序的錯誤有可能導致整個系統崩潰。
2 Linux 驅動程序分類
Linux 的外設可以分為 3 類:自費設備、塊設備和網絡接口。
字符設備是能夠像字節流(比如文件)一樣被訪問的設備,就是說對它的讀寫是以字節為單位的。 比如串口在進行收發數據時就是一個字節一個字節的進行的,我們可以在驅動程序內部使用緩沖區來存放數據以提高效率,但是串口本身對這并沒有要求。字符設備的驅動程序中實現了 open、close、read、write 等系統調用,應用程序可以通過設備文件(比如/dev/ttySAC0 等)來訪問字符設備。
塊設備上的數據以塊的形式存放,比如 NAND Flash 上的數據就是以頁為單位存放的。塊設備驅動程序向用戶層提供的接口與字符設備一樣, 應用程序也可以通過相應的設備文件(比如/dev/mtdblock0、/dev/hda1 等)來調用 open、close、read、write 等系統調用,與塊設備傳送任意字節的數據。對用戶而言,字符設備和塊設備的訪問方式沒有差別。塊設備驅動程序的特別之處如下。
1). 操作硬件的接口實現方式不一樣。
塊設備驅動程序先將用戶發來的數據組織成塊,再寫入設備;或從設備中讀出若干塊數據,再從中挑出用戶需要的。
2). 數據塊上的數據可以有一定的格式。
通常在塊設備中按照一定的格式存放數據,不同的文件系統類型就是用來定義這些格式的。內核中,文件系統的層次位于塊設備驅動程序上面,這意味著塊設備驅動程序除了向用戶層提供與字符設備一樣的接口外,還要向內核其他部件提供一些接口,這些接口用戶是看不到的。這些接口使得可以在塊設備上存放文件系統,掛載塊設備。
網絡接口同時具有字符設備、塊設備的部分特點,無法將它歸入這兩類中:如果說它是字符設備,他的輸入/輸出卻是有結構的、成塊的(報文、包、幀);如果說它是塊設備,它的“塊”又不是固定大小的,大到數百甚至數千字節,小到幾字節。UNIX 式的操作系統訪問網絡接口的方法是給它們分配一個惟一的名字(比如 eth0),但這個名字在文件系統中(比如/dev 目錄下)不存在對應的節點項。應用程序、內核和網絡驅動程序間的通信完全不同于字符設備、塊設備,庫、內核提供了一套和數據包傳輸相關的函數,而不是 open、read、write 等。
3 Linux 驅動程序開發步驟
Linux 內核就是由各種驅動組成的,內核源碼中有大約 85%是各種驅動程序的代碼。內核中驅動程序種類齊全,可以在同類驅動的基礎上進行修改以符合具體單板。
編寫驅動程序的難點并不是硬件的具體操作,而是弄清楚現有驅動程序的框架,在這個框架中加入這個硬件。比如,x86 架構的內核對 IDE 硬盤的支持非常完善:首先通過 BIOS 得到硬盤的信息,或者使用默認 I/O 地址去枚舉硬盤,然后識別分區、掛載文件系統。對于其他架構的內核,只是要指定了硬盤的訪問地址和中斷號,后面的枚舉、識別和掛接的過程完全是一樣的。也許修改的代碼不超過 10 行,花費精力的地方在于:了解硬盤驅動的框架, 找到修改的位置。
編寫驅動程序還有很多需要注意的地方,比如:驅動程序可能同時被多個進程使用,這需要考慮并發的問題;盡可能發揮硬件的作用以提高性能。比如在硬盤驅動程序中既可以使用 DMA 也可以不用,使用 DMA 時程序比較復雜,但是可以提高效率;處理硬件的各種異常情況(即使效率低),否則出錯時可能導致整個系統崩潰。
一般來說,編寫一個 Linux 設備驅動程序的大致流程如下。
1). 查看原理圖、數據手冊,了解設備的操作方法。
2). 在內核中找到相近的驅動程序,以它為模板進行開發,有時候需要從零開始。
3). 實現驅動程序的初始化:比如向內核注冊這個驅動程序,這樣應用程序傳入文件名時,內核才能找到相應的驅動程序。
4). 設計所要實現的操作,比如 open、close、read、write 等函數。
5). 實現中斷服務(中斷并不是每個設備驅動所必須的)。
6). 編譯該驅動程序到內核中,或者用 insmod 命令加載。
7). 測試驅動程序。
4 驅動程序的加載和卸載
可以將驅動程序靜態編譯進內核中,也可以將它作為模塊在使用時再加載。在配置內核時,如果某個配置選項被設為 m,就表示它將會被編譯成一個模塊。在 2.6 的內核中,模塊的擴展名為.ko,可以使用 insmod 命令加載,使用 rmmod 命令卸載,使用 lsmod 命令查看內核中已經加載了哪些模塊。
當使用 insmod 加載模塊時,模塊的初始化函數被調用,它用來向內核注冊驅動程序; 當使用 rmmod 卸載模塊時,模塊的清除函數被調用。在驅動代碼中,這兩個函數要么取固定的名字:init_module 和 cleanup_module,要么使用以下兩行來標記它們(假設初始化函數、清除函數為 my_init 和 my_cleanup)。
moudle_init(my_init);
module_exit(my_cleanup);
-
內核
+關注
關注
3文章
1372瀏覽量
40276 -
操作系統
+關注
關注
37文章
6801瀏覽量
123283 -
驅動程序
+關注
關注
19文章
831瀏覽量
48022 -
應用程序
+關注
關注
37文章
3265瀏覽量
57677 -
OpenWrt
+關注
關注
10文章
130瀏覽量
39296
發布評論請先 登錄
相關推薦
評論