




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
-.z.目錄中文目錄格式中文目錄格式摘要………………1關鍵詞……………1前言………………2第1章Linu*設備驅動程序編寫方式…………..31設備驅動程序的編寫模式2module的原理第2章Linu*下的驅動設備類型………………211CharacterDevices2BlockDevices第3章Linu*設備驅動程序的框架…………251設備驅動程序的功能1對設備初始化和釋放1.2把數據從內核傳送到硬件和從硬件讀取數據1.3讀取應用程序傳送給設備文件的數據和回送應用程序請求的數據1.4檢測和處理設備出現的錯誤設備驅動程序的組成局部2.1自動配置和初始化子程序2.2效勞于I/O請求的子程序2.3中斷效勞子程序3設備驅動程序的構造1驅動程序注冊與注銷3.2設備的翻開與釋放3.3設備的讀寫操作3.4設備的控制操作3.5設備的中斷和輪詢處理4設備驅動程序接口第4章Linu*設備驅動程序的實現…………301PCI驅動程序實現的關鍵數據構造1pci_driver構造2pci_dev構造寫驅動程序的fuctoin問題2.1function的encode問題2.2function的e*port問題2.3兩個常用function3設備驅動程序中的一些具體問題1I/OPort3.2內存操作3.3中斷處理4RealtekFastEthernetDriverrtl8139網卡驅動3個模塊的改寫1設備指明模塊4.2數據讀寫和控制信息模塊4.3中斷處理模塊參考文獻……….25附錄…………….26致謝……………28論Linu*KernelModule之設備驅動程序【摘要】驅動程序編寫方法。由Linu*設備驅動程序編寫方式著手,轉而研究設備驅動程序的KernelModule程序。在了解了Linu*下的驅動設備類型之后,從Linu*下設備驅動程序的功能,組成局部,構造,接口4個方面分析了設備驅動程的框架。在參看了許多的書籍和網絡論壇的文章,資料,具體討論了Linu*下設備驅動程序的實現,具體研究了驅動程序的數據構造,function問題和編寫程序時各個局部可能出現的難點,疑點問題。并根據DonaldBecker1999-2000年編寫的RealtekFastEthernetDriverrtl8139網卡驅動Linu*版(內核版本2.0.24)改寫了其中的3個模塊:設備指明模塊,數據讀寫和控制信息模塊與中斷處理模塊。【關鍵詞】KernelLinu*內核KernelModuleLinu*的內核模塊CharacterDevices字符設備BlockDevices塊設備Funtion函數前言Linu*是最受歡送的自由電腦操作系統內核。它是一個用C語言寫成,符合POSI*標準的類Uni*操作系統。Linu*最早是由芬蘭黑客LinusTorvalds為嘗試在英特爾*86架構上提供自由免費的類Uni*操作系統而開發的。該方案開場于1991年,從LinusTorvalds當時在Usenet新聞組comp.os.mini*所登載了一分著名的貼子,標志了Linu*方案的正式開場。在方案的早期有一些Mini*黑客提供了協助,而今天全球無數程序員正在為該方案無償提供幫助。技術上說Linu*是一個內核。"內核〞指的是一個提供硬件抽象層、磁盤及文件系統控制、多任務等功能的系統軟件。一個內核不是一套完整的操作系統。一套基于Linu*內核的完整操作系統叫作Linu*操作系統,或是GNU/Linu*。今天Linu*是一個一體化內核系統。設備驅動程序可以完全訪問硬件。Linu*內的設備驅動程序可以方便地以模塊的形式設置,并在系統運行期間可直接裝載或卸載。第1章Linu*設備驅動程序編寫方式1設備驅動程序的編寫模式Linu*下的設備驅動程序可以按照兩種方式進展編譯,一種是直接靜態編譯成內核的一局部,另一種則是編譯成可以動態加載的模塊。如果編譯進內核的話,會增加內核的大小,還要改動內核的源文件,而且不能動態地卸載,不利于調試,所有推薦使用模塊方式。從本質上來講,模塊也是內核的一局部,它不同于普通的應用程序,不能調用位于用戶態下的C或者C++庫函數,而只能調用Linu*內核提供的函數,在/proc/ksyms中可以查看到內核提供的所有函數。2module的原理module的出現是Linu*的一大革新。有了module之后,寫devicedriver不需要每次要測試driver就重新compilekernel一次。防止了很多的麻煩。Module允許我們動態的改變kernel,加載devicedriver,而且它也能縮短我們driverdevelopment的時間。module就是模塊。module其實是一般的程序。但是它可以被動態載到kernel里成為kernel的一局部。載到kernel里的module它具有跟kernel一樣的權力。可以access任何kernel的datastructure。第2章Linu*下的驅動設備類型正文格式1CharacterDevices正文格式字符設備,Linu*最簡單的設備,象文件一樣訪問。應用程序使用標準系統調用翻開、讀取、寫和關閉,完全好似這個設備是一個普通文件一樣。甚至連接一個Linu*系統上網的PPP守護進程使用的modem,也是這樣的。當字符設備初始化的時候,它的設備驅動程序向Linu*核心登記,在chrdevs向量表增加一個device_struct數據構造條目。這個設備的主設備標識符〔例如對于tty設備是4〕,用作這個向量表的索引。一個設備的主設備標識符是固定的。Chrdevs向量表中的每一個條目,一個device_struct數據構造,包括兩個元素:一個登記的設備驅動程序的名稱的指針和一個指向一組文件操作的指針。這塊文件操作本身位于這個設備的字符設備驅動程序中,每一個都處理特定的文件操作比方翻開、讀、寫和關閉。/proc/devices中字符設備的內容來自chrdevs向量表當代表一個字符設備〔例如/dev/cua0〕的字符特殊文件翻開,核心必須做一些事情,從而去掉用正確的字符設備驅動程序的文件操作例程。和普通文件或目錄一樣,每一個設備特殊文件都用VFSI節點表達。這個字符特殊文件的VFSinode〔實際上所有的設備特殊文件〕都包括設備的major和minor標識符。這個VFSI節點由底層的文件系統〔例如E*T2〕,在查找這個設備特殊文件的時候根據實際的文件系統創立。每一個VFSI節點都聯系著一組文件操作,依賴于I節點所代表的文件系統對象不同而不同。不管代表一個字符特殊文件的VFSI節點什么時候創立,它的文件操作被設置成字符設備的缺省操作。這只有一種文件操作:open操作。當一個應用程序翻開這個字符特殊文件的時候,通用的open文件操作使用設備的主設備標識符作為chrdevs向量表中的索引,取出這種特殊設備的文件操作塊。它也建立描述這個字符特殊文件的file數據構造,讓它的文件操作指向設備驅動程序中的操作。然后應用程序所有的文件系統操作都被映射到字符設備的文件操作。2BlockDevices塊設備也支持象文件一樣被訪問。這種為翻開的塊特殊文件提供正確的文件操作組的機制和字符設備的十分相似。Linu*用blkdevs向量表維護已經登記的塊設備文件。它象chrdevs向量表一樣,使用設備的主設備號作為索引。它的條目也是device_struct數據構造。和字符設備不同,塊設備進展分類。SCSI是其中一類,而IDE是另一類。類向Linu*核心登記并向核心提供文件操作。一種塊設備類的設備驅動程序向這種類提供和類相關的接口。例如,SCSI設備驅動程序必須向SCSI子系統提供接口,讓SCSI子系統用來對核心提供這種設備的文件操作。每一個塊設備驅動程序必須提供普通的文件操作接口和對于buffercache的接口。每一個塊設備驅動程序填充blk_dev向量表中它的blk_dev_struct數據構造。這個向量表的索引還是設備的主設備號。這個blk_dev_struct數據構造包括一個請求例程的地址和一個指針,指向一個request數據構造的列表,每一個都表達buffercache向設備讀寫一塊數據的一個請求。每一次buffercache希望讀寫一塊數據到或從一個登記的設備的時候它就在它的blk_dev_struc中增加一個request數據構造。圖8.2顯示了每一個request都有一個指針指向一個或多個buffer_head數據構造,每一個都是一個讀寫一塊數據的請求。這個buffer_head數據構造被鎖定〔buffercache〕,可能會有一個進程在等待這個緩沖區的阻塞進程完成。每一個request構造都是從一個靜態表,all_request表中分配的。如果這個request增加到一個空的request列表,就調用驅動程序的request函數處理這個request隊列。否則,驅動程序只是簡單地處理request隊列中的每一個請求。一旦設備驅動程序完成了一個請求,它必須把每一個buffer_head構造從request構造中刪除,標記它們為最新的,然后解鎖。對于buffer_head的解鎖會喚醒任何正在等待這個阻塞操作完成的進程。這樣的例子包括文件解析的時候:必須等待E*T2文件系統從包括這個文件系統的塊設備上讀取包括下一個E*T2目錄條目的數據塊,這個進程將會在將要包括目錄條目的buff_head隊列中睡眠,直到設備驅動程序喚醒它。這個request數據構造會被標記為空閑,可以被另一個塊請求使用。字符設備是以字節為單位逐個進展I/O操作的設備,在對字符設備發出讀寫請求時,實際的硬件I/O緊接著就發生了,一般來說字符設備中的緩存是可有可無的,而且也不支持隨機訪問。塊設備則是利用一塊系統內存作為緩沖區,當用戶進程對設備進展讀寫請求時,驅動程序先查看緩沖區中的內容,如果緩沖區中的數據能滿足用戶的要求就返回相應的數據,否則就調用相應的請求函數來進展實際的I/O操作。塊設備主要是針對磁盤等慢速設備設計的,其目的是防止消耗過多的CPU時間來等待操作的完成。一般說來,PCI卡通常都屬于字符設備。第2章Linu*設備驅動程序的框架正文格式1設備驅動程序的功能正文格式對設備初始化和釋放2把數據從內核傳送到硬件和從硬件讀取數據3讀取應用程序傳送給設備文件的數據和回送應用程序請求的數據1.4檢測和處理設備出現的錯誤.2設備驅動程序的組成局部2.1自動配置和初始化子程序自動配置和初始化子程序,負責檢測所要驅動的硬件設備是否存在和是否能正常工作。如果該設備正常,則對這個設備及其相關的、設備驅動程序需要的軟件狀態進展初始化。這局部驅動程序僅在初始化的時候被調用一次。2.2效勞于I/O請求的子程序效勞于I/O請求的子程序,又稱為驅動程序的上半局部。調用這局部是由于系統調用的結果。這局部程序在執行的時候,系統仍認為是和進展調用的進程屬于同一個進程,只是由用戶態變成了核心態,具有進展此系統調用的用戶程序的運行環境,因此可以在其中調用sleep()等與進程運行環境有關的函數。2.3中斷效勞子程序中斷效勞子程序,又稱為驅動程序的下半局部。在UNI*系統中,并不是直接從中斷向量表中調用設備驅動程序的中斷效勞子程序,而是由UNI*系統來接收硬件中斷,再由系統調用中斷效勞子程序。中斷可以產生在任何一個進程運行的時候,因此在中斷效勞程序被調用的時候,不能依賴于任何進程的狀態,也就不能調用任何與進程運行環境有關的函數。因為設備驅動程序一般支持同一類型的假設干設備,所以一般在系統調用中斷效勞子程序的時候,都帶有一個或多個參數,以唯一標識請求效勞的設備。在系統內部,I/O設備的存取通過一組固定的入口點來進展,這組入口點是由每個設備的設備驅動程序提供的。3設備驅動程序的構造3.1驅動程序注冊與注銷向系統增加一個驅動程序意味著要賦予它一個主設備號,這可以通過在驅動程序的初始化過程中調用register_chrdev()或者register_blkdev()來完成。而在關閉字符設備或者塊設備時,則需要通過調用unregister_chrdev()或unregister_blkdev()從內核中注銷設備,同時釋放占用的主設備號。3.2設備的翻開與釋放翻開設備是通過調用file_operations構造中的函數open()來完成的,它是驅動程序用來為今后的操作完成初始化準備工作的。在大局部驅動程序中,open()通常需要完成以下工作:檢查設備相關錯誤,如設備尚未準備好等。如果是第一次翻開,則初始化硬件設備。識別次設備號,如果有必要則更新讀寫操作的當前位置指針f_ops。分配和填寫要放在file->private_data里的數據構造。使用計數增1。釋放設備是通過調用file_operations構造中的函數release()來完成的,這個設備方法有時也被稱為close(),它的作用正好與open()相反,通常要完成以下工作:使用計數減1。釋放在file->private_data中分配的內存。如果使用計算為0,則關閉設備。3.3設備的讀寫操作字符設備的讀寫操作相比照擬簡單,直接使用函數read()和write()就可以了。但如果是塊設備的話,則需要調用函數block_read()和block_write()來進展數據讀寫,這兩個函數將向設備請求表中增加讀寫請求,以便Linu*內核可以對請求順序進展優化。由于是對內存緩沖區而不是直接對設備進展操作的,因此能很大程度上加快讀寫速度。如果內存緩沖區中沒有所要讀入的數據,或者需要執行寫操作將數據寫入設備,則就要執行真正的數據傳輸,這是通過調用數據構造blk_dev_struct中的函數request_fn()來完成的。3.4設備的控制操作除了讀寫操作外,應用程序有時還需要對設備進展控制,這可以通過設備驅動程序中的函數ioctl()來完成。ioctl()的用法與具體設備密切關聯,因此需要根據設備的實際情況進展具體分析。3.5設備的中斷和輪詢處理對于不支持中斷的硬件設備,讀寫時需要輪流查詢設備狀態,以便決定是否繼續進展數據傳輸。如果設備支持中斷,則可以按中斷方式進展操作。4設備驅動程序接口Linu*中的I/O子系統向內核中的其他局部提供了一個統一的標準設備接口,這是通過include/linu*/fs.h中的數據構造file_operations來完成的:structfile_operations{structmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar*,size_t,loff_t*);int(*readdir)(structfile*,void*,filldir_t);unsignedint(*poll)(structfile*,structpoll_table_struct*);int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*);int(*release)(structinode*,structfile*);int(*fsync)(structfile*,structdentry*,intdatasync);int(*fasync)(int,structfile*,int);int(*lock)(structfile*,int,structfile_lock*);ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);};當應用程序對設備文件進展諸如open、close、read、write等操作時,Linu*內核將通過file_operations構造訪問驅動程序提供的函數。例如,當應用程序對設備文件執行讀操作時,內核將調用file_operations構造中的read函數。第4章Linu*設備驅動程序的實現正文格式1PCI驅動程序實現的關鍵數據構造正文格式PCI設備上有三種地址空間:PCI的I/O空間、PCI的存儲空間和PCI的配置空間。CPU可以訪問PCI設備上的所有地址空間,其中I/O空間和存儲空間提供應設備驅動程序使用,而配置空間則由Linu*內核中的PCI初始化代碼使用。內核在啟動時負責對所有PCI設備進展初始化,配置好所有的PCI設備,包括中斷號以及I/O基址,并在文件/proc/pci中列出所有找到的PCI設備,以及這些設備的參數和屬性。Linu*驅動程序通常使用構造〔struct〕來表示一種設備,而構造體中的變量則代表*一具體設備,該變量存放了與該設備相關的所有信息。好的驅動程序都應該能驅動多個同種設備,每個設備之間用次設備號進展區分,如果采用構造數據來代表所有能由該驅動程序驅動的設備,則就可以簡單地使用數組下標來表示次設備號。1.1pci_driver構造這個數據構造在文件include/linu*/pci.h里,這是Linu*內核版本2.4之后為新型的PCI設備驅動程序所添加的,其中最主要的是用于識別設備的d_table構造,以及用于檢測設備的函數probe()和卸載設備的函數remove():structpci_driver{structlist_headnode;char*name;conststructpci_device_id*id_table;int(*probe)(structpci_dev*dev,conststructpci_device_id*id);void(*remove)(structpci_dev*dev);int(*save_state)(structpci_dev*dev,u32state);int(*suspend)(structpci_dev*dev,u32state);int(*resume)(structpci_dev*dev);int(*enable_wake)(structpci_dev*dev,u32state,intenable);};2pci_dev構造這個數據構造也在文件include/linu*/pci.h里,它詳細描述了一個PCI設備幾乎所有的硬件信息,包括廠商ID、設備ID、各種資源等:structpci_dev{structlist_headglobal_list;structlist_headbus_list;structpci_bus*bus;structpci_bus*subordinate;void*sysdata;structproc_dir_entry*procent;unsignedintdevfn;unsignedshortvendor;unsignedshortdevice;unsignedshortsubsystem_vendor;unsignedshortsubsystem_device;unsignedintclass;u8hdr_type;u8rom_base_reg;structpci_driver*driver;void*driver_data;u64dma_mask;u32current_state;unsignedshortvendor_compatible[DEVICE_COUNT_COMPATIBLE];unsignedshortdevice_compatible[DEVICE_COUNT_COMPATIBLE];unsignedintirq;structresourceresource[DEVICE_COUNT_RESOURCE];structresourcedma_resource[DEVICE_COUNT_DMA];structresourceirq_resource[DEVICE_COUNT_IRQ];charname[80];charslot_name[8];intactive;intro;unsignedshortregs;int(*prepare)(structpci_dev*dev);int(*activate)(structpci_dev*dev);int(*deactivate)(structpci_dev*dev);};2寫驅動程序的fuctoin問題2.1function的encode問題在寫C程序的時候,一個程序只能有一個main。Kernel本身其實也是一個程序,它本身也有個main,叫start_kernel()。當我們把一個module載到kernel里的時候,它會跟kernel整合在一起,成為kernel的一局部。module是不能使用main的,因為module是處于被動的角色,它提供*些功能讓別人去使用的。Kernel里有一個變量叫module_list,每當user將一個module載到kernel里的時候,這個module就會被記錄在module_list里面。當kernel要使用到這個module提供的function時,它就會去search這個list,找到module,然后再使用其提供的function或variable。每一個module都可以e*port一些function或變量來讓別人使用。除此之外,module也可以使用已經載到kernel里的module提供的function。這種情形叫做modulestack。比方說,moduleA用到moduleB的東西,那在加載moduleA之前必須要先加載moduleB。否則moduleA會無法加載。除了module會e*port東西之外,kernel本身也會e*port一些function或variable。同樣的,module也可以使用kernel所e*port出來的東西。module所使用的function或variable,要嘛就是自己寫在module里,要嘛就是別的module提供的,再不就是kernel所提供的。你不能使用一般libc或glibc所提供的function。像printf之類的東西。kernel本身會e*port出一些function或variable來讓module使用,Linu*提供一個command,叫ksyms,你只要執行ksyms-a就可以知道kernel或目前載到kernel里的module提供了那些function或variable。例如*系統的情形:c0216ba0drive_info_R744aa133c01e4a44boot_cpu_data_R660bd466c01e4ac0EISA_bus_R7413793ac01e4ac4MCA_bus_Rf48a2c4cc010cc34__verify_write_R203afbeb.....在kernel里,有一個symboltable是用來記錄e*port出去的function或variable。除此之外,也會記錄著那個modulee*port那些function。上面幾行中,表示kernel提供了drive_info這個function/variable。所以,我們可以在kernel里直接使用它,等載到kernel里時,會自動做好link的動作。由此,我們可以知道,module本身其實是還沒做link的一些objectcode。一切都要等到module被加載kernel之后,link才會完成。各位應該可以看到drive_info后面還接著一些奇怪的字符串。_R744aa133,這個字符串是根據目前kernel的版本再做些encode得出來的結果。為什幺額外需要這一個字符串呢"Linu*有一個config的選項,叫做Setversionnumberinsymbolsofmodule。這是為了防止對系統造成不穩定。我們知道Linu*的kernel更新的很快。在kernel更新的過程,有時為了效率起見,會對*些舊有的datastructure或function做些改變,而且一變可能有的variable被拿掉,有的function的prototype跟原來的都不太一樣。如果這種情形發生的時候,那可能以前2.0.33版本的module拿到2.2.1版本的kernel使用,假設原來module使用了2.0.33kernel提供的變量叫A,但是到了2.2.1由于*些原因必須把A都設成NULL。那當此module用在2.2.1kernel上時,如果它沒去檢查A的值就直接使用的話,就會造成系統的錯誤。也許不會整個系統都死掉,但是這個module肯定是很難發揮它的功能。為了這個原因,Linu*就在compilemodule時,把kernel版本的號碼encode到各個e*portedfunction和variable里。所以,剛剛也許我們不應該講kernel提供了drive_info,而應該說kernel提供了driver_info_R744aa133來讓我們使用。也就是說,kernel認為它提供的driver_info_R744aa133這個東西,而不是driver_info。所以,我們可以發現有的人在加載module時,系統都一直告訴你*個function無法resolved。這就是因為kernel里沒有你要的function,要不然就是你的module里使用的function跟kernelencode的結果不一樣。所以無法resolve。解決方式,要嘛就是將kernel里的setversion選項關掉,要嘛就是將modulecompile成kernel有方法承受的型式。如果kernel認定它提供的function名字叫做driver_info_R744aa133的話,那我們寫程序時,把用到這個funnction的地方都改成driver_info_R744aa133就可以了。為了防止煩瑣地對同一個functoin不動調用的地方的修改,linu*為我們提供了#defineprintkprintk_Rdd132261這樣的形式來解決這個問題。如果將系統的setversion的選項翻開的話,可以到/usr/src/linu*/include/linu*/modules這個目錄底下。這個目錄底下有所多的..ver檔案。這些檔案其實就是用來做#define用的。我們來看看ksyms.ver這個檔案里,里面有一行是這樣子的:#defineprintk_set_ver(printk)set_ver是一個macro,就是用來在printk后面加上versionnumber的。用了這些ver檔,我們就可以在module里直接使用printk這樣的名字了。而這些ver檔會自動幫我們做好#define的動作。可是,我們可以發現這個目錄有很多很多的ver檔。為了讓我們知我們要呼叫的function是在那個ver檔里Linu*又給我們提供了幫助。/usr/src/linu*/include/linu*/modversions.h這個檔案已經將全部的ver檔都加進來了。所以在我們的module里只要include這個檔,那名字的問題都解決了。但是,如果要將modversions.h這個檔在module里include進來,就要加上以下數行:#ifdefMODVERSIONS#include<linu*/modversions.h>#endif參加這三行的原因是,防止這個module在沒有設定kernelversion的系統上,將modversions.h這個檔案include進來。當你把setversion的選項關掉時,modversions.h和modules這個目錄都會不見。如果沒有上面三行,那compile就不會過關。所以一般來講,modversions.h我們會選擇在compile時傳給gcc使用。就像下面這個樣子。gcc-c-D__KERNEL__-DMODULE-DMODVERSIONSmain.c\-includeusr/src/linu*/include/linu*/modversions.h在這個commandline里,我們看到了-D__KERNEL__,這是說要定義__KERNEL__這個constant。很多跟kernel有關的headerfile,都必須要定義這個constant才能include的。所以最好將它定義起來。另外還有一個-DMODVERSIONS。要解決fucntion或variable名字encode的方式就是要includemodversions.h,其實除此之外,你還必須定義MODVERSIONS這個constant。再來就是MODULE這個constant。其實,只要是你要寫module就一定要定義這個變量。而且你還要includemodule.h這個檔案,因為_set_ver就是定義在這里的。2.2function的e*port問題如果我們自己的module想要e*port一些東西讓別的module使用,并且限定幾個必要的東西e*port出去。在kernel里提供了一個macro,叫做E*PORT_SYMBOL,這是用來幫我們選擇要e*port的variable或function。比方說,要e*port一個叫full的variable,就只需要在module里寫:E*PORT_SYMBOL(full);就會自動將fulle*port出去,馬上就可以從ksyms里發現有full這個變量被e*port出去。在使用E*PORT_SYMBOL之前,必須在gcc里定義E*PORT_SYMTAB這個constant,否則在compile時會發生parsererror。所以,要使用E*PORT_SYMBOL的話,那gcc應該要下:gcc-c-D__KERNEL__-DMODULE-DMODVERSIONS-DE*PORT_SYMTAB\main.c-include/usr/src/linu*/include/linu*/modversions.h如果不想e*port任何的東西,那只要在module里寫下E*PORT_NO_SYMBOLS;就可以了。使用E*PORT_NO_SYMBOLS用不著定義任何的constant。當使用E*PORT_SYMBOL把一些function或variablee*port出來之后,我們使用ksyma-a去看一些結果。我們發現E*PORT_SYMBOL(full)確實是把fulle*port出來了:c8822200full[my_module]c01b8e08pci_find_slot_R454463b5...如果在module的開頭。參加一行#definefullfull_R******之后,再重新compilemodule一次,載到kernel之后,就可以發現ksyms-a顯示的是c8822200full_R******[my_module]c01b8e08pci_find_slot_R454463b5.....了。Linu*里提供了一個command,叫genksyms,是用來產生.ver的檔案的。它會從stdin里讀取sourcecode,然后檢查sourcecode里是否有e*port的variable或function。如果有,它就會自動為每個e*port出來的東西產生一些define。假設我們的程序都放在一個叫main.c的檔案里,我們可以使用以下的方式產生這些define。gcc-E-D__GENKSYMS__main.c|genksyms-k2.2.1>main.vergcc的-E參數是指將preprocessing的結果show出來。也就是說將它include的檔案,一些define的結果都展開。用一個管線是因為genksyms是從stdin讀資料的,所以,經由管線將gcc的結果傳給genksyms。-k2.2.1是指目前使用的kernel版本是2.2.1,如果kernel版本不一樣,必須指定kernel的版本。產生的define將會被放到main.ver里。產生完main.ver檔之后,在main.c里將它include進來。使用這個方式產生的module,其e*port出來的東西會經由main.ver的define改頭換面。所以如果要讓別人使用,那就必須將main.ver公開。3兩個常用function要寫一個module,必須要提供兩個function。這兩個function是給insmod和rmmod使用的。它們分別是init_module(),以及cleanup_module()。intinit_module();voidcleanup_module();一般來講,我們在init_module()做的事都是一些初始化的工作。比方說,我們的的module需要一塊內存,那就可以在init_module()做kmalloc的動作。。cleanup_module()就是在module要移除的時候做的事,比方像把之前kmalloc的內存free掉。由于module是載到kernel使用的,所以,可能別的module會使用你的module,甚至*些process也會使用到你的module,為了防止module還有人使用時就被移除,每個module都有一個usecount。用來記錄目前有多少個process或module正在使用這個module。當module的usecount不等于0時,module是不會被移除掉的。也就是說,當module的usecount不等于0時,cleanup_module()是不會被呼叫的。以下的三個macro,是跟module的usecount有關系密切的。MOD_INC_USE_COUNTMOD_DEC_USE_COUNTMOD_IN_USEMOD_INC_USE_COUNT是用來增加module的usecount,而MOD_DEC_USE_COUNT是用來減少module的usecount。MOD_IN_USE則是用來檢查目前這個module是不是被使用中。也就是檢查usecount是否為0。module的usecount必須由寫module的人自己來maintain。系統并不會自動為你把usecount加一或減一,都可以自己來控制3設備驅動程序中的一些具體問題I/OPort和硬件打交道離不開I/OPort,老的ISA設備經常是占用實際的I/O端口,在linu*下,操作系統沒有對I/O口屏蔽,也就是說,任何驅動程序都可對任意的I/O口操作,這樣就很容易引起混亂。每個驅動程序應該自己防止誤用端口。有兩個重要的kernel函數可以保證驅動程序做到這一點。check_region(intio_port,intoff_set)這個函數觀察系統的I/O表,看是否有別的驅動程序占用*一段I/O口。參數1:io端口的基地址,參數2:io端口占用的范圍。返回值:0沒有占用,非0,已經被占用。request_region(intio_port,intoff_set,char*devname)如果這段I/O端口沒有被占用,在我們的驅動程序中就可以使用它。在使用之前,必須向系統登記,以防止被其他程序占用。登記后,在/proc/ioports文件中可以看到你登記的io口。參數1:io端口的基地址。參數2:io端口占用的范圍。參數3:使用這段io地址的設備名。在對I/O口登記后,就可以放心地用inb(),outb()之類的函來訪問了。3.2內存操作在設備驅動程序中動態開辟內存,不是用malloc,而是kmalloc,或者用get_free_pages直接申請頁。釋放內存用的是kfree,或free_pages.kmalloc等函數返回的是物理地址!而malloc等返回的是線性地址!kmalloc最大只能開辟128k-16,16個字節是被頁描述符構造占用了。內存映射的I/O口,存放器或者是硬件設備的RAM(如顯存)一般占用F0000000以上的地址空間。在驅動程序中不能直接訪問,要通過kernel函數vremap獲得重新映射以后的地址。另外,很多硬件需要一塊比擬大的連續內存用作DMA傳送。這塊內存需要一直駐留在內存,不能被交換到文件中去。但是kmalloc最多只能開辟128k的內存。這可以通過犧牲一些系統內存的方法來解決。具體做法是:比方說你的機器由32M的內存,在lilo.conf的啟動參數中加上mem=30M,這樣linu*就認為你的機器只有30M的內存,剩下的2M內存在vremap之后就可以為DMA所用了。用vremap映射后的內存,不用時應用unremap釋放,否則會浪費頁表。3.3中斷處理同處理I/O端口一樣,要使用一個中斷,必須先向系統登記。intrequest_irq(unsignedintirq,void(*handle)(int,void*,structpt_regs*),unsignedintlongflags,constchar*device);irq:是要申請的中斷。handle:中斷處理函數指針。flags:SA_INTERRUPT請求一個快速中斷,0正常中斷。device:設備名。如果登記成功,返回0,這時在/proc/interrupts文件中可以看你請求的中斷。4RealtekFastEthernetDriverrtl8139網卡驅動3個模塊的改寫1設備指明模塊/*設備指明模塊*/staticstructpci_id_infopci_tbl[]={ {"RealTekRTL8129FastEthernet",{0*812910ec,0*ffffffff,}, RTL8139_IOTYPE,0*80,RTL8129_CAPS,}, {"RealTekRTL8139FastEthernet",{0*813910ec,0*ffffffff,}, RTL8139_IOTYPE,0*80,RTL8139_CAPS,}, {"RealTekRTL8139BPCI/CardBus",{0*813810ec,0*ffffffff,}, RTL8139_IOTYPE,0*80,RTL8139_CAPS,}, {"SMC1211T*EZCard10/100(RealTekRTL8139)",{0*12111113,0*ffffffff,}, RTL8139_IOTYPE,0*80,RTL8139_CAPS,}, {"AcctonMP*5030(RealTekRTL8139)",{0*12111113,0*ffffffff,}, RTL8139_IOTYPE,0*80,RTL8139_CAPS,}, {0,}, /*0terminatedlist.*/};2數據讀寫和控制信息模塊PCI設備驅動程序可以通過demo_fops構造中的函數demo_ioctl(),向應用程序提供對硬件進展控制的接口。例如,通過它可以從I/O存放器里讀取一個數據,并傳送到用戶空間里:/*數據讀寫和控制信息模塊*/staticintmii_ioctl(structnet_device*dev,structifreq*rq,intcmd){ structrtl8129_private*tp=(structrtl8129_private*)dev->priv; u16*data=(u16*)&rq->ifr_data; switch(cmd){ caseSIOCDEVPRIVATE: /*GettheaddressofthePHYinuse.*/ data[0]=tp->phys[0]&0*3f; /*FallThrough*/ caseSIOCDEVPRIVATE+1: /*ReadthespecifiedMIIregister.*/ data[3]=mdio_read(dev,data[0],data[1]&0*1f); return0; caseSIOCDEVPRIVATE+2: /*WritethespecifiedMIIregister*/ if(!capable(CAP_NET_ADMIN)) return-EPERM; mdio_write(dev,data[0],data[1]&0*1f,data[2]); return0; default: return-EOPNOTSUPP; }}3中斷處理模塊PC的中斷資源比擬有限,只有0~15的中斷號,因此大局部外部設備都是以共享的形式申請中斷號的。當中斷發生的時候,中斷處理程序首先負責對中斷進展識別,然后再做進一步的處理。/*中斷處理模塊*/staticvoidrtl8139CP_interrupt(intirq,void*dev_instance,structpt_regs*regs){ structnet_device*dev=(structnet_device*)dev_instance; structrtl8129_private*tp=(structrtl8129_private*)dev->priv; intboguscnt=ma*_interrupt_work; longioaddr=dev->base_addr; intlink_changed=0;#ifdefined(__i386__) /*AlocktopreventsimultaneousentrybugonIntelSMPmachines.*/ if(test_and_set_bit(0,(void*)&dev->interrupt)){ printk(KERN_ERR"%s:SMPsimultaneousentryofaninterrupthandler.\n", dev->name); dev->interrupt=0; /*Avoidhaltingmachine.*/ return; }#else if(dev->interrupt){ printk(KERN_ERR"%s:Re-enteringtheinterrupthandler.\n",dev->name); return; } dev->interrupt=1;#endif do{ intstatus=inw(ioaddr+IntrStatus); /*AcknowledgeallofthecurrentinterruptsourcesASAP,but anfirstgetanadditionalstatusbitfromCSCR.*/if((status&R*Underrun)&&inw(ioaddr+CSCR)&CSCR_LinkChangeBit){link_changed=inw(ioaddr+CSCR)&CSCR_LinkChangeBit; /*IflinkOK,disableLinkDownPowerSavingmode. IflinkOFF,enableit.ThismodeisavaiableonlyforRTL8139C*/if((inb(ioaddr+T*Config+3)&0*7C)==0*74)//0*74isRTL8139CHWVer.{if((inb(ioaddr+MediaStatus)&LINK_Status)==0)outb(inb(ioaddr+Config5)|0*4,ioaddr+Config5);elseoutb(inb(ioaddr+Config5)&0*B,ioaddr+Config5);}} if(status&(R*FIFOOver|R*Overflow)) outw(R*FIFOOver&R*Overflow,ioaddr+IntrStatus); if(status&T*Err) outl(0*01,ioaddr+T*Config); outw(status,ioaddr+IntrStatus); if(debug>4) printk(KERN_DEBUG"%s:interruptstatus=%#4.4*newintstat=%#4.4*.\n", dev->name,status,inw(ioaddr+IntrStatus)); if((status&(PCIErr|PCSTimeout|R*Underrun|R*Overflow|R*FIFOOver|T*Err|T*OK|R*Err|R*OK))==0) break; if(status&(R*OK|R*Underrun|R*Overflow|R*FIFOOver))/*R*interrupt*/ rtl8139cp_r*(dev); if(status&(T*Err|T*OK)){ unsignedintdirty_t*=tp->dirty_t*; while(tp->cur_t*-dirty_t*>0){ intentry=dirty_t*%NUM_CP_T*_DESC; /*Freetheoriginalskb.*/ dev_free_skb(tp->t*_skbuff[entry]); tp->t*_skbuff[entry]=0; if(test_bit(0,&tp->t*_full)){ /*Theringisnolongerfull,cleartbusy.*/ clear_bit(0,&tp->t*_full); clear_bit(0,(void*)&dev->tbusy); netif_wake_queue(dev); } dirty_t*++; tp->stats.t*_packets++; }#ifndeffinal_version if(tp->cur_t*-dirty_t*>NUM_CP_T*_DESC){ printk(KERN_ERR"%s:Out-of-syncdirtypointer,%dvs.%d,full=%d.\n", dev->name,dirty_t*,tp->cur_t*,(int)tp->t*_full);
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 農業種植技術發展進度記錄表
- 電子商務領域創業投資證明(5篇)
- 小動物們的新年派對童話作文(4篇)
- 食品行業分類表(包含各類食品企業、品牌等)
- 小學英語文化教學的創新方法
- 《修辭立其誠:高中語文作文技巧教學教案》
- 兒童心理發展與課堂管理
- DB14-T 3374-2025 黃桃栽培技術規程
- 電子發票管理統計表
- 詩仙李白的詩意人生:古詩文人物背景課教案
- 精品解析浙江省溫州市蒼南縣2021年小學科學六年級畢業考試試卷
- 鋪麻醉床技術操作評分標準
- 管道焊接及焊縫外觀檢查記錄表
- GB∕T 24508-2020 木塑地板-行業標準
- 回油管夾片的沖壓工藝與模具設計
- 個體化健康教育
- 《白內障》ppt課件
- Resume(簡歷英文版)
- 報價單模板(中英文
- 股骨頸骨折中醫診療方案
- 房產證英文翻譯件模板
評論
0/150
提交評論