1樓:苗子蜂情
如何編寫linux裝置驅動程式回想學習linux作業系統已經有近一年的時間了,前前後後,零零碎碎的一路學習過來,也該試著寫的東西了。也算是給自己能留下一點記憶和回憶吧!由於完全是自學的,以下內容若有不當之處,還請大家多指教。
linux是unix作業系統的一種變種,在linux下編寫驅動程式的原理和思想完全類似於其他的unix系統,但它dos或window環境下的驅動程式有很大的區別。在linux環境下設計驅動程式,思想簡潔,操作方便,功能也很強大,但是支援函式少,只能依賴kernel中的函式,有些常用的操作要自己來編寫,而且除錯也不方便。以下的一些文字主要**於khg,johnsonm的writelinuxdevicedriver,brennan'sguidetoinlineassembly,thelinuxa-z,還有清華bbs上的有關devicedriver的一些資料。
一、linuxdevicedriver的概念 系統呼叫是作業系統核心和應用程式之間的介面,裝置驅動程式是作業系統核心和機器硬體之間的介面。裝置驅動程式為應用程式遮蔽了硬體的細節,這樣在應用程式看來,硬體裝置只是一個裝置檔案,應用程式可以象操作普通檔案一樣對硬體裝置進行操作。裝置驅動程式是核心的一部分,它完成以下的功能:
1、對裝置初始化和釋放。 2、把資料從核心傳送到硬體和從硬體讀取資料。 3、讀取應用程式傳送給裝置檔案的資料和回送應用程式請求的資料。
4、檢測和處理裝置出現的錯誤。 在linux作業系統下有三類主要的裝置檔案型別,一是字元裝置,二是塊裝置,三是網路裝置。字元裝置和塊裝置的主要區別是:
在對字元裝置發出讀/寫請求時,實際的硬體i/o一般就緊接著發生了,塊裝置則不然,它利用一塊系統記憶體作緩衝區,當使用者程序對裝置請求能滿足使用者的要求,就返回請求的資料,如果不能,就呼叫請求函式來進行實際的i/o操作。塊裝置是主要針對磁碟等慢速裝置設計的,以免耗費過多的cpu時間來等待。 已經提到,使用者程序是通過裝置檔案來與實際的硬體打交道。
每個裝置檔案都都有其檔案屬性(c/b),表示是字元裝置還是塊裝置?另外每個檔案都有兩個裝置號,第一個是主裝置號,標識驅動程式,第二個是從裝置號,標識使用同一個裝置驅動程式的不同的硬體裝置,比如有兩個軟盤,就可以用從裝置號來區分他們。裝置檔案的的主裝置號必須與裝置驅動程式在登記時申請的主裝置號一致,否則使用者程序將無法訪問到驅動程式。
最後必須提到的是,在使用者程序呼叫驅動程式時,系統進入核心態,這時不再是搶先式排程。也就是說,系統必須在你的驅動程式的子函式返回後才能進行其他的工作。如果你的驅動程式陷入死迴圈,不幸的是你只有重新啟動機器了,然後就是漫長的fsck。
讀/寫時,它首先察看緩衝區的內容,如果緩衝區的資料未被處理,則先處理其中的內容。 如何編寫linux作業系統下的裝置驅動程式 二、例項剖析我們來寫一個最簡單的字元裝置驅動程式。雖然它什麼也不做,但是通過它可以瞭解linux的裝置驅動程式的工作原理。
把下面的c**輸入機器,你就會獲得一個真正的裝置驅動程式。#define__no_version__#include#include charkernel_version=uts_release; 這一段定義了一些版本資訊,雖然用處不是很大,但也必不可少。johnsonm說所有的驅動程式的開頭都要包含,一般來講最好使用。
由於使用者程序是通過裝置檔案同硬體打交道,對裝置檔案的操作方式不外乎就是一些系統呼叫,如open,read,write,close…,注意,不是fopen,fread,但是如何把系統呼叫和驅動程式關聯起來呢?這需要了解一個非常關鍵的資料結構:structfile_operations 這個結構的每一個成員的名字都對應著一個系統呼叫。
使用者程序利用系統呼叫在對裝置檔案進行諸如read/write操作時,系統呼叫通過裝置檔案的主裝置號找到相應的裝置驅動程式,然後讀取這個資料結構相應的函式指標,接著把控制權交給該函式。這是linux的裝置驅動程式工作的基本原理。既然是這樣,則編寫裝置驅動程式的主要工作就是編寫子函式,並填充file_operations的各個域。
下面就開始寫子程式。#include#include#include#include#include#includeunsignedinttest_major=0;staticintread_test(structinode*node,structfile*file,char*buf,intcount)returncount;}這個函式是為read呼叫準備的。當呼叫read時,read_test()被呼叫,它把使用者的緩衝區全部寫1。
buf是read呼叫的一個引數。它是使用者程序空間的一個地址。但是在read_test被呼叫時,系統進入核心態。
所以不能使用buf這個地址,必須用__put_user(),這是kernel提供的一個函式,用於向使用者傳送資料。另外還有很多類似功能的函式。請參考robert著的《linux核心設計與實現》(第二版)。
然而,在向使用者空間拷貝資料之前,必須驗證buf是否可用。這就用到函式verify_area。staticintwrite_tibet(structinode*inode,structfile*file,constchar*buf,intcount)staticintopen_tibet(structinode*inode,structfile*file)staticvoidrelease_tibet(structinode*inode,structfile*file) 這幾個函式都是空操作。
實際呼叫發生時什麼也不做,他們僅僅為下面的結構提供函式指標。structfile_operationstest_fops=; 這樣,裝置驅動程式的主體可以說是寫好了。現在要把驅動程式嵌入核心。
驅動程式可以按照兩種方式編譯。一種是編譯進kernel,另一種是編譯成模組(modules),如果編譯進核心的話,會增加核心的大小,還要改動核心的原始檔,而且不能動態的解除安裝,不利於除錯,所以推薦使用模組方式。intinit_module(void)read(testdev,buf,10);for(i=0;i<10;i++)printf("%d\n",buf[i]);close(testdev);} 編譯執行,看看是不是列印出全1?
以上只是一個簡單的演示。真正實用的驅動程式要複雜的多,要處理如中斷,dma,i/oport等問題。這些才是真正的難點。
請看下節,實際情況的處理。如何編寫linux作業系統下的裝置驅動程式 三、裝置驅動程式中的一些具體問題 1。i/oport。
和硬體打交道離不開i/oport,老的isa裝置經常是佔用實際的i/o埠,在linux下,作業系統沒有對i/o口遮蔽,也就是說,任何驅動程式都可對任意的i/o口操作,這樣就很容易引起混亂。每個驅動程式應該自己避免誤用埠。 有兩個重要的kernel函式可以保證驅動程式做到這一點。
1)check_region(intio_port,intoff_set) 這個函式察看系統的i/o表,看是否有別的驅動程式佔用某一段i/o口。 引數1:i/o埠的基地址, 引數2:
i/o埠占用的範圍。 返回值:0沒有佔用,非0,已經被佔用。
2)request_region(intio_port,intoff_set,char*devname) 如果這段i/o埠沒有被佔用,在我們的驅動程式中就可以使用它。在使用之前,必須向系統登記,以防止被其他程式佔用。登記後,在/proc/ioports檔案中可以看到你登記的i/o口。
引數1:io埠的基地址。 引數2:
io埠占用的範圍。 引數3:使用這段io地址的裝置名。
在對i/o口登記後,就可以放心地用inb(),outb()之類的函來訪問了。在一些pci裝置中,i/o埠被對映到一段記憶體中去,要訪問這些埠就相當於訪問一段記憶體。經常性的,我們要獲得一塊記憶體的實體地址。
2。記憶體操作 在裝置驅動程式中動態開闢記憶體,不是用malloc,而是kmalloc,或者用get_free_pages直接申請頁。釋放記憶體用的是kfree,或free_pages。
請注意,kmalloc等函式返回的是實體地址! 注意,kmalloc最大隻能開闢128k-16,16個位元組是被頁描述符結構佔用了。 記憶體對映的i/o口,暫存器或者是硬體裝置的ram(如視訊記憶體)一般佔用f0000000以上的地址空間。
在驅動程式中不能直接訪問,要通過kernel函式vremap獲得重新對映以後的地址。 另外,很多硬體需要一塊比較大的連續記憶體用作dma傳送。這塊程式需要一直駐留在記憶體,不能被交換到檔案中去。
但是kmalloc最多隻能開闢128k的記憶體。 這可以通過犧牲一些系統記憶體的方法來解決。 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檔案中可以看你請求的中斷。 4。一些常見的問題。
對硬體操作,有時時序很重要(關於時序的具體問題就要參考具體的裝置晶片手冊啦!比如網絡卡晶片rtl8139)。但是如果用c語言寫一些低階的硬體操作的話,gcc往往會對你的程式進行優化,這樣時序會發生錯誤。
如果用匯編寫呢,gcc同樣會對彙編**進行優化,除非用volatile關鍵字修飾。最保險的法是禁止優化。這當然只能對一部分你自己編寫的**。
如果對所有的**都不優化,你會發現驅動程式根本無法裝載。這是因為在編譯驅動程式時要用到gcc的一些擴充套件特性,而這些擴充套件特性必須在加了優化選項之後才能體現出來。寫在後面:
學習linux確實不是一件容易的事情,因為要付出很多精力,也必須具備很好的c語言基礎;但是,學習linux也是一件非常有趣的事情,它裡面包含了許多高手的智慧和“幽默”,這些都需要自己親自動手才能體會到,o(∩_∩)o~哈哈!
linu提示符gt怎樣退出,linux 提示符 怎樣退出?
linux 提示符 退出的方法。如下參考 1.telnet測試一般使用的是telnetip 主機 埠號,測試。2.正常輸入telnet命令列介面複製,只需輸入退出即可退出。3.telnet埠如果被阻塞將直接退出。但如果成功了,它就在這裡。按回車鍵返回。然後鍵入quit命令。4.如果遇到半天不知道的響...
請教linux安裝硬碟分割槽
簡單點就直接一個根分割槽,你記憶體夠大,不需要swap交換分割槽了 如果想在細分的話,建議把boot分割槽單獨劃出來不需要多大102m作用就夠了,然後是根分割槽root,接著劃1g作用的交換分割槽,你記憶體已經足夠大了,劃交換分割槽的話,建議最大1g 在細分的劃需要考慮的你以後的具體應用了。你不是裝...
請教linu下是什麼意思,請教linux下 是什麼意思?
1.如果使用 執行,可以理解為程式執行在一個全新的shell中,不繼承當前shell的環境內 變數的值,同時若容在程式中改變了當前shell中的環境變數 不使用export 則當前shell的環境變數值不變。2.如果使用 執行,則程式繼承當前shell中的環境變數,同時,若在程式中改變了當前shel...