如何編寫Linux裝置驅動程式

2022-09-01 12:18:10 字數 4637 閱讀 9006

感謝  @上海一九四三啊    (李大屌)

reference :

序言linux是unix作業系統的一種變種,在linux下編寫驅動程式的原理和思想完全類似於其他的unix系統,但它dos或window環境下的驅動程式有很大的區別。在linux環境下設計驅動程式,思想簡潔,操作方便,功能也很強大,但是支援函式少,只能依賴kernel中的函式,有些常用的操作要自己來編寫,而且除錯也不方便。本人這幾周來為實驗室自行研製的一塊多**卡編制了驅動程式,獲得了一些經驗,願與linux fans共享,有不當之處,請予指正。

以下的一些文字主要**於khg,johnsonm的write linux device driver,brennan』s guide to inline assembly,the linux a-z,還有清華bbs上的有關device driver的一些資料。 這些資料有的已經過時,有的還有一些錯誤,我依據自己的試驗結果進行了修正。

一、linux device driver 的概念

系統呼叫是作業系統核心和應用程式之間的介面,裝置驅動程式是作業系統核心和機器硬體之間的介面。裝置驅動程式為應用程式遮蔽了硬體的細節,這樣在應用程式看來,硬體裝置只是乙個裝置檔案, 應用程式可以象操作普通檔案一樣對硬體裝置進行操作。裝置驅動程式是核心的一部分,它完成以下的功能:

1.對裝置初始化和釋放。

2.把資料從核心傳送到硬體和從硬體讀取資料。

3.讀取應用程式傳送給裝置檔案的資料和回送應用程式請求的資料。

4.檢測和處理裝置出現的錯誤。

在linux作業系統下有兩類主要的裝置檔案型別,一種是字元裝置,另一種是塊裝置。字元裝置和塊裝置的主要區別是:在對字元裝置發出讀/寫請求時,實際的硬體i/o一般就緊接著發生了,塊裝置則不然,它利用一塊系統記憶體作緩衝區,當使用者程序對裝置請求能滿足使用者的要求,就返回請求的資料,如果不能,就呼叫請求函式來進行實際的i/o操作。塊裝置是主要針對磁碟等慢速裝置設計的,以免耗費過多的cpu時間來等待。

已經提到,使用者程序是通過裝置檔案來與實際的硬體打交道。每個裝置檔案都都有其檔案屬性(c/b),表示是字元裝置還蔤強檣璞?另外每個檔案都有兩個裝置號,第乙個是主裝置號,標識驅動程式,第二個是從裝置號,標識使用同乙個裝置驅動程式的不同的硬體裝置,比如有兩個軟盤,就可以用從裝置號來區分他們。裝置檔案的的主裝置號必須與裝置驅動程式在登記時申請的主裝置號一致,否則使用者程序將無法訪問到驅動程式。

最後必須提到的是,在使用者程序呼叫驅動程式時,系統進入核心態,這時不再是搶先式排程。也就是說,系統必須在你的驅動程式的子函式返回後才能進行其他的工作。如果你的驅動程式陷入死迴圈,不幸的是你只有重新啟動機器了,然後就是漫長的fsck.//hehe

讀/寫時,它首先察看緩衝區的內容,如果緩衝區的資料

如何編寫linux作業系統下的裝置驅動程式

二、例項剖析

我們來寫乙個最簡單的字元裝置驅動程式。雖然它什麼也不做,但是通過它可以了解linux的裝置驅動程式的工作原理。把下面的c**輸入機器,你就會獲得乙個真正的裝置驅動程式。不過我的kernel是2.0.34,在低版本的kernel上可能會出現問題,我還沒測試過。//xixi

#define __no_version__

#include

#include

char kernel_version = uts_release;

這一段定義了一些版本資訊,雖然用處不是很大,但也必不可少。johnsonm說所有的驅動程式的開頭都要包含,但我看倒是未必。 由於使用者程序是通過裝置檔案同硬體打交道,對裝置檔案的操作方式不外乎就是一些系統呼叫,如 open,read,write,close…, 注意,不是fopen, fread,但是如何把系統呼叫和驅動程式關聯起來呢?這需要了解乙個非常關鍵的資料結構:

struct file_operations "

就可以獲得主裝置號,可以把上面的命令列加入你的shell script中去。

minor是從裝置號,設定成0就可以了。

我們現在可以通過裝置檔案來訪問我們的驅動程式。寫乙個小小的測試程式。

#include

#include

#include

#include

main()

int testdev;

int i;

char buf[10];

testdev = open("/dev/test",o_rdwr);

if ( testdev == -1 )

printf("cannt open file \n");

exit(0);

read(testdev,buf,10);

for (i = 0; i < 10;i++)

printf("%d\n",buf[i]);

close(testdev);

編譯執行,看看是不是列印出全1 ?

以上只是乙個簡單的演示。真正實用的驅動程式要複雜的多,要處理如中斷,dma,i/o port等問題。這些才是真正的難點。請看下節,實際情況的處理。

如何編寫linux作業系統下的裝置驅動程式 三、裝置驅動程式中的一些具體問題

1. i/o port.

和硬體打交道離不開i/o port,老的isa裝置經常是占用實際的i/o埠,在linux下,作業系統沒有對i/o口遮蔽,也就是說,任何驅動程式都可對任意的i/o口操作,這樣就很容易引起混亂。每個驅動程式應該自己避免誤用埠。

有兩個重要的kernel函式可以保證驅動程式做到這一點。

1)check_region(int io_port, int off_set)

這個函式察看系統的i/o表,看是否有別的驅動程式占用某一段i/o口。

引數2:io埠占用的範圍。

返回值:0 沒有占用, 非0,已經被占用。

2)request_region(int io_port, int off_set,char *devname)

如果這段i/o埠沒有被占用,在我們的驅動程式中就可以使用它。在使用之前,必須向系統登記,以防止被其他程式占用。登記後,在/proc/ioports檔案中可以看到你登記的io口。

引數2:io埠占用的範圍。

在對i/o口登記後,就可以放心地用inb(), outb()之類的函來訪問了。

在一些pci裝置中,i/o埠被對映到一段記憶體中去,要訪問這些埠就相當於訪問一段記憶體。經常性的,我們要獲得一塊記憶體的實體地址。在dos環境下,(之所以不說是dos作業系統是因為我認為dos根本就不是乙個作業系統,它實在是太簡單,太不安全了)只要用段:偏移就可以了。在window95中,95ddk提供了乙個vmm 呼叫 _maplineartophys,用以把線性位址轉化為實體地址。但在linux中是怎樣做的呢?

2.記憶體操作

在裝置驅動程式中動態開闢記憶體,不是用malloc,而是kmalloc,或者用get_free_pages直接申請頁。釋放記憶體用的是kfree,或free_pages. 請注意,kmalloc等函式返回的是實體地址!而malloc等返回的是線性位址!關於kmalloc返回的是實體地址這一點本人有點不太明白:既然從線性位址到實體地址的轉換是由386cpu硬體完成的,那樣彙編指令的運算元應該是線性位址,驅動程式同樣也不能直接使用實體地址而是線性位址。但是事實上kmalloc返回的確實是實體地址,而且也可以直接通過它訪問實際的ram,我想這樣可以由兩種解釋,一種是在核心態禁止分頁,但是這好像不太現實;另一種是linux的頁目錄和頁表項設計得正好使得實體地址等同於線性位址。我的想法不知對不對,還請高手指教。

言歸正傳,要注意kmalloc最大只能開闢128k-16,16個位元組是被頁描述符結構占用了。kmalloc用法參見khg.

記憶體對映的i/o口,暫存器或者是硬體裝置的ram(如視訊記憶體)一般占用f0000000以上的位址空間。在驅動程式中不能直接訪問,要通過kernel函式vremap獲得重新對映以後的位址。

另外,很多硬體需要一塊比較大的連續記憶體用作dma傳送。這塊記憶體需要一直駐留在記憶體,不能被交換到檔案中去。但是kmalloc最多只能開闢128k的記憶體。

這可以通過犧牲一些系統記憶體的方法來解決。

具體做法是:比如說你的機器由32m的記憶體,在lilo.conf的啟動引數中加上mem=30m,這樣linux就認為你的機器只有30m的記憶體,剩下的2m內存在vremap之後就可以為dma所用了。

請記住,用vremap對映後的記憶體,不用時應用unremap釋放,否則會浪費頁表。

3.中斷處理

同處理i/o埠一樣,要使用乙個中斷,必須先向系統登記。

int request_irq(unsigned int irq ,

void(*handle)(int,void *,struct pt_regs *),

unsigned int long flags,

const char *device);

irq: 是要申請的中斷。

handle:中斷處理函式指標。

flags:sa_interrupt 請求乙個快速中斷,0 正常中斷。

device:裝置名。

如果登記成功,返回0,這時在/proc/interrupts檔案中可以看你請求的中斷。

4.一些常見的問題。

對硬體操作,有時時序很重要。但是如果用c語言寫一些低階的硬體操作的話,gcc往往會對你的程式進行優化,這樣時序就錯掉了。如果用彙編寫呢,gcc同樣會對彙編**進行優化,除非你用volatile關鍵字修飾。最保險的辦法是禁止優化。這當然只能對一部分你自己編寫的**。如果對所有的**都不優化,你會發現驅動程式根本無法裝載。這是因為在編譯驅動程式時要用到gcc的一些擴充套件特性,而這些擴充套件特性必須在加了優化選項之後才能體現出來。

編寫Linux裝置驅動程式教程

序言 linux是unix作業系統的一種變種,在linux下編寫驅動程式的原理和思想完全類似於其他的unix系統,但它dos或window環境下的驅動程式有很大的區別。在linux環境下設計驅動程式,思想簡潔,操作方便,功能也很強大,但是支援函式少,只能依賴kernel中的函式,有些常用的操作要自己...

linux裝置驅動程式 字元裝置驅動程式

先留個 有一起學習驅動程式的加qq295699450 字元裝置驅動 這篇比較惱火。載入成功,但是讀不出來資料,有知道怎麼回事的,留個言,一起討論下 資料結構 struct scull mem struct scull dev dev 整個驅動程式 如下 include include include...

Linux裝置驅動程式 字元裝置驅動程式

1.檢視主裝置號,次裝置號 進入 dev目錄執行ls l,第四,五列分別為主次裝置號,10,180,1,5,這些是主裝置號,而60,63這些就是次裝置號 130 shell android dev ls l crw rw r system radio 10,60 1969 12 31 21 00 a...