對(duì) Linux 內(nèi)核狼讨、驅(qū)動(dòng)有初步的認(rèn)識(shí)
-
應(yīng)用程序、庫(kù)、內(nèi)核享完、驅(qū)動(dòng)程序的關(guān)系
應(yīng)用程序使用庫(kù)提供的 open 函數(shù)打開代表 LED 的設(shè)備文件。
庫(kù)根據(jù) open 函數(shù)傳入的參數(shù)執(zhí)行“ SWI”指令有额,該指令會(huì)引起 CPU 異常般又,進(jìn)入內(nèi)核。
內(nèi)核的異常處理函數(shù)根據(jù)這些參數(shù)找到相應(yīng)的驅(qū)動(dòng)程序巍佑,返回一個(gè)文件句柄給庫(kù)茴迁,進(jìn)而返回給應(yīng)用程序。
應(yīng)用程序得到文件句柄后萤衰,使用庫(kù)提供的 write 或 ioclt 函數(shù)發(fā)出控制命令堕义。
庫(kù)根據(jù) write 和 ioclt 函數(shù)傳人的參數(shù)執(zhí)行 “ swi” 指令, 這條指令會(huì)引起 CPU 異常脆栋,進(jìn)入內(nèi)核倦卖。
內(nèi)核的異常處理函數(shù)根據(jù)這些參數(shù)調(diào)用驅(qū)動(dòng)程序的相關(guān)函數(shù),點(diǎn)亮 LED椿争。
庫(kù)(比如 glibc)給應(yīng)用程序提供的 open怕膛、 read、 write丘薛、 ioctl嘉竟、 mmap 等接口函數(shù)被稱為系統(tǒng)調(diào)用,它們都是設(shè)置好相關(guān)寄存器后,執(zhí)行某條指令引發(fā)異常進(jìn)入內(nèi)核舍扰。
除系統(tǒng)調(diào)用接口外倦蚪, 庫(kù)還提供其他函數(shù), 比如字符串處理函數(shù)(strcpy边苹、 strcmp 等)陵且、 輸入/輸出函數(shù)(scanf、 printf 等)个束、數(shù)學(xué)庫(kù)慕购,還有應(yīng)用程序的啟動(dòng)代碼等。在異常處理函數(shù)中茬底,內(nèi)核會(huì)根據(jù)傳入的參數(shù)執(zhí)行各種操作沪悲,比如根據(jù)設(shè)備文件名找到對(duì)應(yīng)的驅(qū)動(dòng)程序,調(diào)用驅(qū)動(dòng)程序的相關(guān)函數(shù)等阱表。一般來說殿如,當(dāng)應(yīng)用程序調(diào)用 open、 read最爬、write涉馁、 ioctl、 mmap 等函數(shù)后爱致,將會(huì)使用驅(qū)動(dòng)程序中的 open烤送、 read、 write糠悯、 ioctl帮坚、 mmap函數(shù)來執(zhí)行相關(guān)操作,比如初始化互艾、讀叶沛、寫等。實(shí)際上忘朝,內(nèi)核和驅(qū)動(dòng)程序之間并沒有界線,因?yàn)轵?qū)動(dòng)程序最終是要編進(jìn)內(nèi)核去的:通過靜態(tài)鏈接和動(dòng)態(tài)加載判帮。
從上面操作 LED 的過程可以知道局嘁,與應(yīng)用程序不同,驅(qū)動(dòng)程序從不主動(dòng)運(yùn)行晦墙,它是被動(dòng)的:根據(jù)應(yīng)用程序的要求進(jìn)行初始化悦昵,根據(jù)應(yīng)用程序的要求進(jìn)行讀寫。驅(qū)動(dòng)程序加載進(jìn)內(nèi)核時(shí)晌畅,只是告訴內(nèi)核“我在這里但指,我能做這些工作” ,至于這些“工作”何時(shí)開始,取決于應(yīng)用程序棋凳。當(dāng)然拦坠,這不是絕對(duì)的,比如用戶完全可以寫一個(gè)系統(tǒng)時(shí)鐘觸發(fā)的驅(qū)動(dòng)程序剩岳,讓它自動(dòng)點(diǎn)亮 LED贞滨。
在 Linux 系統(tǒng)中,應(yīng)用程序運(yùn)行于“用戶空間” 拍棕,擁有 MMU 的系統(tǒng)能夠限制應(yīng)用程序的權(quán)限(比如將它限制于某個(gè)內(nèi)存塊中)晓铆,這可以避免應(yīng)用程序的錯(cuò)誤使整個(gè)系統(tǒng)崩潰。
而驅(qū)動(dòng)程序運(yùn)行于“內(nèi)核空間” 绰播,它是系統(tǒng)“信任”的一部分骄噪,驅(qū)動(dòng)程序的錯(cuò)誤有可能導(dǎo)致整個(gè)系統(tǒng)崩潰。
Linux 驅(qū)動(dòng)程序分類
Linux 的外設(shè)可以分為 3 類:
字符設(shè)備
塊設(shè)備
網(wǎng)絡(luò)接口蠢箩。
字符設(shè)備是能夠像字節(jié)流(比如文件)一樣被訪問的設(shè)備链蕊,就是說對(duì)它的讀寫是以字節(jié)為單位的。 比如串口在進(jìn)行收發(fā)數(shù)據(jù)時(shí)就是一個(gè)字節(jié)一個(gè)字節(jié)的進(jìn)行的忙芒, 我們可以在驅(qū)動(dòng)程序內(nèi)部使用緩沖區(qū)來存放數(shù)據(jù)以提高效率示弓, 但是串口本身對(duì)這并沒有要求。 字符設(shè)備的驅(qū)動(dòng)程序中實(shí)現(xiàn)了 open呵萨、 close奏属、 read、 write 等系統(tǒng)調(diào)用潮峦,應(yīng)用程序可以通過設(shè)備文件(比如/dev/ttySAC0 等)來訪問字符設(shè)備囱皿。
塊設(shè)備上的數(shù)據(jù)以塊的形式存放,比如 NAND Flash 上的數(shù)據(jù)就是以頁(yè)為單位存放的忱嘹。塊設(shè)備驅(qū)動(dòng)程序向用戶層提供的接口與字符設(shè)備一樣嘱腥, 應(yīng)用程序也可以通過相應(yīng)的設(shè)備文件(比如/dev/mtdblock0、 /dev/hda1 等)來調(diào)用 open拘悦、 close齿兔、 read、 write 等系統(tǒng)調(diào)用础米,與塊設(shè)備傳送任意字節(jié)的數(shù)據(jù)分苇。對(duì)用戶而言,字符設(shè)備和塊設(shè)備的訪問方式?jīng)]有差別屁桑。
塊設(shè)備驅(qū)動(dòng)程序的特別之處如下医寿。
- 操作硬件的接口實(shí)現(xiàn)方式不一樣。
塊設(shè)備驅(qū)動(dòng)程序先將用戶發(fā)來的數(shù)據(jù)組織成塊蘑斧,再寫入設(shè)備靖秩;或從設(shè)備中讀出若干塊數(shù)據(jù)须眷,再?gòu)闹刑舫鲇脩粜枰摹?/li> - 數(shù)據(jù)塊上的數(shù)據(jù)可以有一定的格式。
通常在塊設(shè)備中按照一定的格式存放數(shù)據(jù)沟突,不同的文件系統(tǒng)類型就是用來定義這些格式的花颗。內(nèi)核中,文件系統(tǒng)的層次位于塊設(shè)備驅(qū)動(dòng)程序上面事扭,這意味著塊設(shè)備驅(qū)動(dòng)程序除了向用戶層提供與字符設(shè)備一樣的接口外捎稚, 還要向內(nèi)核其他部件提供一些接口, 這些接口用戶是看不到的求橄。這些接口使得可以在塊設(shè)備上存放文件系統(tǒng)今野,掛載塊設(shè)備。
網(wǎng)絡(luò)接口同時(shí)具有字符設(shè)備罐农、塊設(shè)備的部分特點(diǎn)条霜,無法將它歸入這兩類中:如果說它是字符設(shè)備,他的輸入/輸出卻是有結(jié)構(gòu)的涵亏、成塊的(報(bào)文宰睡、包、幀)气筋;如果說它是塊設(shè)備拆内,它的“塊”又不是固定大小的,大到數(shù)百甚至數(shù)千字節(jié)宠默,小到幾字節(jié)麸恍。
UNIX 式的操作系統(tǒng)訪問網(wǎng)絡(luò)接口的方法是給它們分配一個(gè)惟一的名字(比如 eth0),但這個(gè)名字在文件系統(tǒng)中(比如/dev 目錄下)不存在對(duì)應(yīng)的節(jié)點(diǎn)項(xiàng)搀矫。應(yīng)用程序抹沪、內(nèi)核和網(wǎng)絡(luò)驅(qū)動(dòng)程序間的通信完全不同于字符設(shè)備、 塊設(shè)備瓤球,庫(kù)融欧、 內(nèi)核提供了一套和數(shù)據(jù)包傳輸相關(guān)的函數(shù), 而不是 open卦羡、 read噪馏、 write 等。
Linux 驅(qū)動(dòng)程序開發(fā)步驟
Linux 內(nèi)核就是由各種驅(qū)動(dòng)組成的绿饵, 內(nèi)核源碼中有大約 85%是各種驅(qū)動(dòng)程序的代碼逝薪。內(nèi)核中驅(qū)動(dòng)程序種類齊全,可以在同類驅(qū)動(dòng)的基礎(chǔ)上進(jìn)行修改以符合具體單板蝴罪。編寫驅(qū)動(dòng)程序的難點(diǎn)并不是硬件的具體操作,而是弄清楚現(xiàn)有驅(qū)動(dòng)程序的框架步清,在這個(gè)框架中加入這個(gè)硬件要门。比如虏肾, x86 架構(gòu)的內(nèi)核對(duì) IDE 硬盤的支持非常完善:首先通過 BIOS得到硬盤的信息,或者使用默認(rèn) I/O 地址去枚舉硬盤欢搜,然后識(shí)別分區(qū)封豪、掛載文件系統(tǒng)。對(duì)于其他架構(gòu)的內(nèi)核炒瘟,只是要指定了硬盤的訪問地址和中斷號(hào)吹埠,后面的枚舉、識(shí)別和掛接的過程完全是一樣的疮装。
也許修改的代碼不超過 10 行缘琅,花費(fèi)精力的地方在于:
了解硬盤驅(qū)動(dòng)的框架, 找到修改的位置廓推。
編寫驅(qū)動(dòng)程序還有很多需要注意的地方刷袍,比如:驅(qū)動(dòng)程序可能同時(shí)被多個(gè)進(jìn)程使用,這需要考慮并發(fā)的問題樊展;盡可能發(fā)揮硬件的作用以提高性能呻纹。比如在硬盤驅(qū)動(dòng)程序中既可以使用 DMA 也可以不用,使用 DMA 時(shí)程序比較復(fù)雜专缠,但是可以提高效率雷酪;處理硬件的各種異常情況(即使效率低),否則出錯(cuò)時(shí)可能導(dǎo)致整個(gè)系統(tǒng)崩潰涝婉。
一般來說哥力,編寫一個(gè) Linux 設(shè)備驅(qū)動(dòng)程序的大致流程如下。
- 查看原理圖嘁圈、數(shù)據(jù)手冊(cè)省骂,了解設(shè)備的操作方法。
- 在內(nèi)核中找到相近的驅(qū)動(dòng)程序最住,以它為模板進(jìn)行開發(fā)钞澳,有時(shí)候需要從零開始。
- 實(shí)現(xiàn)驅(qū)動(dòng)程序的初始化:比如向內(nèi)核注冊(cè)這個(gè)驅(qū)動(dòng)程序涨缚,這樣應(yīng)用程序傳入文件名時(shí)轧粟,內(nèi)核才能找到相應(yīng)的驅(qū)動(dòng)程序。
- 設(shè)計(jì)所要實(shí)現(xiàn)的操作脓魏,比如 open兰吟、 close、 read茂翔、 write 等函數(shù)混蔼。
- 實(shí)現(xiàn)中斷服務(wù)(中斷并不是每個(gè)設(shè)備驅(qū)動(dòng)所必須的)。
- 編譯該驅(qū)動(dòng)程序到內(nèi)核中珊燎,或者用 insmod 命令加載惭嚣。
- 測(cè)試驅(qū)動(dòng)程序遵湖。
驅(qū)動(dòng)程序的加載和卸載
可以將驅(qū)動(dòng)程序靜態(tài)編譯進(jìn)內(nèi)核中,也可以將它作為模塊在使用時(shí)再加載晚吞。在配置內(nèi)核時(shí),如果某個(gè)配置選項(xiàng)被設(shè)為 m槽地,就表示它將會(huì)被編譯成一個(gè)模塊迁沫。
在 2.6 的內(nèi)核中,模塊的擴(kuò)展名為.ko捌蚊,可以使用 insmod 命令加載集畅,使用 rmmod 命令卸載,使用 lsmod 命令查看內(nèi)核中已經(jīng)加載了哪些模塊逢勾。當(dāng)使用 insmod 加載模塊時(shí)牡整,模塊的初始化函數(shù)被調(diào)用,它用來向內(nèi)核注冊(cè)驅(qū)動(dòng)程序溺拱;當(dāng)使用 rmmod 卸載模塊時(shí)逃贝,模塊的清除函數(shù)被調(diào)用。
在驅(qū)動(dòng)代碼中迫摔,這兩個(gè)函數(shù)要么取固定的名字:init_module 和 cleanup_module沐扳,要么使用以下兩行來標(biāo)記它們(假設(shè)初始化函數(shù)、 清除函數(shù)為 my_init 和 my_cleanup)句占。moudle_init(my_init);