新聞中心

EEPW首頁 > 消費(fèi)電子 > 設(shè)計(jì)應(yīng)用 > 基于嵌入式Linux的設(shè)備驅(qū)動程序設(shè)計(jì)

基于嵌入式Linux的設(shè)備驅(qū)動程序設(shè)計(jì)

——
作者: 時間:2007-11-17 來源:單片機(jī)與嵌入式系統(tǒng)應(yīng)用 收藏

  為是一個成熟而穩(wěn)定的。將植入嵌入式設(shè)備具有眾多的優(yōu)點(diǎn),包括可剪裁和容易移植等,所以在嵌入式領(lǐng)域獲得了廣泛的應(yīng)用。嵌入式Linux一直是嵌入式領(lǐng)域的研究熱點(diǎn),與PC架構(gòu)不同,的硬件具有多樣性和差異性,的開發(fā)需要對特定系統(tǒng)進(jìn)行硬件設(shè)計(jì),同時還要針對這些硬件來編寫。Linux內(nèi)核就是通過來同外圍設(shè)備打交道的,系統(tǒng)設(shè)計(jì)人員必須為每個設(shè)備編寫,否則設(shè)備無法在下正常工作。

  1 設(shè)備驅(qū)動程序設(shè)計(jì)的基本概念與模型

  設(shè)備驅(qū)動程序是操作系統(tǒng)內(nèi)核與機(jī)器硬件之間的接口,它為應(yīng)用程序屏蔽了硬件的細(xì)節(jié),在應(yīng)用程序看來,硬件設(shè)備只是一個設(shè)備文件,應(yīng)用程序可以象操作普通文件一樣對硬件設(shè)備進(jìn)行操作,設(shè)計(jì)驅(qū)動程序是內(nèi)核的一部分,可以實(shí)現(xiàn)以下功能:

  對設(shè)備初始化和釋放;

  把數(shù)據(jù)從內(nèi)核傳送到硬件,以及從硬件讀取數(shù)據(jù);

  讀取應(yīng)用程序傳送給設(shè)備文件的數(shù)據(jù),以及回送應(yīng)用程序請求的數(shù)據(jù);

  檢測和處理設(shè)備出現(xiàn)的錯誤。

  前面已經(jīng)提到驅(qū)動程序的作用,而編寫驅(qū)動程序就是構(gòu)造一系列可供應(yīng)用程序調(diào)動的函數(shù)(包括open、release、read、write、llseek、ioctl等)。在用戶自己的驅(qū)動程序中,首先要根據(jù)驅(qū)動程序的功能,實(shí)現(xiàn)file_operations結(jié)構(gòu)中的函數(shù),不需要的函數(shù)接口可以直接在file_operations結(jié)構(gòu)中初始化為NULL;file_operations變量會在驅(qū)動程序初始化時注冊到系統(tǒng)內(nèi)部。當(dāng)操作系統(tǒng)對設(shè)備操作時,會調(diào)用驅(qū)動程序注冊的file_operations結(jié)構(gòu)中的函數(shù)指針。

  以下是嵌入式linux2.4設(shè)備驅(qū)動程序的最簡模型。

  

  

  具體實(shí)現(xiàn)前面定義的函數(shù)時,需注意下面幾點(diǎn):

  1)在test_init函數(shù)中要通過調(diào)用register_chrdev()函數(shù)來向內(nèi)核注冊字符設(shè)備驅(qū)動程序。如果是塊設(shè)備,則還需調(diào)用mmmap()進(jìn)行地址空間的映射,再調(diào)用register_blkdev()函數(shù)來向內(nèi)核注冊塊設(shè)備驅(qū)動程序,在Linux系統(tǒng)中,對中斷的處理是屬于系統(tǒng)核心部分,因而如果設(shè)備與系統(tǒng)之間以中斷方式進(jìn)行數(shù)據(jù)交換,則必須把該設(shè)備的驅(qū)動程序作為系統(tǒng)核心的一部分,也就是說設(shè)備驅(qū)動程序要通過調(diào)用request_irq()函數(shù)來申請中斷,通過free_irq()函數(shù)來釋放中斷(在test_cleanup中實(shí)現(xiàn))。

  2)open()函數(shù)和release()函數(shù)的具體實(shí)現(xiàn)有著一定的對應(yīng)性,在open()函數(shù)中主要是執(zhí)行打開設(shè)備時的一些初始化代碼,如果該驅(qū)動程序需要管理多個設(shè)備,那么還要獲取從設(shè)備號,根據(jù)從設(shè)備號來判斷需要操作的設(shè)備,其中,從設(shè)備號可通過調(diào)用函數(shù)MINOR(inode->i_rdev)來獲得,然后再調(diào)用宏MOD_INC_USE_COUNT來使得驅(qū)動程序使用計(jì)數(shù)器加1,而在release()函數(shù)中則要進(jìn)行相反的處理。即調(diào)用宏MOD_DEC_USE_COUNT來減小驅(qū)動程序使用計(jì)數(shù)器。

  3)歸根到底,驅(qū)動函數(shù)的實(shí)現(xiàn)就是調(diào)用內(nèi)核所支持的函數(shù)(包括內(nèi)核提供的API和用戶自己定義的寄存器操作函數(shù))來完成對設(shè)備的操作,雖然設(shè)備的種類眾多,不同設(shè)備操作的具體實(shí)現(xiàn)方法不可能相同,但是Linux操作系統(tǒng)提供了一系列特殊API,為開發(fā)內(nèi)核驅(qū)動程序帶來了很大的方便,調(diào)用這些API時需要注意的是:通常情況下,應(yīng)用程序是通過內(nèi)核接口訪問驅(qū)動程序的(這是驅(qū)動程序的主要使用方式),因此驅(qū)動程序需要與應(yīng)用程序交換數(shù)據(jù),但是操作系統(tǒng)內(nèi)核和驅(qū)動程序在內(nèi)核空間中運(yùn)行,而用戶程序在用戶空間中運(yùn)行,用戶程序不能訪問內(nèi)核空間,操作系統(tǒng)內(nèi)核和驅(qū)動程序也不能使用指針或memcpy()等常規(guī)的C庫函與用戶空間傳輸數(shù)據(jù),造成這種狀況的主要原因是linux操作系統(tǒng)使用了虛擬內(nèi)存機(jī)制,使用了虛擬內(nèi)存機(jī)制后,用戶空間的內(nèi)存可能被換出,當(dāng)內(nèi)核使用用戶空間指針時,對應(yīng)的頁面可能已經(jīng)不在內(nèi)存中了,因此在使用調(diào)用函數(shù)時要注意:設(shè)備驅(qū)動程序在申請和釋放內(nèi)存時不是調(diào)用malloc()和free(),而調(diào)用kmalloc()和kfree();用于內(nèi)核空間與用戶空間進(jìn)行數(shù)據(jù)拷貝的函數(shù)主要有access_ok()(檢查某內(nèi)存空間是否有權(quán)訪問),copy_to_user()和put_usr()(內(nèi)核函數(shù)向用戶空間傳輸數(shù)據(jù)),copy_from_user()和get_user()(用戶空間向內(nèi)核空間傳輸數(shù)據(jù));關(guān)于內(nèi)核空間與I/O空間的數(shù)據(jù)交換,不同體系結(jié)構(gòu)的處理器對I/O的處理方式也不同,x86系列處理器中,I/O與內(nèi)存完成不同,它是分開編址的,訪問它要使用專用的指令;而對ARM體系結(jié)構(gòu)的處理器來說,則是不區(qū)分I/O和內(nèi)存,統(tǒng)一編址的,可以使用同樣的指令訪問,在驅(qū)動程序中可以使用一系列函數(shù)來訪問I/O口,如outb()、outw()、outl()inb()、inw()、inl()、outsb()、outsw()、outsl()、insb()、insw()和insl()等。

  2 Linux2.6與2.4內(nèi)核驅(qū)動程序的區(qū)別

  為了徹底防止對正在被使用的內(nèi)核模塊進(jìn)行錯誤操作,linux2.6內(nèi)核在加載和導(dǎo)出內(nèi)核模塊方面都較2.4內(nèi)核有所改進(jìn),避免了用戶執(zhí)行將導(dǎo)致系統(tǒng)崩潰的操作(例如強(qiáng)制刪除模塊等)。同時,當(dāng)驅(qū)動程序需要在多個文件中包含頭文件時,不必定義宏來檢查內(nèi)核的版本。與2.4內(nèi)核相比,2.6內(nèi)核在可擴(kuò)展性、吞吐率等方面有較大提升,其新特性主要包括:使用了新的調(diào)度器算法;內(nèi)核搶占功能顯著地降低了用戶交互式應(yīng)用程序;多媒體應(yīng)用程序等類似應(yīng)用程序的延遲;改進(jìn)了線程模型以及對NPTL的支持,顯著改善了虛擬內(nèi)存在一定成程度負(fù)載下的性能;能夠支持更多的文件系統(tǒng);引進(jìn)了內(nèi)存池技術(shù);支持更多的系統(tǒng)設(shè)備,在2.4內(nèi)核中有約束大型系統(tǒng)的限制,其支持的每一類設(shè)備的最大數(shù)量為256,而2.6內(nèi)核則徹底打破了這些限制,可以支持4095種主要的設(shè)備類型,且每個單獨(dú)的類型又可以支持超過一百萬個的子設(shè)備;支持反向映射機(jī)制(reverse mapping),內(nèi)存管理器為每一個物理頁建立一個鏈表,包含指向當(dāng)前映射頁中每個進(jìn)程的頁表?xiàng)l目的指針。該鏈表叫PTE鏈,它極大的提高了找到那些映射某個頁的進(jìn)程的速度。

  Linux操作系統(tǒng)的設(shè)備驅(qū)動程序是在內(nèi)核空間運(yùn)行的程序,其中涉及很多內(nèi)核的操作,隨著Linux內(nèi)核版本的升級,驅(qū)動程序的開發(fā)必然也要作出相應(yīng)的修改,總之,在linux2.6內(nèi)核上編寫設(shè)備驅(qū)動程序時具體要注意以下幾個方面:

  1)Linux2.6內(nèi)核驅(qū)動程序必須由MODULE_LICENSE("Dual BSD/GPL")語句來定義許可證,而不能再用2.4內(nèi)核的MODULE_LICENSE("GPL")。否則,在編譯時會出現(xiàn)警告提示。

  2)Linux2.6內(nèi)核驅(qū)動程序可以用int try_module_get(&module)來加載模塊,用module_put()函數(shù)來卸載模塊,而以前2.4內(nèi)核使用的宏MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT則可不用。

  3)前面給出的字符型設(shè)備驅(qū)動程序模型中結(jié)構(gòu)體file_operations的定義要采用下面的形式。這是因?yàn)樵贚inux內(nèi)核中對結(jié)構(gòu)體的定義形式發(fā)生了變化,不再支持原來的定義形式。

  

  4)就字符型設(shè)備而言,test_open()函數(shù)中向內(nèi)核注冊設(shè)備的調(diào)用函數(shù)register_chrdev()可以升級為int register_chrdev_region(dev_t from,unsigned count,char * name),如果要動態(tài)申請主設(shè)備號可調(diào)用函數(shù)int alloc_chrdev_region(dev_t * dev,unsigned baseminor,unsigned count,char * name)來完成;原來的注冊函數(shù)還可以用,只是不能注冊設(shè)備號大于256的設(shè)備,同理,對于塊設(shè)備和網(wǎng)絡(luò)設(shè)備的注冊函數(shù)也有著相對應(yīng)的代替函數(shù)。

  5)在聲明驅(qū)動程序是否要導(dǎo)出符號表方面有著很大的變化。當(dāng)驅(qū)動程序模塊裝入內(nèi)核后,它所導(dǎo)出的任何符號都會變成公共符合表的一部分,在/proc/ksyms中可以看到這些新增加的符號。通常情況之下,模塊只需實(shí)現(xiàn)自己的功能,不必導(dǎo)出任何符號,然而,如果有其他模塊需要使用模塊導(dǎo)出的符號時,就必須導(dǎo)出符號,只有顯示的導(dǎo)出符號才能被其他模塊使用,Linux2.6內(nèi)核中默認(rèn)不導(dǎo)出所有的符號,不必使用EXPORT_NO_SYMBOLS宏來定義;而在2.4內(nèi)核中恰恰相反,它默認(rèn)導(dǎo)出所有的符號,除非使用EXPORT_NO_SYMBOLS,因此在上面給出的范例中可以省略去該定義語句。

  6)Linx內(nèi)核統(tǒng)一了很多設(shè)備類型,同時也支持更大的系統(tǒng)和更多的設(shè)備,原來Linux2.4內(nèi)核中的變量kdev_t已經(jīng)被廢除不可用,取而代之的是dev_t。它拓展到了32位,其中包括12位主設(shè)備號和20位次設(shè)備號。調(diào)用函數(shù)為unsigned int iminor(struct inode * inode)和unsigned int imajor(struct inode * inode),而不再用Linux2.4版本中的int MAJOR(kdev_t dev)和int MINOR(kdev_t dev)。

  所有的內(nèi)存分配函數(shù)不再包含在頭文件中,而是包含在中,而原來的已經(jīng)不存在。所以當(dāng)在驅(qū)動程序中要用到函數(shù)kmalloc()或kfree()等內(nèi)存分配函數(shù)時,就必須要定義頭文件而不是。同時,前面提到的申請內(nèi)存和釋放內(nèi)存函數(shù)的具體參數(shù)也有了一定的改變,包括:分配標(biāo)志GFP_BUFFER被取消,取而代之的是GFP_NOIO和GFP_NOFS;新增了_GFP_REPEAT、_GFP_NOFAIL和_GFP_NORETRY分配標(biāo)志等,使得內(nèi)存操作更加方便。

  8)因?yàn)閮?nèi)核中有些地方的內(nèi)存分配是不允許失敗的,所以為了確保這種情況下得成功分配,linux2.6版本內(nèi)核中開發(fā)了一種稱為"內(nèi)存池"的抽象。內(nèi)存池其實(shí)相當(dāng)于后備的高速緩存,以便在緊急狀態(tài)下使用。要使用內(nèi)存池的處理函數(shù)時,必須包含頭文件。內(nèi)存池處理函數(shù)主要有以下幾個:mempool_t *mempool_create()、void*mempool_alloc()、void mempool_free()、int mempool_resize();

  另外值得一提的是:2.6內(nèi)核為了區(qū)別以.o為擴(kuò)展名的常規(guī)對象文件,將內(nèi)核模塊的擴(kuò)展名改為.ko,所以驅(qū)動程序最后是被編譯為ko后綴的可加載模塊,在應(yīng)用程序中加載驅(qū)動程序模塊時要注意。 結(jié)語

  驅(qū)動程序的開發(fā)作為嵌入式linux系統(tǒng)開發(fā)過程當(dāng)中最重要的環(huán)節(jié)之一,與硬件特性和操作系統(tǒng)的內(nèi)核有著緊密的聯(lián)系。隨著linux內(nèi)核版本的升級,內(nèi)核驅(qū)動程序必然要作出相應(yīng)的改進(jìn),相信隨著嵌入式Linux系統(tǒng)在各個領(lǐng)域中的廣泛應(yīng)用,具有可搶占實(shí)時性的Linux2.6內(nèi)核必定會在嵌入式領(lǐng)域大顯身手。本文會對廣大的驅(qū)動程序開發(fā)人員有一定的幫助。

linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)


評論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉