李蘭蘭
南京航空航天大學(xué)金城學(xué)院 江蘇省 211156
任何一個(gè)計(jì)算機(jī)系統(tǒng)都是由計(jì)算機(jī)硬件子系統(tǒng)和計(jì)算機(jī)軟件子系統(tǒng)組成的,缺一不可,他們共同協(xié)作完成各種任務(wù),但同時(shí)他們又具有一定的獨(dú)立性。硬件工程師往往不必關(guān)心軟件,而應(yīng)用軟件工程師也不會(huì)過(guò)多關(guān)注硬件。應(yīng)用軟件工程師需要看到一個(gè)沒(méi)有硬件的純粹的軟件世界,硬件對(duì)他們來(lái)說(shuō)是透明的。而設(shè)備驅(qū)動(dòng)程序就能達(dá)到這個(gè)目的,實(shí)現(xiàn)硬件對(duì)應(yīng)用軟件工程師的隱形。
Linux系統(tǒng)下,設(shè)備可以分成三種基本類(lèi)型:字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備。對(duì)于字符設(shè)備而言,往往是以字節(jié)流的形式進(jìn)行訪問(wèn)的設(shè)備,如鼠標(biāo)、觸摸屏等,其對(duì)應(yīng)的驅(qū)動(dòng)類(lèi)型是字符設(shè)備驅(qū)動(dòng)程序。塊設(shè)備可以以任意順序進(jìn)行訪問(wèn),以塊為單位進(jìn)行操作,如硬盤(pán),其驅(qū)動(dòng)通過(guò)塊設(shè)備驅(qū)動(dòng)程序來(lái)實(shí)現(xiàn)。Linux系統(tǒng)中,應(yīng)用程序可以像操作字符設(shè)備一樣的讀寫(xiě)塊設(shè)備,但內(nèi)核為字符設(shè)備驅(qū)動(dòng)和塊設(shè)備驅(qū)動(dòng)提供了完全不同的接口。在Linux系統(tǒng)中,網(wǎng)絡(luò)設(shè)備處理的事務(wù)一般面向數(shù)據(jù)的接收和發(fā)送,不像字符設(shè)備和塊設(shè)備一樣對(duì)應(yīng)于文件系統(tǒng)的節(jié)點(diǎn),其對(duì)應(yīng)的驅(qū)動(dòng)為網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序。內(nèi)核和網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序間的通信,完全不同于和字符及塊設(shè)備驅(qū)動(dòng)程序之間的通信,內(nèi)核為網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序提供了一套和數(shù)據(jù)包傳輸相關(guān)的函數(shù)。
這種分類(lèi)方法并不是非常嚴(yán)格的,對(duì)于某些復(fù)雜的設(shè)備,Linux系統(tǒng)還定義了其他的驅(qū)動(dòng)體系結(jié)構(gòu)。
Linux內(nèi)核非常龐大,包含的組件也非常多。這些組件成為內(nèi)核的一部分通常可以通過(guò)兩種途徑,一種是編譯時(shí)直接成為L(zhǎng)inux內(nèi)核的組件,另一種是編譯時(shí)不進(jìn)入內(nèi)核,但可以在需要時(shí)通過(guò)加載的方式成為內(nèi)核的一部分,當(dāng)不再需要該模塊時(shí),通過(guò)卸載的方式從內(nèi)核中移除。Linux中編寫(xiě)的設(shè)備驅(qū)動(dòng)程序就可以通過(guò)后面這種模塊化的方式進(jìn)入到內(nèi)核中。這種模塊化驅(qū)動(dòng)程序編程方式是Linux系統(tǒng)的一個(gè)很好的特色,有助于縮短模塊的開(kāi)發(fā)周期,不需要每次都經(jīng)過(guò)冗長(zhǎng)的關(guān)機(jī)/重啟過(guò)程。
加載模塊時(shí)可以使用insmod或modprobe命令將模塊加載到正在運(yùn)行的內(nèi)核中,通過(guò)rmmod程序把模塊從內(nèi)核中移除。當(dāng)通過(guò)insmod或modprobe命令加載驅(qū)動(dòng)模塊時(shí),模塊的加載函數(shù)會(huì)自動(dòng)被內(nèi)核執(zhí)行,完成模塊的初始化工作,對(duì)字符設(shè)備驅(qū)動(dòng)程序來(lái)說(shuō),一般會(huì)在加載函數(shù)完成字符設(shè)備的注冊(cè)。當(dāng)通過(guò)rmmod命令卸載驅(qū)動(dòng)模塊時(shí),內(nèi)核會(huì)自動(dòng)運(yùn)行模塊的卸載函數(shù),對(duì)字符設(shè)備驅(qū)動(dòng)程序來(lái)說(shuō),一般在卸載函數(shù)對(duì)字符設(shè)備進(jìn)行注銷(xiāo)。
模塊的加載函數(shù)以“module_init(函數(shù)名)”的形式被指定,若初始化成功,應(yīng)返回 0,而在初始化失敗時(shí),應(yīng)返回一個(gè)負(fù)值。模塊的卸載函數(shù)則以“module_exit(函數(shù)名)”形式指定,但卸載函數(shù)不返回任何值。
Linux內(nèi)核中使用cdev結(jié)構(gòu)體描述字符設(shè)備,其中cdev結(jié)構(gòu)體的dev_t成員定義了32位的設(shè)備號(hào),其中高12位用來(lái)表示主設(shè)備號(hào),低 20位為次設(shè)備號(hào)。可以通過(guò)使用宏MAJOR(dev_t dev)和 MINOR(dev_t dev)來(lái)分別獲取主設(shè)備號(hào)和次設(shè)備號(hào),而宏MKDEV(int major, int minor)則通過(guò)主設(shè)備號(hào)和次設(shè)備號(hào)來(lái)生成dev_t。通常,用主設(shè)備號(hào)來(lái)標(biāo)識(shí)設(shè)備對(duì)應(yīng)的驅(qū)動(dòng)程序。
驅(qū)動(dòng)程序在建立一個(gè)字符設(shè)備之前,首先要做的事情就是獲得一個(gè)或者多個(gè)設(shè)備號(hào)。該過(guò)程可以通過(guò)以下函數(shù)實(shí)現(xiàn)。

register_chrdev_region()函數(shù)用于已知設(shè)備的設(shè)備號(hào)的情況;而alloc_chrdev_region()用于設(shè)備號(hào)未知,內(nèi)核為設(shè)備動(dòng)態(tài)分配所需要的設(shè)備號(hào),函數(shù)調(diào)用成功時(shí),得到的設(shè)備號(hào)會(huì)放入第一個(gè)參數(shù)dev中。動(dòng)態(tài)分配設(shè)備號(hào)的函數(shù)有個(gè)很大的優(yōu)點(diǎn),即能避免設(shè)備號(hào)沖突的發(fā)生。
在模塊卸載函數(shù)中,在對(duì)字符設(shè)備注銷(xiāo)后,調(diào)用unregister_chrdev_region()函數(shù)釋放開(kāi)始分配的設(shè)備號(hào)。該函數(shù)原型為:

在申請(qǐng)完設(shè)備號(hào)之后,需要對(duì)字符設(shè)備進(jìn)行注冊(cè),而字符設(shè)備是用結(jié)構(gòu)體cdev來(lái)表示的,cdev結(jié)構(gòu)體的定義如下:

在對(duì)字符設(shè)備進(jìn)行注冊(cè)時(shí),先為字符設(shè)備分配 cdev結(jié)構(gòu),一般調(diào)用如下函數(shù):

cdev_alloc函數(shù)會(huì)向系統(tǒng)動(dòng)態(tài)申請(qǐng)一個(gè)cdev結(jié)構(gòu),接下來(lái)把該cdev結(jié)構(gòu)嵌入到自己的設(shè)備特定結(jié)構(gòu)中去,初始化已分配到的結(jié)構(gòu):

接下來(lái)對(duì)cdev結(jié)構(gòu)體中的某些字段進(jìn)行初始化:

在設(shè)置好cdev結(jié)構(gòu)體后,通過(guò)下面的函數(shù)調(diào)用把該結(jié)構(gòu)告訴內(nèi)核:

參數(shù)dev為已設(shè)置好的cdev結(jié)構(gòu)體,num為該設(shè)備對(duì)應(yīng)的第一個(gè)設(shè)備號(hào),count應(yīng)該為和該設(shè)備關(guān)聯(lián)的設(shè)備號(hào)的數(shù)量,count的值常常會(huì)被設(shè)置成1。 cdev_add函數(shù)如果返回一個(gè)負(fù)的錯(cuò)誤碼,則設(shè)備不會(huì)被添加到系統(tǒng)中去,只有當(dāng)成功返回時(shí),該字符設(shè)備才真正被添加到內(nèi)核中去,它的操作也才能被內(nèi)核調(diào)用。
當(dāng)需要從系統(tǒng)中刪除一個(gè)字符設(shè)備,則需調(diào)用cdev_del函數(shù),該函數(shù)原型如下:

一旦調(diào)用了該函數(shù),會(huì)刪除cdev結(jié)構(gòu),注銷(xiāo)字符設(shè)備,此時(shí)就不能再訪問(wèn)cdev結(jié)構(gòu)。
file_operations結(jié)構(gòu)體中的成員函數(shù)是字符設(shè)備驅(qū)動(dòng)程序設(shè)計(jì)的主體內(nèi)容,這些函數(shù)會(huì)在應(yīng)用程序進(jìn)行 open()、write()、read()、close()等系統(tǒng)調(diào)用時(shí)最終被調(diào)用。File_operations結(jié)構(gòu)體比較龐大,其中比較主要的成員如下:

該成員指向擁有該結(jié)構(gòu)體的模塊的指針,該字段可以避免內(nèi)核正在操作該模塊時(shí)卸載該模塊。大多情形下,該成員都會(huì)被初始化為T(mén)HIS_MODULE。

用來(lái)從設(shè)備讀取數(shù)據(jù)。函數(shù)返回非負(fù)值表示成功讀取的字節(jié)數(shù),出錯(cuò)時(shí)返回一個(gè)負(fù)值。

用來(lái)向設(shè)備發(fā)送數(shù)據(jù)。函數(shù)返回的非負(fù)值表示成功寫(xiě)入的字節(jié)數(shù),

用來(lái)修改文件當(dāng)前讀寫(xiě)位置,并將新位置作為返回值返回,在出錯(cuò)時(shí),該函數(shù)返回一個(gè)負(fù)值。

用來(lái)提供一種執(zhí)行設(shè)備特定命令的方法。當(dāng)調(diào)用成功時(shí),返回給調(diào)用程序一個(gè)非負(fù)值。內(nèi)核本身常常會(huì)提供一些操作設(shè)備的命令,而不需要調(diào)用設(shè)備驅(qū)動(dòng)中的ioctl函數(shù)。如果設(shè)備不提供ioctl函數(shù),則對(duì)于任何內(nèi)核未預(yù)先定義的請(qǐng)求,ioctl系統(tǒng)調(diào)用將返回一個(gè)負(fù)的錯(cuò)誤碼。
字符設(shè)備驅(qū)動(dòng)中需要對(duì)file_operations結(jié)構(gòu)進(jìn)行初始化,如下:

其中read和write函數(shù)分別進(jìn)行拷貝數(shù)據(jù)到應(yīng)用程序空間和從應(yīng)用程序空間拷貝數(shù)據(jù)的操作,ssize_t (*read) (struct file *filp, char __user*buff, size_t count, loff_t *offp);

對(duì)于這兩個(gè)函數(shù),參數(shù)filp是文件指針;參數(shù)count是請(qǐng)求傳輸?shù)臄?shù)據(jù)長(zhǎng)度;參數(shù)buff指向用戶(hù)空間緩沖區(qū),這個(gè)緩沖區(qū)或者保存要寫(xiě)入的數(shù)據(jù),或者是一個(gè)存放新讀入數(shù)據(jù)的空緩沖區(qū);參數(shù)offp指明用戶(hù)在文件中進(jìn)行存取操作的位置。對(duì)于表示用戶(hù)空間的指針的buff參數(shù),內(nèi)核代碼不能直接引用其中的內(nèi)容,而驅(qū)動(dòng)程序又必須訪問(wèn)用戶(hù)空間的緩沖區(qū)以便完成自己的工作,這種訪問(wèn)應(yīng)始終通過(guò)內(nèi)核提供的專(zhuān)用函數(shù)完成。Read函數(shù)的任務(wù)是從設(shè)備拷貝數(shù)據(jù)到用戶(hù)空間,通過(guò)使用copy_to_user函數(shù)來(lái)實(shí)現(xiàn);write函數(shù)的任務(wù)則是從用戶(hù)空間拷貝數(shù)據(jù)到設(shè)備上,這可通過(guò)使用copy_from_user函數(shù)來(lái)實(shí)現(xiàn)。實(shí)現(xiàn)內(nèi)核空間和用戶(hù)空間之間拷貝數(shù)據(jù)的這兩個(gè)函數(shù)的原型是:


對(duì)編寫(xiě)好的字符設(shè)備驅(qū)動(dòng)程序進(jìn)行編譯,將會(huì)得到mydev.ko文件,通過(guò)命令”insmod mydev.ko”加載驅(qū)動(dòng)模塊,運(yùn)行”lsmod”命令,發(fā)現(xiàn) mydev模塊已被加載。當(dāng)執(zhí)行”cat/proc/devices”命令,發(fā)現(xiàn)多出了主設(shè)備號(hào)為***的“mydev“字符設(shè)備驅(qū)動(dòng)。
接下來(lái)通過(guò)命令“mknod /dev/mydev c *** 0“創(chuàng)建”/dev/mydev”設(shè)備節(jié)點(diǎn),最后進(jìn)行驗(yàn)證。通過(guò)命令“echo “Is the driver OK” > /dev/mydev”和 命令“cat /dev/mydev”分別驗(yàn)證設(shè)備的寫(xiě)和讀,結(jié)果證明字符“Is the driver OK”被成功寫(xiě)入到mydev字符設(shè)備中去。
本文介紹的字符設(shè)備驅(qū)動(dòng)程序的設(shè)計(jì)是針對(duì)Linux內(nèi)核2.6而言的,相比較塊設(shè)備和網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序而言,字符設(shè)備驅(qū)動(dòng)程序還是相對(duì)較簡(jiǎn)單的一類(lèi)驅(qū)動(dòng)程序。對(duì)于實(shí)際的物理設(shè)備,當(dāng)把該設(shè)備注冊(cè)為字符設(shè)備時(shí),除了實(shí)現(xiàn)字符設(shè)備驅(qū)動(dòng)的部分,往往還需根據(jù)設(shè)備本身的特點(diǎn),實(shí)現(xiàn)設(shè)備功能相關(guān)的代碼。
[1]Cobet,Rubini,Kroah-Hartman. Linux設(shè)備驅(qū)動(dòng)程序.第三版.魏永明等譯.北京:中國(guó)電力出版社.2005.
[2]宋寶華編著.Linux設(shè)備驅(qū)動(dòng)開(kāi)發(fā)詳解.北京:人民郵電出版社.2008.
[3] NeilMatthew RichardStones著.Linux程序設(shè)計(jì)[M].楊曉云,楊濤譯等譯.北京:機(jī)械工業(yè)出版社.2002.
[4]Mait Weish,Matthias Kalle Dalheimer,Lar Kaufman著.Linux權(quán)威指南(第3版)[M].北京:中國(guó)電力出版社.2000.
網(wǎng)絡(luò)安全技術(shù)與應(yīng)用2012年4期