精选学习资料 - - - - - - - - - 读书之法 ,在循序而渐进 ,熟读而精思编译 linux 外部驱动模块时的基础学问linux 内核模块编译引言为了清楚的编译 Linux 内核,内核编译系统使用 Kbuild 规章对编译的过程以及依靠进行规约;在内核模块的编译中,为了保持与内核源码的兼容以及传递编译链接选项给 GCC,也使用Kbuild 规章;内核模块的源代码可以在内核源码树中,也可以在内核源码树外,当使用 Kbuild 时,两种情况的编译方式也大致相像;一般的内核模块在开发时,都是放在源码树外的;本文主要是针对源码树外部的内核模块的编译;为了屏蔽内核模块编译的复杂性,开发人员需要编写额外的Makefile ,最终让编译内核模块就像编译一般的应用程序一样,敲入” make”就行了;本文后面就给了一个实例;编译外部模块在编译外部模块之前,需要第一预备好当前内核的配置以及内核头文件,同时,当前内核的modules enable选项应当开启 〔编译内核时指定〕;命令行选项使用如下命令编译外部模块:make – C M= 其中 -C 说明 make 要调用 下的 Makefile ,该 Makefile 就是内核的Makefile ,M 为该 Makefile 的参数,指定外部模块源码的路径;当 译外部模块;Makefile 接收到 M 参数时,就默认编例如,当前目录下存放一个外部模块的源码,其编译命令如下:make – C /lib/modules/`uname -r`/build M=`pwd` 其中 uname –r 猎取当前运行内核的版本,pwd 为当前源码路径,将其绽开之后为:make – C /lib/modules/ 2.6.42.9/build M=/home/user/hello 其中 /lib/modules/ 2.6.42.9/build是指向内核源码目录的符号链接;编译完成之后,要安装驱动时,调用如下命令:make – C /lib/modules/`uname -r`/build M=`pwd` modules_install 编译目标 modules 编译外部模块,默认目标就是 modules modules_install安装编译胜利了的外部模块,默认的安装目录为/lib/modules//extra/,前缀可以同过 INSTALL_MOD_PATH 指定;clean 清除选项 help 列出可用的外部目标名师归纳总结 Kbuild文件Makefile第 1 页,共 4 页在执行了 make – C /lib/modules/`uname-r`/build M=`pwd`之后,内核源码树中的会再次跳转到 `pwd`目录下,加载Kbuild 或 Makefile〔 假如没有Kbuild 文件,就加载Makefile ,因此, Kbuild 文件中的内容也可以放到Makefile 中〕;- - - - - - -精选学习资料 - - - - - - - - - 读书之法 ,在循序而渐进 ,熟读而精思假如模块源码目录中的 Kbuild 或 Makefile 中没有定义编译目标时,编译过程最终是什么都没生成的;以下一行就是定义生成目标:obj- m: = .o .o ⋯上面的 obj-m 变量是指外部模块,其后面的一组 .o 最终生成.ko 模块;同样,仍有一个变量 obj-y ,它包含要静态编译进入内核的模块;本文不考虑它;在默认情形下,内核源码编译系统会将 .c 编译成 .o ,并最终链接生 成 .ko ;假如 .ko 需要多个源文件时, Kbuild或 Makefile 中要添加如下行:-y: = src1.o src2.o ⋯ .Makefile 与 Kbuild 合并为了屏蔽编译内核模块的复杂性,让使用人员简洁的调用 make/makeinstall 即可完成内核模块的编译,模块源码目录下通常添加了一个 wrapper Makefile ,供向的 Makefile 包含了 Kbuild部分,内容如下:ifneq 〔$〔KERNELRELEASE〕,〕 obj-m := hello.o else default:: $〔MAKE〕 -C /lib/modules/`uname -r`/build M=`pwd` modules endif Kbuild 与 Makefile 分别当内核模块源码目录下同时包含了Kbuild 与 Makefile 时,编译系统只加载Kbuild 文件;两个文件内容分别如下:Makefile内容如下:default:: $〔MAKE〕-C /lib/modules/`uname -r`/build M=`pwd` 这里的 Makefile 只是对内核 Makefile 调用进行了封装;Kbuild内容如下:EXTRA_CFLAGS := -I.-include ./xxx.h obj-m := module1.o module2.o module1-objs := src1.o module2-objs := src2.o 头文件在内核源码树中,头文件的存放规章如下:1.假如该头文件定义的是模块内部的接口,就头文件放在模块所在的目录下2.假如头文件中内容在内核其他子系统中使用,就放在 include/linux 模块版本名师归纳总结 - - - - - - -第 2 页,共 4 页精选学习资料 - - - - - - - - - 读书之法 ,在循序而渐进 ,熟读而精思模块版本选项是通过内核编译选项CONFIG_MODVERSIONS 定义的,它是一个简洁的ABI 兼容性检查机制;对于模块的每个导出符号,都有一个对应的CRC 校验值;当模块加载或使用时,内核会用自己的 CRC值与模块的 CRC 值进行对比,假如不同,就拒绝加载模块;在内核源码树根目录中,其中的 Module.symvers 文件就包含了 内核全部的导出符号 以及全部编译后模块的导出符号 ;symbol from kernel〔vmlinux+all modules〕在编译内核时,根目录下会生成 Module.symvers 文件,它包含了内核以及编译后的模块导出的全部符号;对于每一个符号,相应的 CRC 校验值也被储存, Module.symvers 每一行数据格式如下: 0x2d036834 scsi_remove_host drivers/scsi/scsi_mod 当内核编译选项CONFIG_MODVERSIONS 关闭时,全部的CRC值都为 0x00000000 ;Module.symvers文件主要有以下用途:1.列出 vmlinux 和全部模块的导出函数2.列出全部符号的 CRC校验值symbol and extern modules当编译外部模块时,在 MODPOST 阶段时,会拜访内核源码树中的 Module.symvers 检测当前模块的外部符号是否已经被定义,同时,假如外部模块源码根目录下包含了 Module.symvers文件,该文件也会被检测;对于当前模块的每个外部符号,编译系统都会从当前目录下的Module.symvers以及内核源码树下的 Module.symvers中查找,检测是否有该符号;在 MODPOST 阶段,会在当前目录下生成一个新的 的全部符号;Module.symvers ,它包含 kernel 中未定义当外部模块需要从另一个外部模块中导入符号时,有三种方法可以解决;1.使用 top-level Kbuild 文件例如,两个模块 foo.ko,bar.ko ,其中 foo.ko 依靠 bar.ko 中的导出符号,可以使用一个顶层公用的 Kbuild 文件对两个模块同时编译,假设目录如下:./foo/ <= contains foo.ko ./bar/ <= contains bar.ko 顶层的 Kbuild 文件内容如下:obj-y := foo/ bar/ 执行 make -C $KDIR M=$PWD,在编译过程中,两个模块的导出符号是共享的;2.使用额外的 Module.symvers 文件第一生成外部模块 bar.ko ,生成之后, bar.ko 目录下生成一个 Module.symvers 文件,它包含了 kernel 中的 Module.symvers 未定义的全部符号 〔当然包含 bar.ko 的导出符号 〕;在编译 foo.ko 时,为了能拜访 bar.ko 的导出符号,可以将 bar.ko 生成的 Module.symvers 文件复制到 foo 编译目录;编译时,会 读取 foo 目录下的 Module.symvers 文件,同时,生成一个全部未在 kernel 中定义的符号文件 Module.symvers ;3. 调用 make 时传入参数 KBUILD_EXTRA_SYMBOLS杂项名师归纳总结 - - - - - - -第 3 页,共 4 页精选学习资料 - - - - - - - - - 读书之法 ,在循序而渐进 ,熟读而精思模块编译有时候需要通过检查内核编译选项 Kbuild 中,可以直接使用这些选项;例如:obj-$〔CONFIG_EXT2_FS〕 += ext2.o ext2-y := balloc.o bitmap.o dir.o CONFIG_option 打算哪些功能被编译进模块,在ext2-$〔CONFIG_EXT2_FS_XATTR〕 += xattr.o 通常情形下, $〔CONFIG_EXT2_FS〕 的值为 m,y 或未定义;为m 时,说明目标模块编译成内名师归纳总结 核模块,如未y,就目标编译进vmlinux ,如未定义,就目标不编译;第 4 页,共 4 页- - - - - - -。