1. 程式人生 > >Linux下安裝驅動的兩種方法

Linux下安裝驅動的兩種方法

linux 編譯安裝驅動有兩種,動態載入與靜態載入 
動態載入 
一,編譯,在指點核心樹下編譯,生成.o檔案或.ko檔案 
二,將生成的.o或.ko檔案拷到相應目錄,一般是/lib/module/kernel下面 
三,用insmod命令載入,用rmmod命令解除安裝 
靜態載入 
靜態載入主要就是編譯核心。就是將編寫好的驅動放進核心相應的目錄下面。然後編譯核心。然後執行編譯好的核心。

靜態載入就是把驅動程式直接編譯到核心裡,系統啟動後可以直接呼叫。靜態載入的缺點是除錯起來比較麻煩,每次修改一個地方都要重新編譯下載核心,效率較低。 
動態載入利用了LINUX的module特性,可以在系統啟動後用insmod命令把驅動程式(.o檔案)新增上去,在不需要的時候用rmmod命令來解除安裝。在臺式機上一般採用動態載入的方式。 
在嵌入式產品裡可以先用動態載入的方式來除錯,除錯完畢後再編譯到核心裡。下面以我們的nHD板卡為例講述一下載入驅動程式的方法。

假設我們需要新增一個名為mydrv的字元型裝置驅動,主裝置號為254,次裝置號為0(只有一個從裝置),靜態載入的步驟如下:

1、編寫自己的驅動程式原始檔mydrv.c,並放在firmware\uClinux-Samsung-2500\linux-2.4.x\drivers\char下面。一個典型的字元型驅動的最後一般包括如下內容:

static int mydrv_init(void) 

int ret; 
ret = register_chrdev(mydrv_major, ” mydrv “, &my_fops); 
if(ret == 0) printk(“register_chrdev succeed!\n”); 
else printk(“register_chrdev fail!\n”); 
return 0; 
}

static __exit void mydrv _cleanup(void) 

unregister_chrdev(mydrv _major, ” mydrv “); 
printk(“register_chrdev succeed!\n”); 
return ; 

module_init(mydrv _init); 
module_exit (mydrv _cleanup);

函式mydrv_init的任務是註冊裝置,mydrv_cleanup的任務是取消註冊。 Module_init和module_exit的作用後面會講到。

2.在firmware\uClinux-Samsung-2500\vendors\Samsung\2500\Makefile中新增如下語句(以剛才的裝置為例,實際新增時當然要根據你自己的裝置名稱和裝置號來新增):

mknod $(ROMFSDIR) /dev/mydrv c 254 0

這句話的目的是在核心中建立一個與你的驅動程式對應的裝置節點。

3.在firmware\uClinux-Samsung-2500\linux-2.4.x\drivers\char\Makefile

中新增如下語句: 
obj-$(CONFIG_CHAR_MYDRV) +=mydrv.o

這句話的目的是根據編譯選項$(CONFIG_CHAR_MYDRV)來決定是否要新增該裝置驅動。

4.在firmware\uClinux-Samsung-2500\linux-2.4.x\drivers\char\config.in

中新增: 
if [“$CONFIG_ARCH_SAMSUNG”=”y”]; then 
tristate ’ ,MYDRV driver module ’ CONFIG_CHAR_MYDRV

這句話的目的是在執行make menuconfig時產生與你的裝置對應的編譯選項。

5.執行make menuconfgi,應該能看到你自己的裝置的選項,選中就可以了。

6.編譯核心,下載,執行自己的測試程式。

如果你覺得上述步驟比較麻煩,可以把4、5兩條都省去,把第3條中的

obj-$(CONFIG_CHAR_MYDRV) +=mydrv.o 
改為 
obj-y +=mydrv.o

這樣在menuconfig就沒有與你的裝置對應的選項了,編譯核心時直接會把你的驅動編譯進去。

還有一個問題需要說明一下。在…/drivers/char下有一個mem.c檔案,其中最後有一個int __init chr_dev_init(void)函式。大家可以看到,所有字元裝置的初始化函式(IDE_INT_init之類)都要新增在這裡,而我們剛才的驅動程式的初始化函式並沒有新增到這裡。這個問題涉及到系統啟動時的do_initcall函式,詳細講述起來比較煩瑣,大家有興趣可以看一下《情景分析》下冊P726~P729。這裡簡單介紹一下。如果對一個函式(通常都是一些初始化函式)作如下處理(仍以我們的mydrv_init函式為例):

__initcall(mydrv_init)

那麼在編譯核心時會生成一個指向mydrv_init的函式指標__initcall_mydrv_int,系統啟動時,在執行do_initcall函式時,會依次執行這些初始化函式,並且會在初始化結束後把這些函式所佔用的記憶體釋放掉。

回到mem.c檔案,在最後有一行: 
__initcall(chr_dev_init)

這句話的作用就顯而易見了,在系統啟動時自動執行chr_dev_init函式。所以我們完全可以不用在mem.c/chr_dev_init中新增我們自己的初始化函式,而是在我們自己的裝置檔案中(mydrv.c)新增如下一行: 
__initcall(mydrv_init).

在我們前面說到的我們自己的裝置檔案mydrv.c中,最後有一句:

module_init(mydrv _init);

大家可以看一下module_init的定義,在…linux.-2.4.x\include\linux\init.h中。如果定義了巨集MODULE時,module_init是作為模組初始化函式,如果沒有定義MODULE,則 
module_init(fn)就被定義為__initcall(fn)。靜態編譯時是不定義MODULE的,所以我們的驅動中的module_init就等於是:

__initcall(mydrv_init).

這樣我們的初始化函式就會在啟動時被執行了。

至於究竟是在mem.c/chr_dev_init中新增你的裝置初始化函式,還是在裝置檔案中通過module_init來完成,完全取決於你的喜好,沒有任何差別。如果你的初始化函式只是註冊一個裝置(沒有申請記憶體等操作),那即使你在兩個地方都加上(等於初始化了兩次)也沒關係,不會出錯(有興趣可以看一下核心裡註冊裝置的函式實現, 
…linux-2.4.x\fs\devices.c\register_chrdev)。不過為了規範起見,還是不建議這樣作。

最後一個問題。在靜態載入驅動的時候,我們那個mydrv_cleanup和module_exit函式永遠不會被執行,所以去掉是完全可以的,不過為了程式看起來結構清晰,也為了與動態載入的程式相容,還是建議保留著。

下面講一下動態載入驅動的方法。 
1、執行make menuconfgi,在核心配置中進入Loadable module support,選擇Enable loadable module support和Kernel module loader(NEW)兩個選項。在應用程式配置中進入busybox,選擇insmod, rmmod, lsmod三個選項。

2、在…vendors\Samsung\2500\Makefile中新增相應的裝置節點,方法與靜態載入時完全一樣。

3、編寫自己的驅動程式檔案,在檔案開始處加一句:

define MODULE

檔案最後的 
mydrv_init 
mydrv_cleanup 
module_init(mydrv _init) 
module_exit (mydrv _cleanup) 
這四項必須保留。

4、仿照如下的格式寫自己的Makefile檔案:

KERNELDIR= /home/hexf/hardware/nHD/Design/firmware/uClinux-Samsung-2500/linux-2.4.x 
CFLAGS = -D__KERNEL__ -I$(KERNELDIR)/include -Wall -Wstrict-prototypes -Wno-trigraphs -O2 -fno-strict-aliasing -fno-common -fno-common -pipe -fno-builtin -D__linux__ -DNO_MM -mapcs-32 -mshort-load-bytes -msoft-float 
CC = arm-elf-gcc

all: mydrv.o 
clean: 
rm -f *.o

5、編譯自己的驅動程式檔案。注意在動態載入時只編譯不連線,所以得到的是.o檔案。

6、把編譯後的驅動程式的.o 檔案,連同自己的測試程式(假設叫mytest,注意這個是可執行檔案)一起放在編譯伺服器的/exports/自己的目錄下。測試程式就是一個普通的應用程式,其編寫和編譯的步驟這裡就不講了。

7、啟動nHD板卡,用nfs的方法把編譯伺服器上/exports/自己的目錄mount上來(假設mount 到 /mnt下)。Nfs的使用大家都很熟悉了,這裡就不再說。

8、 
cd /mnt 
/bin/insmod mydrv.o 
現在你的裝置就已經被動態載入到系統裡了。可以用lsmod命令檢視當前已掛接的模組。

9、執行你的測試程式

10、除錯完畢後用 rmmod mydrv把你的裝置解除安裝掉。

補充幾點: 
1、關於建立裝置節點的問題,因為大家所使用的系統不太一樣,所以不需要按照我說的方法。總之只要在你自己的系統的dev目錄下建立了自己的驅動程式的裝置節點就可以了。

2、沒有考慮動態分配主裝置號的問題。所以註冊裝置那個地方稍微有點不嚴密。

3、模組載入時要把自己的.c檔案編譯成.o檔案,CFLAGS後面那一串編譯選項有時可能有點煩人,如果你沒搞定,最簡單的辦法就是重新編譯一遍核心並重定向到一個檔案中(別忘了先make clean一下):make > out。

然後在out檔案裡隨便找一個字元驅動程式的編譯過程,把它的編譯選項找出來,拷貝到你自己的Makefile裡就可以了。我就是這麼作的。

下面是一個最簡單的字元裝置驅動的例子。實際的驅動千差萬別,但其實也就是“填充”自己的open,close,read,write,ioctl幾個函式而已。

ifndef KERNEL

define KERNEL

endif

define MODULE

define drvtest_major 254

include

include

include

include

include // printk()

include // kmalloc()

include // error codes

include // size_t

include // mark_bh

include

include

include

include

include

include

include

static int mytest_open(struct inode *inode,struct file *filp) 
{

MOD_INC_USE_COUNT;

printk(“mytest open!\n”);

return 0;

}

static ssize_t mytest_read(struct file flip,char buff,size_t count, 
loff_t * f_pos) 

char buf[10] ={0x1,0x2,0x3,0x4,0x5};

memcpy(buff,buf,5);

return 5; 
}

static int mytest_close(struct inode *inode,struct file *filp)

{ MOD_DEC_USE_COUNT;

printk(“mytest close!\n”);

return 0;

}

static struct file_operations my_fops = { 
read: mytest_read, 
// write: mytest_write, 
open: mytest_open, 
release: mytest_close, 
// ioctl: mytest_ioctl,

};

static int mytest_init(void)


int ret; 
ret = register_chrdev(drvtest_major, “drvtest”, &my_fops);

if(ret == 0) printk(“register_chrdev succeed!\n”); 
else printk(“register_chrdev fail!\n”); 
return 0;

}

static __exit void mytest_cleanup(void) 
{

unregister_chrdev(drvtest_major, “drvtest”); 
printk(“register_chrdev succeed!\n”); 
printk(“bye!\n”);

return ; 

module_init(mytest_init); 
module_exit(mytest_cleanup);