?? linux系統(tǒng)下的設(shè)備驅(qū)動程序.txt
字號:
int result;
result = register_chrdev(0, "test", &test_fops);
if (result < 0) {
printk(KERN_INFO "test: can't get major number ");
return result;
}
if (test_major == 0) test_major = result; /* dynamic */
return 0;
}
在用insmod命令將編譯好的模塊調(diào)入內(nèi)存時(shí),init_module 函數(shù)被調(diào)用。在
這里,init_module只做了一件事,就是向系統(tǒng)的字符設(shè)備表登記了一個(gè)字符
設(shè)備。register_chrdev需要三個(gè)參數(shù),參數(shù)一是希望獲得的設(shè)備號,如果是
零的話,系統(tǒng)將選擇一個(gè)沒有被占用的設(shè)備號返回。參數(shù)二是設(shè)備文件名,
參數(shù)三用來登記驅(qū)動程序?qū)嶋H執(zhí)行操作的函數(shù)的指針。
如果登記成功,返回設(shè)備的主設(shè)備號,不成功,返回一個(gè)負(fù)值。
void cleanup_module(void)
{
unregister_chrdev(test_major, "test");
}
在用rmmod卸載模塊時(shí),cleanup_module函數(shù)被調(diào)用,它釋放字符設(shè)備test
在系統(tǒng)字符設(shè)備表中占有的表項(xiàng)。
一個(gè)極其簡單的字符設(shè)備可以說寫好了,文件名就叫test.c吧。
下面編譯
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c
得到文件test.o就是一個(gè)設(shè)備驅(qū)動程序。
如果設(shè)備驅(qū)動程序有多個(gè)文件,把每個(gè)文件按上面的命令行編譯,然后
ld -r file1.o file2.o -o modulename.
驅(qū)動程序已經(jīng)編譯好了,現(xiàn)在把它安裝到系統(tǒng)中去。
$ insmod -f test.o
如果安裝成功,在/proc/devices文件中就可以看到設(shè)備test,
并可以看到它的主設(shè)備號,。
要卸載的話,運(yùn)行
$ rmmod test
下一步要?jiǎng)?chuàng)建設(shè)備文件。
mknod /dev/test c major minor
c 是指字符設(shè)備,major是主設(shè)備號,就是在/proc/devices里看到的。
用shell命令
$ cat /proc/devices | awk "\$2=="test" {print \$1}"
就可以獲得主設(shè)備號,可以把上面的命令行加入你的shell script中去。
minor是從設(shè)備號,設(shè)置成0就可以了。
我們現(xiàn)在可以通過設(shè)備文件來訪問我們的驅(qū)動程序。寫一個(gè)小小的測試程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file ");
exit(0);
}
read(testdev,buf,10);
for (i = 0; i < 10;i++)
printf("%d ",buf[i]);
close(testdev);
}
編譯運(yùn)行,看看是不是打印出全1 ?
以上只是一個(gè)簡單的演示。真正實(shí)用的驅(qū)動程序要復(fù)雜的多,要處理如中斷,
DMA,I/O port等問題。這些才是真正的難點(diǎn)。請看下節(jié),實(shí)際情況的處理。
如何編寫Linux操作系統(tǒng)下的設(shè)備驅(qū)動程序
Roy G
三 設(shè)備驅(qū)動程序中的一些具體問題。
1. I/O Port.
和硬件打交道離不開I/O Port,老的ISA設(shè)備經(jīng)常是占用實(shí)際的I/O端口,
在linux下,操作系統(tǒng)沒有對I/O口屏蔽,也就是說,任何驅(qū)動程序都可以
對任意的I/O口操作,這樣就很容易引起混亂。每個(gè)驅(qū)動程序應(yīng)該自己避免
誤用端口。
有兩個(gè)重要的kernel函數(shù)可以保證驅(qū)動程序做到這一點(diǎn)。
1)check_region(int io_port, int off_set)
這個(gè)函數(shù)察看系統(tǒng)的I/O表,看是否有別的驅(qū)動程序占用某一段I/O口。
參數(shù)1:io端口的基地址,
參數(shù)2:io端口占用的范圍。
返回值:0 沒有占用, 非0,已經(jīng)被占用。
2)request_region(int io_port, int off_set,char *devname)
如果這段I/O端口沒有被占用,在我們的驅(qū)動程序中就可以使用它。在使用
之前,必須向系統(tǒng)登記,以防止被其他程序占用。登記后,在/proc/ioports
文件中可以看到你登記的io口。
參數(shù)1:io端口的基地址。
參數(shù)2:io端口占用的范圍。
參數(shù)3:使用這段io地址的設(shè)備名。
在對I/O口登記后,就可以放心地用inb(), outb()之類的函來訪問了。
在一些pci設(shè)備中,I/O端口被映射到一段內(nèi)存中去,要訪問這些端口就相當(dāng)
于訪問一段內(nèi)存。經(jīng)常性的,我們要獲得一塊內(nèi)存的物理地址。在dos環(huán)境下,
(之所以不說是dos操作系統(tǒng)是因?yàn)槲艺J(rèn)為DOS根本就不是一個(gè)操作系統(tǒng),它實(shí)
在是太簡單,太不安全了)只要用段:偏移就可以了。在window95中,95ddk
提供了一個(gè)vmm 調(diào)用 _MapLinearToPhys,用以把線性地址轉(zhuǎn)化為物理地址。但
在Linux中是怎樣做的呢?
2 內(nèi)存操作
在設(shè)備驅(qū)動程序中動態(tài)開辟內(nèi)存,不是用malloc,而是kmalloc,或者用
get_free_pages直接申請頁。釋放內(nèi)存用的是kfree,或free_pages. 請注意,
kmalloc等函數(shù)返回的是物理地址!而malloc等返回的是線性地址!關(guān)于
kmalloc返回的是物理地址這一點(diǎn)本人有點(diǎn)不太明白:既然從線性地址到物理
地址的轉(zhuǎn)換是由386cpu硬件完成的,那樣匯編指令的操作數(shù)應(yīng)該是線性地址,
驅(qū)動程序同樣也不能直接使用物理地址而是線性地址。但是事實(shí)上kmalloc
返回的確實(shí)是物理地址,而且也可以直接通過它訪問實(shí)際的RAM,我想這樣可
以由兩種解釋,一種是在核心態(tài)禁止分頁,但是這好像不太現(xiàn)實(shí);另一種是
linux的頁目錄和頁表項(xiàng)設(shè)計(jì)得正好使得物理地址等同于線性地址。我的想法
不知對不對,還請高手指教。
言歸正傳,要注意kmalloc最大只能開辟128k-16,16個(gè)字節(jié)是被頁描述符
結(jié)構(gòu)占用了。kmalloc用法參見khg.
內(nèi)存映射的I/O口,寄存器或者是硬件設(shè)備的RAM(如顯存)一般占用F0000000
以上的地址空間。在驅(qū)動程序中不能直接訪問,要通過kernel函數(shù)vremap獲得
重新映射以后的地址。
另外,很多硬件需要一塊比較大的連續(xù)內(nèi)存用作DMA傳送。這塊內(nèi)存需要一直
駐留在內(nèi)存,不能被交換到文件中去。但是kmalloc最多只能開辟128k的內(nèi)存。
這可以通過犧牲一些系統(tǒng)內(nèi)存的方法來解決。
具體做法是:比如說你的機(jī)器由32M的內(nèi)存,在lilo.conf的啟動參數(shù)中加上
mem=30M,這樣linux就認(rèn)為你的機(jī)器只有30M的內(nèi)存,剩下的2M內(nèi)存在vremap
之后就可以為DMA所用了。
請記住,用vremap映射后的內(nèi)存,不用時(shí)應(yīng)用unremap釋放,否則會浪費(fèi)頁表。
3 中斷處理
同處理I/O端口一樣,要使用一個(gè)中斷,必須先向系統(tǒng)登記。
int request_irq(unsigned int irq ,
void(*handle)(int,void *,struct pt_regs *),
unsigned int long flags,
const char *device);
irq: 是要申請的中斷。
handle:中斷處理函數(shù)指針。
flags:SA_INTERRUPT 請求一個(gè)快速中斷,0 正常中斷。
device:設(shè)備名。
如果登記成功,返回0,這時(shí)在/proc/interrupts文件中可以看你請求的
中斷。
4一些常見的問題。
對硬件操作,有時(shí)時(shí)序很重要。但是如果用C語言寫一些低級的硬件操作
的話,gcc往往會對你的程序進(jìn)行優(yōu)化,這樣時(shí)序就錯(cuò)掉了。如果用匯編寫呢,
gcc同樣會對匯編代碼進(jìn)行優(yōu)化,除非你用volatile關(guān)鍵字修飾。最保險(xiǎn)的
辦法是禁止優(yōu)化。這當(dāng)然只能對一部分你自己編寫的代碼。如果對所有的代碼
都不優(yōu)化,你會發(fā)現(xiàn)驅(qū)動程序根本無法裝載。這是因?yàn)樵诰幾g驅(qū)動程序時(shí)要
用到gcc的一些擴(kuò)展特性,而這些擴(kuò)展特性必須在加了優(yōu)化選項(xiàng)之后才能體現(xiàn)
出來。
關(guān)于kernel的調(diào)試工具,我現(xiàn)在還沒有發(fā)現(xiàn)有合適的。有誰知道請告訴我,
不勝感激。我一直都在printk打印調(diào)試信息,倒也還湊合。
關(guān)于設(shè)備驅(qū)動程序還有很多內(nèi)容,如等待/喚醒機(jī)制,塊設(shè)備的編寫等。
我還不是很明白,不敢亂說。
歡迎大家批評指正。
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -