| uclinux下靜態(tài)/動(dòng)態(tài)加載驅動(dòng)程序的方法 說(shuō)明:這是我最近給單位寫(xiě)的一篇文檔,沒(méi)有什么復雜的東東,對剛接觸linux driver的朋友或許有點(diǎn)幫助。文檔本來(lái)是針對我們自己的產(chǎn)品的,有些地方(路徑、mknod、動(dòng)態(tài)分配主設備號等)本來(lái)應該改改,因為懶惰也沒(méi)去改。 在LINUX下加載驅動(dòng)程序可以采用動(dòng)態(tài)和靜態(tài)兩種方式。靜態(tài)加載就是把驅動(dòng)程序直接編譯到內核里,系統啟動(dòng)后可以直接調用。靜態(tài)加載的缺點(diǎn)是調試起來(lái)比 較麻煩,每次修改一個(gè)地方都要重新編譯下載內核,效率較低。動(dòng)態(tài)加載利用了LINUX的module特性,可以在系統啟動(dòng)后用insmod命令把驅動(dòng)程序 (.o文件)添加上去,在不需要的時(shí)候用rmmod命令來(lái)卸載。在臺式機上一般采用動(dòng)態(tài)加載的方式。在嵌入式產(chǎn)品里可以先用動(dòng)態(tài)加載的方式來(lái)調試,調試完 畢后再編譯到內核里。 下面以我們的nHD板卡為例講述一下加載驅動(dòng)程序的方法。 假設我們需要添加一個(gè)名為mydrv的字符型設備驅動(dòng),主設備號為254,次設備號為0(只有一個(gè)從設備)。靜態(tài)加載的步驟如下: 1、編寫(xiě)自己的驅動(dòng)程序源文件mydrv.c,并放在firmware\uClinux-Samsung-2500\linux-2.4.x drivers\char下面。 一個(gè)典型的字符型驅動(dòng)的最后一般包括如下內容: 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的任務(wù)是注冊設備,mydrv_cleanup的任務(wù)是取消注冊。 Module_init和module_exit的作用后面會(huì )講到。 2.在firmware\uClinux-Samsung-2500\vendors\Samsung\2500\Makefile中添加如下語(yǔ)句(以剛 才的設備為例,實(shí)際添加時(shí)當然要根據你自己的設備名稱(chēng)和設備號來(lái)添加): mknod $(ROMFSDIR) /dev/mydrv c 254 0 .這句話(huà)的目的是在內核中創(chuàng )建一個(gè)與你的驅動(dòng)程序對應的設備節點(diǎn)。 3.在firmware\uClinux-Samsung-2500\linux-2.4.x\drivers\char\Makefile 中添加如下語(yǔ)句: obj-$(CONFIG_CHAR_MYDRV) +=mydrv.o 這句話(huà)的目的是根據編譯選項$(CONFIG_CHAR_MYDRV)來(lái)決定是否要添加該設備驅動(dòng)。 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 這句話(huà)的目的是在運行make menuconfig時(shí)產(chǎn)生與你的設備對應的編譯選項。 5.運行make menuconfgi,應該能看到你自己的設備的選項,選中就可以了。 6.編譯內核,下載,運行自己的測試程序。 如果你覺(jué)得上述步驟比較麻煩,可以把4、5兩條都省去,把第3條中的 obj-$(CONFIG_CHAR_MYDRV) +=mydrv.o 改為 obj-y +=mydrv.o 這樣在menuconfig就沒(méi)有與你的設備對應的選項了,編譯內核時(shí)直接會(huì )把你的驅動(dòng)編譯進(jìn)去。 還有一個(gè)問(wèn)題需要說(shuō)明一下。在…/drivers/char下有一個(gè)mem.c文件,其中最后有一個(gè)int __init chr_dev_init(void)函數。大家可以看到,所有字符設備的初始化函數(IDE_INT_init之類(lèi))都要添加在這里,而我們剛才的驅動(dòng) 程序的初始化函數并沒(méi)有添加到這里。這個(gè)問(wèn)題涉及到系統啟動(dòng)時(shí)的do_initcall函數,詳細講述起來(lái)比較煩瑣,大家有興趣可以看一下《情景分析》下 冊P726~P729。這里簡(jiǎn)單介紹一下。如果對一個(gè)函數(通常都是一些初始化函數)作如下處理(仍以我們的mydrv_init函數為例): __initcall(mydrv_init) 那么在編譯內核時(shí)會(huì )生成一個(gè)指向mydrv_init的函數指針__initcall_mydrv_int,系統啟動(dòng)時(shí),在運行do_initcall函 數時(shí),會(huì )依次執行這些初始化函數,并且會(huì )在初始化結束后把這些函數所占用的內存釋放掉。 回到mem.c文件,在最后有一行: __initcall(chr_dev_init) 這句話(huà)的作用就顯而易見(jiàn)了,在系統啟動(dòng)時(shí)自動(dòng)執行chr_dev_init函數。所以我們完全可以不用在mem.c/chr_dev_init中添加我們 自己的初始化函數,而是在我們自己的設備文件中(mydrv.c)添加如下一行: __initcall(mydrv_init). 在我們前面說(shuō)到的我們自己的設備文件mydrv.c中,最后有一句: module_init(mydrv _init); 大家可以看一下module_init的定義,在…linux.-2.4.x\include\linux\init.h中。如果定義了宏MODULE 時(shí),module_init是作為模塊初始化函數,如果沒(méi)有定義MODULE,則 module_init(fn)就被定義為_(kāi)_initcall(fn)。 靜態(tài)編譯時(shí)是不定義MODULE的,所以我們的驅動(dòng)中的module_init 就等于是: __initcall(mydrv_init). 這樣我們的初始化函數就會(huì )在啟動(dòng)時(shí)被執行了。 至于究竟是在mem.c/chr_dev_init中添加你的設備初始化函數,還是在設備文件中通過(guò)module_init來(lái)完成,完全取決于你的喜好, 沒(méi)有任何差別。如果你的初始化函數只是注冊一個(gè)設備(沒(méi)有申請內存等操作),那即使你在兩個(gè)地方都加上(等于初始化了兩次)也沒(méi)關(guān)系,不會(huì )出錯(有興趣可 以看一下內核里注冊設備的函數實(shí)現, …linux-2.4.x\fs\devices.c\register_chrdev)。不過(guò)為了規范起見(jiàn),還是不建議這樣作。 最后一個(gè)問(wèn)題。在靜態(tài)加載驅動(dòng)的時(shí)候,我們那個(gè)mydrv_cleanup和module_exit函數永遠不會(huì )被執行,所以去掉是完全可以的,不過(guò)為了 程序看起來(lái)結構清晰,也為了與動(dòng)態(tài)加載的程序兼容,還是建議保留著(zhù)。 下面講一下動(dòng)態(tài)加載驅動(dòng)的方法。 1、運行make menuconfgi,在內核配置中進(jìn)入Loadable module support,選擇Enable loadable module support和Kernel module loader(NEW)兩個(gè)選項。在應用程序配置中進(jìn)入busybox,選擇insmod, rmmod, lsmod三個(gè)選項。 2、在…vendors\Samsung\2500\Makefile中添加相應的設備節點(diǎn),方法與靜態(tài)加載時(shí)完全一樣。 3、編寫(xiě)自己的驅動(dòng)程序文件,在文件開(kāi)始處加一句: #define MODULE 文件最后的 mydrv_init mydrv_cleanup module_init(mydrv _init) module_exit (mydrv _cleanup) 這四項必須保留。 4、仿照如下的格式寫(xiě)自己的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、編譯自己的驅動(dòng)程序文件。注意在動(dòng)態(tài)加載時(shí)只編譯不連接,所以得到的是.o文件。 6、把編譯后的驅動(dòng)程序的.o 文件,連同自己的測試程序(假設叫mytest,注意這個(gè)是可執行文件)一起放在編譯服務(wù)器的/exports/自己的目錄下。 測試程序就是一個(gè)普通的應用程序,其編寫(xiě)和編譯的步驟這里就不講了。 7、啟動(dòng)nHD板卡,用nfs的方法把編譯服務(wù)器上/exports/自己的目錄mount上來(lái)(假設mount 到 /mnt下)。Nfs的使用大家都很熟悉了,這里就不再說(shuō)。 8、 cd /mnt /bin/insmod mydrv.o 現在你的設備就已經(jīng)被動(dòng)態(tài)加載到系統里了??梢杂胠smod命令查看當前已掛接的模塊。 9、運行你的測試程序 10、調試完畢后用 rmmod mydrv把你的設備卸載掉。 補充幾點(diǎn): 1、關(guān)于建立設備節點(diǎn)的問(wèn)題,因為大家所使用的系統不太一樣,所以不需要按照我說(shuō)的方法??傊灰谀阕约旱南到y的dev目錄下建立了自己的驅動(dòng)程序的設 備節點(diǎn)就可以了。 2、沒(méi)有考慮動(dòng)態(tài)分配主設備號的問(wèn)題。所以注冊設備那個(gè)地方稍微有點(diǎn)不嚴密。 3、模塊加載時(shí)要把自己的.c文件編譯成.o文件,CFLAGS后面那一串編譯選項有時(shí)可能有點(diǎn)煩人,如果你沒(méi)搞定,最簡(jiǎn)單的辦法就是重新編譯一遍內核并 重定向到一個(gè)文件中(別忘了先make clean一下):make > out。 然后在out文件里隨便找一個(gè)字符驅動(dòng)程序的編譯過(guò)程,把它的編譯選項找出來(lái),拷貝到你自己的Makefile里就可以了。我就是這么作的。 下面是一個(gè)最簡(jiǎn)單的字符設備驅動(dòng)的例子。實(shí)際的驅動(dòng)千差萬(wàn)別,但其實(shí)也就是 “填充”自己的open,close,read,write,ioctl幾個(gè)函數而已。 |
聯(lián)系客服