1 .将嵌入式开发包的内核的源代码安装在 /usr/src 下,文件名为: linux-2.6.14.7-dma2410.tar.gz (我开发上用的)是一个压缩包,解压缩既可以得到整个内核的源代码:
# tar jxvf linux-2.6.14.7-dma2410.tar.gz
解压后生成一个新的目录 /usr/src/linux-2.6.14.7 ,所有的源代码都在该目录下。
注:该目录会因内核版本的不同而不同,各位动手实践的朋友只需知道自己的源代码所在的具体位置即可。
2 .配置及编译内核。
进入 /usr/src/ linux-source-2.6.14.7 目录下,可以看到 Makefile 文件,它包含了整个内核树编译信息。该文件最上面四行是关于内核版本的信息。对于整个 Makefile 可以不用做修改(跟我的编译环境一致),采用默认的就可以了。否则,按上述修改。
一般情况下,需要先用命令诸如 "make menuconfig", "make xconfig" 或者 "make oldcofig" 对内核进行配置,这几个都是对内核进行配置的命令,只是它们运行的环境不一样,执行一下这几个命令中的任何一个即可对内核进行配置:
Loadable module support ( 可加载模块选项 )
其选项如下:
[*] Enable loadable module support
这个选项可以让你的内核支持模块,模块是什么呢?模块是一小段代码,编译后可在系统内核运行时动态的加入内核,从而为内核增加一些特性或是对某种硬件进行支持。一般一些不常用到的驱动或特性可以编译为模块以减少内核的体积。在运行时可以使用 modprobe 命令来加载它到内核中去 ( 在不需要时还可以移除它 ) 。
一些特性是否编译为模块的原则是,不常使用的,特别是在系统启动时不需要的驱动可以将其编译为模块,如果是一些在系统启动时就要用到的驱动比如说文件系统,系统总线的支持就不要编为模块了,否在无法启动系统。 ( 当然还有一些变通的方法,我以后会提到 )
[ *] Module unloading
这个选项可以让你卸载不再使用的模块,如果不选的话你将不能卸载任何模块 ( 有些模块一旦加载就不能卸载,不管是否选择了这个选项 ) 。不选择这个选项会让你的内核体积减小一点
[ ] Module versioning support (EXPERIMENTAL)
这个选项将让你可以使用其它版本内核中编译的模块,不过并不可靠,所以一般我们不选择它
[*] Automatic kernel module loading
一般情况下,如果我们的内核在某些任务中要使用一些被编译为模块的驱动或特性时,我们要先使用 modprobe 命令来加载它,内核才能使用。不过,如果你选择了这个选项,在内核需要一些模块时它可以自动调用 modprobe 命令来加载需要的模块,这是个很棒的特性,当然要选 Y 喽: )
Kernel Feautre -> Preemptible Kernel
[*]
注:该项为 内核抢占式调度设置 , 必须保证在构建运行的内核与编译环境的内核时都选上,一般 PC 机运行的内核已经选上了,否则在 insmod 时将出现如下错误:
# insmod st7565p_driver26.ko
Using st7565p_driver26.ko
st7565p_driver26: version magic '2.6.14.7 ARMv4 gcc-3.4' should be '2.6.14.7 preempt ARMv4
gcc-3.4'
insmod: cannot insert `st7565p_driver26.ko': Invalid module format (-1): Exec format error
在内核源码的目录下执行:
# make
# make bzImage
其中,第一个 make 也可以不执行,直接 make bzImage 。这个过程可能要持续一个小时左右,因此是对整个内核重新编译了。执行结束后,可以看到在当前目录下生成了一个新的文件 : vmlinux, 其属性为 -rwxr-xr-x 。
然后执行:
# touch * // 时间或时区设置,源代码的时间戳比本机的时间更新,否则产生 :make[2]: 警告:检测到时钟错误。您的创建可能是不完整的。
# make modules 对内核的所有模块进行编译
# make modules_install 对内核的所有模块进行和安装
执行结束之后,会在 /lib/modules 下生成新的目录 /lib/modules/2.6.14.7 在随后的编译模块文件时,要用到这个路径下的 build 目录。至此,内核编译完成。
目录 “/usr/src/ 2.6.14 .7 ” 中就是所谓的 “ 内核代码树 ” 但是 “/lib/modules/ 2.6.14.7 /build” 是个符号链接,也指向这个目录,所以这里也可以叫做 “ 内核代码树 ” ,
可以重启一下系统。
3 .编写模块文件及 Makefile
也以 LDD3 上的 hello.c 为例:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk("hello,world./n");
return 0;
}
static void hello_exit(void)
{
printk("Good bye!/n");
}
module_init(hello_init);
module_exit(hello_exit);
Makefile 文件的内容为 :
obj-m := hello.o
#KERNELDIR:=/lib/modules/$(shell uname -r)/build
KERNELDIR:=/lib/modules/2.6.14.7/build // 注意与上面的不同
PWD:=$(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
# make -C $(KERNELDIR) M=$(PWD) clean
上面的 Makefile 是这样确定内核源码树所在的目录的 : 我们先到 /lib/modules 目录 , 会看到一些以内核版本为名的目录 , 目录中有一个 build 文件 , 它是一个符号连接 , 指向内核源码树 .
上面的例子中只讨论了所有的代码在一个文件中的情况 . 若代码分布在多个源文件中 , 比如 file1.c, file2.c, 生成 hello.ko. 应该这样写 Makefile:
obj-m := hello.o
hello-objs := file1.o file2.o
注意 , 虽然我们的目的是生成 .ko 文件 , 但在 Makefile 中写为 .o!
hello.c 和 Makefile 文件应该位于同一个目录下 , 可以放在 /home 下。
4 . : 编译和装载模块
在文件所处的目录下 , 执行 :
# make
然后查看该目录下有哪些文件生成 : 可见 , 已经生成模块文件 hello.ko.
然后 , 下载该文件到开发板,就可以加载该模块 :
运行命令:
# insmod hello.ko
应该可以看到返回的信息:Hello, world
然后再运行命令:
# rmmod hello
应该可以看到返回的信息:Goodbye !
以上讲的是在 在源码树之外编译模块,那么在源码树中呢?
在源码树中编译模块
官方内核模块的源代码都是按模块 ( 驱动 ) 类型组织的 , 我们到内核源码树的 drivers 目录可以看到 char, usb, block 之类的子目录 . 那么我们在内核源码树中添加文件时 , 最好也遵循这些分类 . 分类的规则自己灵活把握 .
下面以前面的 "hello, world" 这个简单的模块为例 , 来看看如何在内核源码树中编译模块 .
1, 不新建子目录
(1) 先在内核源码树中的 drivers 目录编辑一个 c 源程序 , 名为 hello.c.
(2) 修改 drivers 目录的 Makefile 文件 , 添加 : obj-m += hello.o
(3) 重新编译内核 ( 回到源码树根目录 , 运行 $ make ).
这样 , 在 drivers 目录多出了这样几个文件 : hello.mod.c, hello.mod.o, hello.o, hello.ko. hello.ko 就是编译出来的模块了 .
2, 新建子目录
如果源文件比较多 , 可以在 drivers 目录中新建子目录 . 还是以 hello, world 为例 :
(1) 在内核源码树的 drivers 目录中新建一个 hello 子目录 , 并将 hello.c 放在 hello 目录中 .
(2) 修改 drivers 目录的 Makefile 文件 , 添加 : obj-m += hello/
(3) 在 hello 目录中新建一个 Makefile 文件 , 内容为 : obj-m += hello.o
(4) 重新编译内核 ( 回到源码树根目录 , 运行 $ make ).
这样 , 新生成的模块文件就位于 hello 目录中 .
---------------------------
你 好,我最近刚接触linux驱动移植,试了好长时间才把“hello world”移成功,但一直有个疑问:在make时到底是调用的哪个编译器?gcc还是arm-linux-gcc?我尝试通过在Makefile中加 “CC = arm-linux-gcc”,但在最后查看内核信息时还是显示所用编译器为gcc!
---------------------------
你 如果按我上述方法做的话,那么你只要修改一下linux-2.6.14.7中的Makefile的 CROSS_COMPILE = /usr/local/arm/3.4.1/bin/arm-linux-就可以了,之后在写驱动时按我上述Makefile的写法,就是使用arm- linux-gcc编译器