动态加载
一,编译,在赐教内核树下编译,生成.o文件或.ko文件
二,将生成的.o或.ko文件拷到相应目录,通常是/lib/module/kernel下边
三,用insmod命令加载,用rmmod命令卸载
静态加载
静态加载主要就是编译内核。就是将编撰好的驱动放进内核相应的目录下边。之后编译内核。之后运行编译好的内核。
静态加载就是把驱动程序直接编译到内核里,系统启动后可以直接调用。静态加载的缺点是调试上去比较麻烦,每次更改一个地方都要重新编译下载内核,效率较低。
动态加载借助了LINUX的module特点,可以在系统启动后用insmod命令把驱动程序(.o文件)添加起来,在不须要的时侯用rmmod命令来卸载。在台式机上通常采用动态加载的形式。
在嵌入式产品里可以先用动态加载的方法来调试,调试完毕后再编译到内核里。下边以我们的nHD板卡为例述说一下加载驱动程序的技巧。
假定我们须要添加一个名为mydrv的字符型设备驱动,主设备号为254,次设备号为0(只有一个从设备),静态加载的步骤如下:
1、编写自己的驱动程序源文件mydrv.c,并置于firmwareuClinux-Samsung-2500linux-2.4.xdriverschar下边。一个典型的字符型驱动的最后通常包括如下内容:
staticintmydrv_init(void)
intret;
ret=register_chrdev(mydrv_major,”mydrv“,&my_fops);
if(ret==0)printk(“register_chrdevsucceed!n”);
elseprintk(“register_chrdevfail!n”);
return0;
static__exitvoidmydrv_cleanup(void)
unregister_chrdev(mydrv_major,”mydrv“);
printk(“register_chrdevsucceed!n”);
return;
module_init(mydrv_init);
module_exit(mydrv_cleanup);
函数mydrv_init的任务是注册设备鸟哥的linux私房菜,mydrv_cleanup的任务是取消注册。Module_init和module_exit的作用前面会提到。
2.在firmwareuClinux-Samsung-2500vendorsSamsung2500Makefile中添加如下句子(以刚刚的设备为例,实际添加时其实要按照你自己的设备名称和设备号来添加):
mknod$(ROMFSDIR)/dev/mydrvc2540
这句话的目的是在内核中创建一个与你的驱动程序对应的设备节点。
3.在firmwareuClinux-Samsung-2500linux-2.4.xdriverscharMakefile
中添加如下句子:
obj-$(CONFIG_CHAR_MYDRV)+=mydrv.o
这句话的目的是按照编译选项$(CONFIG_CHAR_MYDRV)来决定是否要添加该设备驱动。
4.在firmwareuClinux-Samsung-2500linux-2.4.xdriverscharconfig.in
中添加:
if[“$CONFIG_ARCH_SAMSUNG”=”y”];then
tristate’,MYDRVdrivermodule’CONFIG_CHAR_MYDRV
这句话的目的是在运行makemenuconfig时形成与你的设备对应的编译选项。
5.运行makemenuconfgi,应当能看见你自己的设备的选项,选中就可以了。
6.编译内核,下载,运行自己的测试程序。
假如你认为上述步骤比较麻烦,可以把4、5两条都省去,把第3条中的
obj-$(CONFIG_CHAR_MYDRV)+=mydrv.o
改为
obj-y+=mydrv.o
这样在menuconfig就没有与你的设备对应的选项了,编译内核时直接会把你的驱动编译进去。
还有一个问题须要说明一下。在…/drivers/char下有一个mem.c文件,其中最后有一个int__initchr_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.xincludelinuxinit.h中。假如定义了宏MODULE时,module_init是作为模块初始化函数arm linux 内核树,假若没有定义MODULE,则
module_init(fn)就被定义为__initcall(fn)。静态编译时是不定义MODULE的,所以我们的驱动中的module_init就等于是:
__initcall(mydrv_init).
这样我们的初始化函数都会在启动时被执行了。
至于到底是在mem.c/chr_dev_init中添加你的设备初始化函数,还是在设备文件中通过module_init来完成,完全取决于你的喜好,没有任何差异。假如你的初始化函数只是注册一个设备(没有申请显存等操作),那就算你在两个地方都加上(等于初始化了两次)也没关系,不会出错(有兴趣可以看一下内核里注册设备的函数实现,
…linux-2.4.xfsdevices.cregister_chrdev)。不过为了规范起见,还是不建议这样作。
最后一个问题。在静态加载驱动的时侯,我们哪个mydrv_cleanup和module_exit函数永远不会被执行,所以除去是完全可以的,不过为了程序看上去结构清晰arm linux 内核树,也为了与动态加载的程序兼容,还是建议保留着。
下边讲一下动态加载驱动的方式。
1、运行makemenuconfgi,在内核配置中步入Loadablemodulesupport,选择Enableloadablemodulesupport和Kernelmoduleloader(NEW)两个选项。在应用程序配置中步入busybox,选择insmod,rmmod,lsmod三个选项。
2、在…vendorsSamsung2500Makefile中添加相应的设备节点,技巧与静态加载时完全一样。
3、编写自己的驱动程序文件,在文件开始处加一句:
defineMODULE
文件最后的
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板卡linux使用教程,用nfs的方式把编译服务器上/exports/自己的目录mount上来(假定mount到/mnt下)。Nfs的使用你们都很熟悉了,这儿就不再说。
8、
cd/mnt
/bin/insmodmydrv.o
如今你的设备就早已被动态加载到系统里了。可以用lsmod命令查看当前已挂接的模块。
9、运行你的测试程序
10、调试完毕后用rmmodmydrv把你的设备卸载掉。
补充几点:
1、关于完善设备节点的问题,由于你们所使用的系统不太一样,所以不须要根据我说的方式。其实只要在你自己的系统的dev目录下构建了自己的驱动程序的设备节点就可以了。
2、没有考虑动态分配主设备号的问题。所以注册设备哪个地方稍为有点不严密。
3、模块加载时要把自己的.c文件编译成.o文件,CFLAGS前面那一串编译选项有时可能有点可恶,假如你没搞定,最简单的办法就是重新编译一遍内核并重定向到一个文件中(别忘了先makeclean一下):make>out。
之后在out文件里随意找一个字符驱动程序的编译过程,把它的编译选项找下来,拷贝到你自己的Makefile里就可以了。我就是如此作的。
下边是一个最简单的字符设备驱动的反例。实际的驱动千差万别,但显然也就是“填充”自己的open,close,read,write,ioctl几个函数而已。
ifndefKERNELdefineKERNELendifdefineMODULEdefinedrvtest_major254includeincludeincludeincludeinclude//printk()include//kmalloc()include//errorcodesinclude//size_tinclude//mark_bhincludeincludeincludeincludeincludeincludeinclude
staticintmytest_open(structinode*inode,structfile*filp)
MOD_INC_USE_COUNT;
printk(“mytestopen!n”);
return0;
staticssize_tmytest_read(structfileflip,charbuff,size_tcount,
loff_t*f_pos)
charbuf[10]={0x1,0x2,0x3,0x4,0x5};
memcpy(buff,buf,5);
return5;
staticintmytest_close(structinode*inode,structfile*filp)
{MOD_DEC_USE_COUNT;
printk(“mytestclose!n”);
return0;
staticstructfile_operationsmy_fops={
read:mytest_read,
//write:mytest_write,
open:mytest_open,
release:mytest_close,
//ioctl:mytest_ioctl,
};
staticintmytest_init(void)
intret;
ret=register_chrdev(drvtest_major,“drvtest”,&my_fops);
if(ret==0)printk(“register_chrdevsucceed!n”);
elseprintk(“register_chrdevfail!n”);
return0;
static__exitvoidmytest_cleanup(void)
unregister_chrdev(drvtest_major,“drvtest”);
printk(“register_chrdevsucceed!n”);
printk(“bye!n”);
return;
module_init(mytest_init);
module_exit(mytest_cleanup);