原文转载至http://blog.csdn.net/xuxinyl/article/details/6996433
首先,为什么要建立内核树呢?因为我们知道驱动可以编译进内核进行执行,也可以以模块的方式加载到内核里进行执行。编写驱动程序在2.6版本的内核与 2.4版本的内核有所不同,2.4版本的内核只需要有一套内核头文件就够了,而2.6的内核的模块则要和内核源代码树中的目标文件相连接,这样,就需要一个建立一个内核树,来提供上面这些目标文件。所以首先我们要建立一个内核源代码树,而这个内核源代码树的建立有两种方式:一,直接用发行版的内核源代码包建立(也就是你装的Linux 系统);二,用www.kernel.org上下的纯净版内核构造,这里选择第二种方式。因为,发行版厂商提供的内核源代码包通常打了许多补丁,从而和你从 www.kernel.org上下的内核存在很大的差异。在某些情况下,有可能厂商的补丁会修改设备驱动程序使用的内核API。而我们最终要一直 Linux系统到开发板上去,我们不会去移植发行版提供的内核,我们只会移植从www.kernel.org下的内核,所以不论是从学习的角度,还是从以后移植Linux的角度,我们都选择第二种办法。好了,废话少说,下面开始叙述建立内核树的详细过程。
首先,用uname -r 查看当前运行内核的版本:
[root@localhost ~]# uname -r
2.6.18-53.el5
[root@localhost ~]#
然后,从www.kernel.org上下一个和你当前运行版本一样的内核,这里可以看到我当前运行的内核版本是2.6.18-53.el5,好,那我就下一个2.16.18.x的版本(必须有和当前运行的版本有同样的版本号,否则在加载模块时候insmod: error inserting '***.o' :-l invalid module format这样的错误)。
下下来之后放在一个目录下,我存放的目录为:
[root@localhost linux]# pwd
/home/linux
解压:
[root@localhost linux]# tar xvzf linux-2.6.18.tar.gz
解压完之后会生成linux-2.6.18目录。进入这个目录:
[root@localhost linux]# cd linux-2.6.18
拷贝当前系统的.config文件到这个目录下:
[root@localhost linux-2.6.18]# cp /usr/src/kernels/2.6.18-53.el5-i686/.config ./(ubantu版本下在/boot/config-2.6.32-28-generic-pae)
运行makemenuconfig
[root@localhost linux-2.6.18]# make menuconfig
![学习Linux驱动第一步:内核树的建立](http://img.huomo.cn/article/7de/69398/1.jpg)
![学习Linux驱动第一步:内核树的建立](http://img.huomo.cn/article/7de/69398/2.jpg)
去掉上图中的*
保存退出。(用ESC键退出,Y保存)
执行make
[root@localhost linux-2.6.18]# make
时间一个多小时,可以去看电影去了。
[root@localhost linux-2.6.18]# make bzImage(此处i要大写,编译压缩形式的内核)
[root@localhost linux-2.6.18]# make modules (编译选择的模块)
[root@localhost linux-2.6.18]# make modules_install (将编译后的模块转移到系统标准位置,模块在系统中的标准目录位于/lib/modules/x.y.z,后面的x.y.z是版本号,为安全起见,在运行#make modules-install之前最好对/lib/modules进行备份。模块通常是带有扩展名.o的文件,使用命令#lsmod可以对当前内核的模块进行列表。 )
可以看到在
[root@localhost code]# cd /lib/modules/
生成了2.6.18目录
[root@localhost modules]# ls
2.6.18 2.6.18-53.el5 2.6.18.8
下面编写一个hello模块
[root@localhost code]# gedit hello.c
- #include <linux/init.h>
- #include <linux/module.h>
- MODULE_LICENSE("Dual BSD/GPL");
- static int hello_init(void)
- {
- printk(KERN_EMERG "Hello, li wei meng/n");
- return 0;
- }
- static void hello_exit(void)
- {
- printk(KERN_EMERG"Goodbye, li wei meng/n");
- }
- module_init(hello_init);
- module_exit(hello_exit);
- #include <linux/init.h>
- #include <linux/module.h>
- MODULE_LICENSE("Dual BSD/GPL");
- static int hello_init(void)
- {
- printk(KERN_EMERG "Hello, li wei meng/n");
- return 0;
- }
- static void hello_exit(void)
- {
- printk(KERN_EMERG"Goodbye, li wei meng/n");
- }
- module_init(hello_init);
- module_exit(hello_exit);
这个模块什么也不干,只是在模块加载时打印一条信息,卸载时也打印一条信息。麻雀虽小,但五脏具全,MODULE_LICENSE()指明认证方式,现在支持的有:“GPL” “GPL v2" "GPL and additional rights" "Dual BSD/GPL" "Dual MIT/GPL" "Dual MPL/GPL" "Proprietary",这是内核2.6里新添加的,实验发现它不是必需的。module_init()指明模块的入口,这是必需的;module_exit()指明模块的出口,这也是必需的。
同时编写Makefile,在同一目录下:
[root@localhost code]# gedit Makefile
- ifneq ($(KERNELRELEASE),)
- obj-m := hello.o
- else
- KDIR := /lib/modules/2.6.18/build
- all:
- make -C $(KDIR) M=$(PWD) modules
- clean:
- rm -f *.ko *.o *.mod.o *.mod.c *.symvers
- endif
- ifneq ($(KERNELRELEASE),)
- obj-m := hello.o
- else
- KDIR := /lib/modules/2.6.18/build
- all:
- make -C $(KDIR) M=$(PWD) modules
- clean:
- rm -f *.ko *.o *.mod.o *.mod.c *.symvers
- endif
其中KDIR指明了引用头文件的位置,请根据具体情况修改该文件。不过有几点要说明,模块的编译需要有一个内核源码的目录结构,如果有内核源码当然更好,直接把KDIR改为源码的路径就OK了。当然只有一个内核目录结构也就可以了,毕竟需要的只是头文件,及一些编译脚本,在Linux各发行版的/usr/src/下是有这样一个目录结构的,比如我的系统为Ubuntu9.10,内核为2.6.31-19的通用版,在 /usr/src/下就有linux-headers-2.6.31-19-generic这样一个目录。所以我使用KDIR=/usr/src /linux-headers-2.6.31-19-generic。其实还有一个目录名为linux-headers-2.6.31-19,而且你会发现linux-headers-2.6.31-19-generic里的大部分文件只是指向linux-headers-2.6.31-19的一些链接,不过linux-headers-2.6.31-19里面默认缺少一些编译模块所需要的文件,不要使用它。 (切记:Makefile每个命令的第一个字符必须是制表符[tab]即KDIR,make,rm前不是四个空格空格开头而是一个[tab],不要想当然的随便认为只是个空格。否则会在编译是报出***missing seperater .stop. 的错误 ) 执行make
[root@localhost code]# make
make -C /lib/modules/2.6.18/build M=/home/code modules
make[1]: Entering directory `/home/linux/linux-2.6.18'
CC [M] /home/code/hello.o
Building modules, stage 2.
MODPOST
CC /home/code/hello.mod.o
LD [M] /home/code/hello.ko
make[1]: Leaving directory `/home/linux/linux-2.6.18'执行insmod
[root@localhost code]# insmod hello.ko
[root@localhost code]#
Message from syslogd@ at Sun May 23 21:02:30 2010 ...
localhost kernel: Hello, li wei meng
使用 insmod ./hello.ko来加载模块,如果你遇到insmod: error inserting 'hello.o' :-l invalid module format这样的错误,不用惊慌,并不是你的模块有问题,而是你当前运行的内核版本与你编译链接的头文件版本不一致,所以会出现格式不对的问题。此时,你可以更换系统内核,也可以下载一个与系统版本一致的内核源码重新编译该模块。如果你没有遇到任何问题,也没有打印出任何信息,那么恭喜你,你的内核加载成功了,你可以使用lsmod命令来罗列出当前你系统加载的所有模块,相信你在列表中会找到hello的。你可能会疑惑,为什么没有如我们想像中的那样,打印出"Hello, World"?呵呵,这是因为printk并不会把打印内容打印到你当面的终端,要知道模块是运行在内核态的,而你所能面对的是用户态,内核态的打印信息需要通过log或者dmesg命令来查看,想看到打印結果,最简单的方法是敲入dmesg命令,你就可以看到你所希望看到的信息了,同时,你也可以打开 /var/log/message这个文件进行查看。
执行rmmod
[root@localhost code]# rmmod hello.ko
[root@localhost code]#
Message from syslogd@ at Sun May 23 21:02:46 2010 ...
localhost kernel: Goodbye, li wei meng
最后,使用rmmod hello来卸载模块,同样,使用dmesg可以看到打印出的"Goodbye, cruel world"。
附:
Linux 有许多功能是通过模块的方式, 在需要时才载入 kernel。 如此可使 kernel 较为精简,进而提高效率,以及保有较大的弹性。这些可动态加载的模块,通常是系统的设备驱动程序。加载模块采用 insmod 指令,其常用语法如下。
insmod [-fkmpsvxX] [-o<模块名称>] [模块文件]
其中的参数解释如下。
-f:不检查目前 kernel 版本与模块编译时的 kernel 版本是否一致,强制将模块载入。
-k:将模块设置为自动卸载。
-m:输出模块的载入信息。
-p:测试模块是否能正确地载入 kernel。
-s:将所有信息记录在系统记录文件中。
-v:执行时显示详细的信息。
-x:要汇出模块的外部符号。
-X:汇出模块所有的外部符号,此为预设置。
• rmmod(卸载模块)指令
Linux 把系统的许多功能编译成一个个单独的模块,待有需要时再分别加载它们,如果不再需要这些模块的时候,就可以使用 rmmod 命令来卸载这些模块。其语法如下。
rmmod [-as] [模块名称...]
其使用参数说明如下。
-a:删除所有目前不需要的模块。
-s:把信息输出至 syslog 常驻服务,而非终端机界面
附:参考文档http://blog.chinaunix.net/link.php?url=http://www.91linux.com%2Fhtml%2Farticle%2Fkernel%2F20071204%2F8805.html
http://www.huomo.cn/os/article-10f16.html
http://blog.csdn.net/liangkaiming/archive/2010/10/13/5937929.aspx