您的位置:首页 > 运维架构 > Linux

Linux驱动环境配置之内核树的建立

2015-11-13 14:18 405 查看
原文转载至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





去掉上图中的*

保存退出。(用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

view
plaincopy
to clipboard

#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);

[cpp] view
plaincopy

#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

view
plaincopy
to clipboard

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

[cpp] view
plaincopy

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: