在ubuntu 10.10上构建内核树
我们平时使用的用户程序和驱动程序不一样,驱动程序作为一个模块连接到内核模块并运行在内核空间里。引用LDD上的一句话“因为2.6内核的模块要和内核源代码树中的目标文件连接,通过这种方式,可得到一个更加健壮的模块加载器,但是需要这些目标文件存在于内核目录树中”。这里提到的内核目录树就是我们在运行我们自己构造的模块前,需要在我们的系统中已经配置好内核源代码树,然后在把构造好的目标模块和内核树连接起来再运行。
查看自己的系统里有没有配置好内核树的方法:在/lib/modules/2.6.35-33-generic目录下面,看看有没有build文件夹,如果有的话,说明我们的系统里已经有内核树了,如果没有的话,就需要自己构建一个内核树了。
经过我的实践操作并参考相关的资料,我总结的内核树构建步骤如下:
1、安装编译内核所需的软件(要用 make menuconfig命令的话得安装,用make oldconfig的话就不用安装)。
-
代码:
-
sudo apt-get install build-essential kernel-package libncurses5-dev fakeroot
2、下载内核源码
-
代码:
-
apt-cache search linux-source
-
或者在http://www.kernel.org/下载内核源码
执行用这条命令系统会提示你安装适合你内核版本的内核源码
-
代码:
-
apt-get install Linux-source-2.6.35
执行这条命令就会自动下载并安装适合我系统内核的Linux-source-2.6.35这个内核源码
3、解压内核源码包
进入/usr/src/,能找到linux-source-2.6.35.tar.bz2,用解压命令解压之。
-
代码:
-
tar jxvf linux-source-2.6.32.tar.bz2
4、拷贝/boot目录下的config-2.6.35-30-generic到刚才解压好的目录下并改名为.config
-
代码:
-
sudo cp /boot/config-2.6.35-30-generic /usr/src/linux-source-2.6.35/.config
5、切换到root用户,进行内核配置
-
代码:
-
sudo -i
cd /usr/src/linux-source-2.6.35
make menuconfig
接着会出现一个配置界面,选择最后面的两个选项:
load an Alternate configuration File 和 save an Alternate configuration File
分别保存并退出,再退出配置环境。
6、编译内核
这个内核的编译要在管理员账号下运行
-
代码:
-
#cd /usr/src/linux-source-2.6.35
#make
如果电脑是双核的话可以在make后面加个参数,例如:
-
代码:
-
make -j4
make的过程时间比较长,我在虚拟机里编译用了2小时左右。。。
再执行
-
代码:
-
#make bzImage
结束后,可以看到在当前目录下产生一个vmlinux文件。
7、编译模块
在编译模块时候可能会出现如下问题:
(ld: /ubuntu/omnibook/sections.lds: No such file: No such file or directory)
解决方法是:
在/usr/src/linux-source-2.6.35/ubuntu/omnibook/Makefile中的ifeq($(KERNELRELEASE),)的前面增加一句:
-
代码:
-
PWD=$(shell pwd)
然后再开始编译模块
-
代码:
-
#make modules
再执行
-
代码:
-
make modules_install
命令后在/lib/modules目录下面产生一个目录
8、测试Hello World 模块
很多编程书都会以一个"hello world"示例程序来说明最简单的程序,但是LDD里面讲的是内核模块而不是程序,所以这个模块里的函数都是内核函数,有别于平时用C编写时用到的函数。
-
代码:
-
//Hello.c
#include
#include
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT"Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
-
代码:
-
//Makefile
# Makefile2.6(2.6内核专门的Makefile文件)
ifneq ($(KERNELRELEASE),)
#kbuild syntax. dependency relationshsip of files and target modules are listed here.
mymodule-objs := hello.o #param-objs := file1.o file2.o
obj-m := hello.o #obj-m := param.o
else
PWD := $(shell pwd)
KVER ?= $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
[TAB]$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
[TAB] rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
endif
这里编译模块的Makefile我是参考了网上的资料,比起LDD上的Makefile我不太清楚的是这个Makefile为什么是2.6内核专门的Makefile文件,不知道特别在哪里,LDD上的简短多了;还有一个问题是mymodule-objs := hello.o这句是起什么作用的,与obj-m :=hello.o作用有什么区别呢?一句obj-m :=hello.o我感觉足够了,不知道对不对
这个Makefile的大概意思是: #KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容。如果make的目标是clean,直接执行clean操作,然后结束。当make的目标为all时,-C $(KDIR)指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明在构造modules目标之前返回到当前的模块源代码的目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句,指明模块源码中各文件的依赖关系,以及要生成的目标模块名。
需要注意的是Makefile的格式,在all:下面的$(MAKE)前面和clean下面的rm前面要按一次TAB键,否则会在make的时候出现错误:“Makefile:xx:***遗漏分隔符。停止”xx是出现问题的第几行。
make 编译成功的话,用ls -al查看可以发现在linux_modules目录下产生了如下文件:
hello.c hello.mod.c hello.o modules.order
hello.ko hello.mod.o Makefile Module.symvers
其中的hello.ko就是可加载的模块文件。
现在就可以加载helloworld模块到内核中去:
-
代码:
-
#insmod ./hello.ko
//这个命令把hello.ko加载到内核
-
代码:
-
#lsmod|grep hello
//lsmod 这个命令可以查看当前所有的驱动模块,结果应该显示hello 692 0
-
代码:
-
#rmmod hello
//这个命令是把hello这个模块移除掉
由于printk不会把结果输出到终端中,所以用如下命令查看结果:
-
代码:
-
dmesg |grep world
或者打开/var/log/syslog文件查看;
模块的输出结果如下:
Hello,World
Goodbye,cruel world
LDD上有提到,在输入insmod ./hello.ko时可能出现如下错误提示:insmod: error inserting 'hello.ko': -1 Invalid module format。
查到的资料说因为记载版本号的字符串和当前正在运行的内核模块的不一样,这个版本印戳作为一个静态的字符串存在于内核模块中,叫vermagic。
如果出现这种情况:
1、根据
-
代码:
-
modinfo hello.ko
命令查看vermagic的值
2、根据
-
代码:
-
uname -r
查看内核版本
3、对比上边两步的值是否相同
若不同,可以采用如下命令来解决这个问题:
-
代码:
-
make -C /lib/modules/此处为你内核版本号对应的文件夹/build M=你模块源码的路径 modules
最后提几个学习方法的问题,关于字符设备驱动的学习,相比C语言的操作有很多不同,我不知道哪些知识点是主干,很多时候看LDD上面的内容很晦涩,知识点看得模棱两可,导致看起来进度比较慢,请教各位大牛,该怎么正确的去学习这本书才能提高效率,少走弯路,达到学以致用的目的,实际上机操作的时候要注意什么?