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);