1.感谢这篇文章,让折腾了好久的我实现了内核模块加载
2.为什么要构造源码树?
我们做 Linux 开发一般是在PC机上编译好,下到板子上去运行,板子上的 Linux 内核和 PC 机上的 Linux 版本很多时候都是不一样的,比如:pc机上的是 Linux 2.6,板子上的系统是 Linux 3.1,这个时候就要在pc本地编译环境中下载 Linux 3.1 的内核,用它编译出驱动模块,从而在板子上加载,不然会因为内核版本不一致而出错。如果只是在PC上运行,内核模块不是用在板子上去,是不用下载内核源码树和编译内核源码的。
3.驱动程序和用户程序不一样,它是作为一个模块连接到内核模块来运行的,运行在内核空间里面。所以要运行我们自己构造的模块,需要自己的系统已经配置好内核树,然后把目标模块和内核树连接起来运行!
如何查看自己的 ubuntu 系统中是否已经有了内核源码树了?直接去查看 /lib/modules/ 目录下是否存在 build 目录,如果存在,则表示我们自己的系统已经拥有内核源码树了(我的本地编译环境 ubuntu14.04 是拥有内核源码树的),如下所示。
4.但为了熟悉整个的过程,我们也重新下载对应自己的编译环境的“内核源码”并对其进行编译,构造内核源码树。首先要查看自己使用的系统“内核版本 ”:
我用的是 Ubuntu 14.04
查看自己 ubuntu 系统的版本:有两种方法,第一种,cat /etc/issue ; 第二种,lsb_release -a 命令
5.内核源码树构建步骤:
(1)安装编译内核所需的软件(要用 make menuconfig 命令的话得安装,用 make oldconfig 的话就不用安装,不论采用哪种生成配置文件的方式,都在系统中安装下面的软件)
sudo apt-get install build-essential kernel-package libncurses5-dev fakeroot
(2)下载内核源码,执行上面的命令 sudo apt-cache search Linux-source ,系统会给出适合我们所安装系统的“内核源码版本 ”
(3)使用命令 sudo apt-get install linux-source-3.13.0 下载安装该“内核源码 ”
(4)解压内核源码包:进入 /usr/src/ 目录,能找到 linux-source-3.13.0.tar.bz2 文件,用解压命令 sudo tar xjvf linux-source-3.13.0.tar.bz2 解压。要注意上面目录下的 linux-source-3.13.0.tar.bz2 文件并不是软件压缩包文件,而是一个软链接,不能直接使用 tar 进行解压。
进入 linux-source-3.13.0 文件夹,发现真正的 tar 源码压缩包,解压。
(5)对“内核源码”进行编译前的配置从下面截图可以看出,在解压后,该文件夹下并没有 .config 文件。
进入源码解压后的文件夹里,为了方便,可以直接在使用 make menuconfig 命令打开可视化配置窗口,然后什么都不要修改的退出。
在运行完 sudo make menuconfig 命令后并不会像上面那样执行完毕,生成一个 .config 文件,而是会先打开下面的可视化配置,毕竟我们的命令是 menuconfig(菜单配置)。
什么都不要操作,直接 Esc 退出,选择 Save,从而可以在该文件夹下生成一个 .config 编译配置文件。
.config 配置文件如下图所示:
(6)使用 sudo make 命令进行内核编译,当然不只是下面截图那样简单,整个内核编译过程是以小时计算的,编译吧,linux君,我玩会再来看你。
编译完,该文件夹下就生成了一个名为 vmlinux 的文件了,但网上的教程大都说要执行完下面的 sudo make bzImage 命令才会生成 vmlinux 文件。
(7)sudo make zImage 命令在我的本地编译环境下是不能使用的。
(8)使用命令 sudo make bzImage 后,下面两个截图对其过程的中间过程省略。
(9)使用 sudo make modules 命令编译模块
(10)使用 sudo make modules_install 命令安装模块,执行完后,会在 /lib/modules 目录下生成一个新的目录 /lib/modules/3.13.0/ 。至此,内核就编译完成了。
(11)由于我的本地 ubuntu 编译环境中的内核版本就是为 3.13.0-32-generic,所以 /lib/modules/3.13.0-24-generic 本身就存在,所以其实我们这里的 sudo make modules 和 sudo make modules_install 这两步是不需要执行的。
(12)但我们这里还是执行了 sudo make modules 和 sudo make modules_install 命令,看上面的截图也就能判断出,其实,在我们执行完 sudo make moduels 和 sudo make modules_install 命令后编译生成了目录 /lib/modules/3.13.11-ckt39
6.现在可以编写 hello world 程序并加载该内核模块了
下面是 hello.c 文件:
7.下面是是 Makefile (必须为这个文件名,连 makefile 都是不能直接使用 make 命令来进行编译的)文件,解释下该文件,加深理解。
(1)obj-m 的意思是将后面的东东编译为“内核模块”,相对应的还有 obj-y 是编译进内核,obj-n 是不编译。
(2)KERNELDIR 是 Makefile 文件中的一个内容标识,这里标识内核源码目录,目录中包含了内核驱动模块所需要的各种头文件及依赖。
(3)-C 表示让 make 命令进入指定的目录,这里即 KERNELDIR ,是内核源代码目录,调用该目录顶层下的 Makefile,目标为 modules。
(4)M=$(PWD) 选项是让该 Makefile 在构造 modules 目标之前返回到模块源代码目录,并在当前目录下生成 obj-m 指定的 xxx.o 目标模块。
(5)三种赋值等号的区别 ?= ,=,:=
“:=”表示:它的右边如果为变量,那么该变量在这条语句之前就要定义好,而不能在使用这条语句之后定义的变量
“=”表示:当它右边如果变量时,这个变量可以在这条语句之前或者之后使用
“?=”表示:当它左边的变量在这条语句之前没有定义过,则执行本条语句,如果已经定义了,则什么都不做。
所以下面截图中的 Makefile 文件中三种赋值都是可以的。
(6)modules 和 modules_install 是 make 需要执行的命令
(7)整理到这里,请继续先阅读:
(8)make 定义了很多默认变量,像常用的命令或者是命令选项之类的,什么 CC ,什么 CFLAGS,$(MAKE) 就是预设的 make 这个命令的名称(或者是路径)。make -p 可以查看所有预定义的变量的当前值。看下面的截图的最后一行就可以看出, $(MAKE) 变量其实就是 make 命令。
8.对驱动程序进行编译的时候,发现 /lib/modules/ 目录下的两个库文件夹都是能用的(在前面说过,其实安装好的 ubuntu本地编译环境是拥有内核源码树的)。
(1)在写好 Makefile 文件的驱动程序目录下,可以直接使用用户权限执行 make 命令编译。
(2)CC是编译
(3)LD是链接