>

加载和卸载,6内核的编译步骤及模块动态加载

- 编辑:www.bifa688.com -

加载和卸载,6内核的编译步骤及模块动态加载

本文是基于2.6的内核,也建议各位可以先看一下《Linux内核设计与实现第二版)》作为一个基础知识的铺垫。当然,从实践角度来看,只要按着以下的步骤去做也应该可以实现成功编译内核及加载模块。

来源:

学了那么多程序语言,总是有一个Hello world开头,不禁感叹Hello world的强大。呵呵,废话少说,咋们的故事当然要从这个Hello world开始。

个人用的Linux版本为:Debian GNU/Linux,内核版本为:2.6.20-1-686.

O'REILLY 写的《LINUX 设备驱动程序》中作者一再强调在编写驱动程序时必须 建立内核树。所谓内核树,我的理解和网上资料说的一致就是内核源码的一种逻辑形式。

先查看自己OS使用的内核版本
[dongliang@dongliang:~]$ uname -r
2.6.22-14-generic /* 这是我显示的结果 */

◆第一步,下载Linux内核的源代码,即构建LDD3Linux Device Drivers 3rd)上面所说的内核树。

先查看自己OS使用的内核版本 

如果安装系统时,自动安装了源码。在 /usr/src 目录下有对应的使用的版本目录。例如下(我是自己下的)
[root@localhost :/usr/src]# ls
linux-headers-2.6.22-14
linux-headers-2.6.22-14-generic
linux-source-2.6.22 /*这个就是解压后的源码目录 */
linux-source-2.6.22.tar.bz2 /* 这是我下的源码 包 */

如过安装的Linux系统中已经自带了源代码的话,应该在/usr/src目录下。如果该目录为空的话,则需要自己手动下载源代码。下载代码的方法和链接很多,也可以在CU上通过

shana@shana:~$ uname -r 
2.6.22-14-generic /* 这是我显示的结果 */ 

如果没有源码。(一般ubuntu 都没有吧)
查看一下可一下载的源码包(切记不要使用超级用户使用此命令否则……会提示没有此命令)
root@localhost :/usr/src# apt-cache search linux-source
linux-source - Linux kernel source with Ubuntu patches
xen-source-2.6.16 - Linux kernel source for version 2.6.17 with Ubuntu patches
linux-source-2.6.22 - Linux kernel source for version 2.6.22 with Ubuntu patches
root@localhost :/usr/src#

Debian下可以很方便的通过Debian源下载:

如果安装系统时,自动安装了源码。在 /usr/src 目录下有对应的使用的版本目录。例如下(我是自己下的) 

我选择了 linux-source-2.6.22 - Linux kernel source for version 2.6.22 with Ubuntu patches 这个~
然后 install 之

首先查找一下可下载的内核源代码:

shana@shana:/usr/src$ ls 
linux-headers-2.6.22-14 
linux-headers-2.6.22-14-generic 
linux-source-2.6.22 /*这个就是解压后的源码目录 */ 
linux-source-2.6.22.tar.bz2 /* 这是我下的源码 包 */ 
shana@shana:/usr/src$ 

root@localhost # sudo apt-get install linux-source-2.6.22

# apt-cache search linux-source

如果没有源码。(一般ubuntu 都没有吧) 
查看一下可一下载的源码包(切记不要使用超级用户使用此命令否则……会提示没有此命令) 

下载完成后,在/usr/src下,文件名为:linux-source-2.6.22.tar.bz2,是一个压缩包,解压缩既可以得到整个内核的源代码:

其中显示的有:linux-source-2.6.20,没有和我的内核版本完全匹配,不过也没关系,直接下载就可以了:

shana@shana:/usr/src$ apt-cache search linux-source 
linux-source - Linux kernel source with Ubuntu patches 
xen-source-2.6.16 - Linux kernel source for version 2.6.17 with Ubuntu patches 
linux-source-2.6.22 - Linux kernel source for version 2.6.22 with Ubuntu patches 
shana@shana:/usr/src$ 

注意 已经切换到超级用户模式

# apt-get install linux-source-2.6.20

我选择了 linux-source-2.6.22 - Linux kernel source for version 2.6.22 with Ubuntu patches 这个~ 
然后 install 之 

root@localhost:/usr/src# tar -jxvf linux-source-2.6.20.tar.bz2

www.bifa688.com,下载完成后,安装在/usr/src下,文件名为:linux-source-2.6.20.tar.bz2,是一个压缩包,解压缩既可以得到整个内核的源代码:

shana@shana:/usr/src$ sudo apt-get install linux-source-2.6.22 

解压后生成一个新的目录/usr/src/linux-source-2.6.22,所有的源代码都在该目录下。

# tar jxvf linux-source-2.6.20.tar.bz2

下载完成后,在/usr/src下,文件名为:linux-source-2.6.22.tar.bz2,是一个压缩包,解压缩既可以得到整个内核的源代码: 

进入该目录

解压后生成一个新的目录/usr/src/linux——source-2.6.20,所有的源代码都在该目录下。

注意 已经切换到超级用户模式 

开始配置内核 选择最快的原版的配置(默认)方式 (我是如此)

注:该目录会因内核版本的不同而不同,各位动手实践的朋友只需知道自己的源代码所在的具体位置即可。

root@shana:/usr/src#tar jxvf linux-source-2.6.20.tar.bz2 

root@localhost:/usr/src/linux-source-2.6.22# make menuconfig

◆第二步:配置及编译内核。

解压后生成一个新的目录/usr/src/linux-source-2.6.22,所有的源代码都在该目录下。 

当然你也可以使用 自己喜欢的配置方式 如 menuconfig , xconfig(必须有GTK环境吧)。反正不用剪裁什么,所以不管那种方式能配置它就行了。

进入/usr/src/linux——source-2.6.20目录下,可以看到Makefile文件,它包含了整个内核树编译信息。该文件最上面四行是关于内核版本的信息。对于整个Makefile可以不用做修改,采用默认的就可以了。

进入该目录 

完成后,开始make 吧 这儿比较久 一般有1一个小时吧。(保证空间足够 我编译完成后 使用了1.8G) 我分区时分给/目录30G的空间,我没遇到这问题。倒是我朋友遇到了。

一般情况下,需要先用命令诸如"make menuconfig", "make xconfig"或者"make oldcofig"对内核进行配置,这几个都是对内核进行配置的命令,只是它们运行的环境不一样,执行一下这几个命令中的任何一个即可对内核进行配置:

开始配置内核 选择最快的原版的配置(默认)方式 (我是如此) 

root@localhost:/usr/src/linux-source-2.6.22$ make

make menuconfig是基于界面的内核配置方法,make xconfig应该是基于QT库的,还有make gcofig也是基于图形的配置方法,应该是需要GTK的环境,make oldcofig就是对内核树原有的.config文件进行配置一下即可。

root@shana:/usr/src/linux-source-2.6.22# make oldconfig 

root@localhost:/usr/src/linux-source-2.6.22$ make bzImage

其实内核的配置部分,主要是保证内核启动模块可动态加载的配置,默认配置里面应该已经包含了这样的内容,因此,我用的是make oldconfig.

当然你也可以使用 自己喜欢的配置方式 如 menuconfig , xconfig(必须有GTK环境吧)。反正不用剪裁什么,所以不管那种方式能配置它就行了。 

当然,第一个make也可以不执行,直接make bzImage。执行结束后,可以看到在当前目录下生成了一个新的文件: vmlinux, 其属性为-rwxr-xr-x。

在内核源码的目录下执行:

完成后,开始make 吧 这儿比较久 一般有1一个小时吧。(保证空间足够 我编译完成后 使用了1.8G) 我分区时分给/目录30G的空间,我没遇到这问题。倒是我朋友遇到了。 

然后 :

# make

shana@shana:/usr/src/linux-source-2.6.22$ make 

root@localhost:/usr/src/linux-source-2.6.22# make modules /* 编译 模块 */

# make bzImage

shana@shana:/usr/src/linux-source-2.6.22$ make bzImage 

root@localhost:/usr/src/linux-source-2.6.22# make modules_install /* 安装 模块 */

其中,第一个make也可以不执行,直接make bzImage。这个过程可能要持续一个小时左右,因此是对整个内核重新编译了。执行结束后,可以看到在当前目录下生成了一个新的文件: vmlinux, 其属性为-rwxr-xr-x。

当然,第一个make也可以不执行,直接make bzImage。执行结束后,可以看到在当前目录下生成了一个新的文件: vmlinux, 其属性为-rwxr-xr-x。 

执行结束之后,会在/lib/modules下生成新的目录/lib/modules/2.6.22-14-generic/
。 在随后的编译模块文件时,要用到这个路径下的build目录。至此,内核编译完成。可以重启一下系统。

然后执行:

然后 : 

至此 内核树就建立啦 原来不是很难.....

# make modules

root@shana:/usr/src/linux-source-2.6.22#make modules /* 编译 模块 */ 

(1)linux开源当然少不了源代码的贡献,请看下边(至于什么是开源,悲剧的我现在也没整明白):

# make modules_install

root@shana:/usr/src/linux-source-2.6.22#make modules_install /* 安装 模块 */ 

#include <linux/init.h>  //所有模块代码中都包含一下两个头文件
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL"); //所有模块代码都应该指定所使用的许可证

static int hello_init(void)  
{  
      printk(KERN_ALERT "Hello,worldn");  
return 0;  
}   

static void hello_exit(void)  
{  
      printk(KERN_ALERT "Goodbye,Cruel worldn");  

}  

module_init(hello_init);  
module_exit(hello_exit);   

对内核的所有模块进行编译和安装。

执行结束之后,会在/lib/modules下生成新的目录/lib/modules/2.6.22-14-generic/ 
。 在随后的编译模块文件时,要用到这个路径下的build目录。至此,内核编译完成。可以重启一下系统。 

   看到这里我们明白了,驱动程序说白了就是提供函数接口给用户空间的程序调用。在c语言中都有main()入口,那设备驱动程序的入口在哪儿呢?你猜对了,就是module_init(),它的参数是一个函数指针,告诉说咋们的入口在hello_init()。明白这层意思,module_exit()就不用多说了吧..

执行结束之后,会在/lib/modules下生成新的目录/lib/modules/2.6.20/。 在随后的编译模块文件时,要用到这个路径下的build目录。至此,内核编译完成。可以重启一下系统。

至此 内核树就建立啦 原来不是很难..... 

(2)连源码都给你了,也就不吝啬一个makefile了如下:

◆第三步:编写模块文件及Makefile

写一个 最简单 最没用的驱动吧 
我在 /home/shana/linux_q/ 目录下创建2个文本文件 hello.c Makefile 

ifneq ($(KERNELRELEASE),)  
     obj-m := hello.o        #设置模块名字
else
     KERNELDIR :=/lib/modules/$(shell uname -r)/build   #将目录改为内核所在目录
     PWD := $(shell pwd)  
all:  
     $(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) modules  
endif  
clean:  
     rm -f *.o *.ko *.mod.c .hello*    

以LDD3上的hello.c为例:

//hello.c 
#include <linux/init.h> 
#include <linux/module.h> 
MODULE_LICENSE("Dual BSD/GPL"); 

千万不要说不懂makefile,大千世界,连地图都不懂,也不知道咋混的。这里要说的是 $(MAKE) 这里一定是大写MAKE,我开始小写,怎么都过不去,郁闷啊..

//hello.c
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
    printk(KERN_ALERT "Hello, worldn");
    return 0;
}

static void hello_exit(void)
{
    printk(KERN_ALERT"Goodbye, cruel worldn");
}

module_init(hello_init);
module_exit(hello_exit);

static int hello_init(void) 

printk(KERN_ALERT "Hello, worldn"); 
return 0; 

     $(MAKE) -C $(KERNELDIR) SUBDIRS = $(PWD) modules这句啥意思?就是说首先改变目录到-C选项指定的目录(即内核源代码目录),其中保存了内核的顶层makefile文件。SUBDIRS=选项让该makefile在构造modules目标返回之前到模块源代码目录。然后,modules目标指向obj-m变量设定的模块。(其实,这样的写Makefile命令还是有些烦人,可有啥办法呢,谁让咱们是笨鸟,聪明的方法?有,那要见下篇介绍-----聪明的makefile,嘿嘿)。

Makefile文件的内容为:

static void hello_exit(void) 

printk(KERN_ALERT"Goodbye, cruel worldn"); 

(3)好了,该有的都有了,不该有的咋一点也不贪。下面make一番:

obj-m := hello.o
KERNELDIR := /lib/modules/2.6.20/build
PWD := $(shell pwd)

modules:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

module_init(hello_init); 
module_exit(hello_exit); 

[root@localhost~]# make
make -C /lib/modules/2.6.29.4-167.fc11.i686.PAE/build SUBDIRS=/root/device modules
make[1]: Entering directory `/usr/src/kernels/2.6.29.4-167.fc11.i686.PAE'
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /root/device/hello.mod.o
  LD [M]  /root/device/hello.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.29.4-167.fc11.i686.PAE'

其中,hello.c和Makefile文件应该位于同一个目录下,可以放在/home下,我的两个文件都位于/home/david/.

程序我就不解释了…… 

这是生成了hello.ko模块(如果没有,是有八九就是makefile有问题,还说会写,露了原形吧),接下来可以看结果了

...

Makefile 文件 

首先再打开一个终端B(刚才make的那个不要关了,叫它A吧),像这样
[root@localhost~]# tail -f /var/log/messages 

obj-m := hello.o 
KERNELDIR := /lib/modules/2.6.20/build 
PWD := $(shell pwd) 

然后在A终端输入
[root@localhost~]# insmod ./hello.ko  哈哈在B终端是不是看到了localhost kernel:hello,world

modules: 
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules 

然后在A终端输入
[root@localhost ~]# rmmod hello 哈哈在B终端是不是看到了localhost kernel:Goodbye,Cruel world

modules_install: 
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install 

查看加载模块
[root@localhost ~]# lsmod
Module Size Used by
hello 2560 0
已经加载上咯~~

如果以上你都完成了在 make 时出现这样的错误 

那程序的输出在那呢?书中说明 如果不出现在终端 则会写进 syslog 文件中
dongliang@dongliang:~/linux_驱动开发$ cat /var/log/syslog | grep world
Mar 16 12:14:53 shana kernel: [ 5937.529297] Hello, world
Mar 16 12:16:05 shana kernel: [ 6009.439036] Goodbye, cruel world

shana@shana:~/linux_驱动开发$ make 
make: 没有什么可以做的为 `modules'。 

   至此,一个最简单的helloworld的设备驱动演示程序就完成了,是不是挺好玩,关键是挺兴奋,这才是关键。哈哈

原因很简单 你肯定是从我这直接复制的吧~~~呵呵,Makefile格式错误啦~ 
解决办法就是 你把如 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install 移动到行首 然后按Tab 键自动对齐 
这时里边的变量都变成绿色了~然后在 make 吧 

   当工人就得劳动;当军人就的准备打仗(不然某些人老是欺负咋们了不是),所以嘛,当程序员,就一定要在最后来个说明注意什么的,烦躁啊..

shana@shana:~/linux_驱动开发$ make 
make -C /lib/modules/2.6.22-14-generic/build M=/home/shana/linux_驱动开发 modules 
make[1]: Entering directory `/usr/src/linux-headers-2.6.22-14-generic' 
CC [M] /home/shana/linux_驱动开发/hello.o 
Building modules, stage 2. 
MODPOST 1 modules 
CC /home/shana/linux_驱动开发/hello.mod.o 
LD [M] /home/shana/linux_驱动开发/hello.ko 
make[1]: Leaving directory `/usr/src/linux-headers-2.6.22-14-generic' 
shana@shana:~/linux_驱动开发$ 

那就说明吧:
(1) 这个helloworld,我建议在fedora 下实现,在centos或redhat或那些我没试过的,是有些问题的。    这主要是要在某些系统下是不支持模块的,这就要重新编译内核,选上“Enable loadable module        support”,这样才可以。你偏要忘北走,我也不能用个绳子套着你喝水是吧,不管了,反正我今天关心的helloworld完成了,这难道就是传说中的责任分工,那个?呵呵,等着,我改天一定不上。
(2) 在编写makefile时,所有的换行后的空格都是tab键,而不是space(空格键)。
(3) 本例程的文件保存为 hello.c  ,不然编译不通过。

shana@shana:~/linux_驱动开发$ ls -l 
总用量 124 
-rw-r--r-- 1 shana shana 303 2008-03-16 10:43 hello.c 
-rw-r--r-- 1 shana shana 49039 2008-03-16 12:11 hello.ko 
-rw-r--r-- 1 shana shana 687 2008-03-16 12:11 hello.mod.c 
-rw-r--r-- 1 shana shana 25840 2008-03-16 12:11 hello.mod.o 
-rw-r--r-- 1 shana shana 24360 2008-03-16 12:11 hello.o 
-rw-r--r-- 1 shana shana 8344 2008-03-16 09:17 linux_qudong_qu.txt 
-rw-r--r-- 1 shana shana 266 2008-03-16 12:09 Makefile 
-rw-r--r-- 1 shana shana 0 2008-03-16 12:11 Module.symvers 
shana@shana:~/linux_驱动开发$ 

学习心得:
(1)驱动模块运行在内核空间,运行时不能依赖于任何函数库和模块连接,所以在写驱动时所调用的函数只能是作为内核一部分的函数。
(2)驱动模块和应用程序的一个重要不同是:应用程序退出时可不管资源释放或者其他的清除工作,但模块的退出函数必须仔细撤销初始化函数所作的一切,否则,在系统重新引导之前某些东西就会残留在系统中。
(3)处理器的多种工作模式(级别)其实就是为了操作系统的用户空间和内核空间设计的。在Unix类的操作系统中只用到了两个级别:最高和最低级别。
(4)要十分注意驱动程序的并发处理。
(5)内核API中具有双下划线(_ _)的函数,通常是接口的底层组件,应慎用。
(6)内核代码不能实现浮点书运算。

然后加载模块 (超级用户) 

root@shana:/home/shana/linux_驱动开发# insmod ./hello.ko 

按照书上的例子 会在终端显示 hello , world 但是运行后什么都没有出现 (原因不解) 

root@shana:/home/shana/linux_驱动开发# insmod ./hello.ko 
root@shana:/home/shana/linux_驱动开发# 

查看加载模块 

root@shana:/home/shana/linux_驱动开发# lsmod 
Module Size Used by 
hello 2560 0 

已经加载上咯~~ 

删除模块 

root@shana:/home/shana/linux_驱动开发# rmmod hello 
root@shana:/home/shana/linux_驱动开发# 

那程序的输出在那呢?书中说明 如果不出现在终端 则会写进 syslog 文件中 

shana@shana:~/linux_驱动开发$ cat /var/log/syslog |grep world 
Mar 16 12:14:53 shana kernel: [ 5937.529297] Hello, world 
Mar 16 12:16:05 shana kernel: [ 6009.439036] Goodbye, cruel world 
shana@shana:~/linux_驱动开发$ 

 

补充:printk输出可以在dmesg里看到。

本文由bifa688.com发布,转载请注明来源:加载和卸载,6内核的编译步骤及模块动态加载