一、为什么要编译源码?
先看看以下文章,描述的很详尽:
http://tscsh.blog.163.com/blog/static/20032010320131641225800/
http://zhidao.baidu.com/linkurl=jP0BO6nl4ZCSdEs_NNEELbQOK7IPu741vRiuONZPl5U5a1mKC8qUfuNivSKSJkOuV2L_jnqUX_G8vhNNHbLXka
http://www.ibm.com/developerworks/cn/linux/l-lkm/#icomments
二、编译启动系统内核
(1)下载源码:
在 www.kernel.org下载linux系统源码,格式为xxx.tar.xz。复制到/usr/src/下。
(2)查看当前的系统内核版本,解压缩文件:
uname -r 查看版本。在/usr/src/下,用xz 及tar解压缩命令:解压tar.xz文件:先 xz -d xxx.tar.xz 将 xxx.tar.xz解压成 xxx.tar 然后,再用 tar xvf xxx.tar来解包。
(3)配置内核:
- cd /usr/src/linux.2.6.xx.xx进入刚才解压缩的那个文件目录
- cp /boot/config- 按Tab键自动补全,再接着输入 .config,回车,目的是将当前运行版本的内核配置拷贝到新内核中。
- make menuconfig具体配置内核,这里我选择的默认,按ESC2次,退出保存。
(4)编译内核:
- make -j4 j后面的数字为编译时候开启的进程数,一般所需要的时间在1到4个小时。
(5)编译安装新内核模块:
- make module_install
(6)安装内核:
- make install
(7)生成启动项:
- sudo mkinitramfs -o /boot/initrd.img-2.6.36.61 //此处是我自己的新内核版本号
- sudo update -initramfs -c -k 2.6.36.61
- sudo -grub2 //自动修改系统引导配置,将新内核的启动项添加到grub.cfg启动文件中
三、编写两个简单模块
在当前的/home/ 目录下创建文件夹mkdir liulu_drivers,在liulu_drivers下创建两个文件夹hello和hello-1。
(1) hello/下包含有导出模块符号的被调用模块源文件hello.c以及Makefile文件:
>>>>>>>>>>hello.c文件代码>>>>>>>>>>
#include
#include
MODULE_LICENSE("Dual BSD/GPL");
int hello_num=5;
EXPORT_SYMBOL(hello_num);
static int hello_init(void)
{
printk(KERN_ALERT "Hello ,liulu.go ahead!\n");
return 0;
}
static void hello_exit()
{
printk(KERN_ALERT "Goodbye,cruel world!\n");
}
module_init(hello_init);
module_exit(hello_exit);
# 如果已经定义KERNELRELEASE,则说明是从内核构造系统调用的?嘛意思?
# 因此可以利用其内建语句
#ifneq ($(KERNELRELEASE),)
# obj-m :=hello.o
# 否则,是直接从命令行调用的
# 这时候要调用内核构造系统
#else
# KERNELDIR := /lib/modules/$(shell uname -r)/build
# PWD :=$(shell pwd)
#
#default:
#
# $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
#
#endif
#
#
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
obj-m :=hello.o
modules:
make -C $(KERNELDIR) M=$(PWD) modules
clean :
rm -rf *.o
rm -rf *.mod.*
(2) hello-1/下包含有调用hello.c的模块源文件hello-1.c以及Makefile文件:
#include
#include
MODULE_LICENSE("Dual BSD/GPL");
extern int hello_num;
int i=2;
module_param(i, int, S_IRUGO);
static int hello_init(void)
{
for(;i
Makefile文件:
#自由发挥中...
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
obj-m :=hello-1.o
KBUILD_EXTRA_SYMBOLS += /home/liulu/liulu_drivers/hello/Module.symvers
export $(KBUILD_EXTRA_SYMBOLS)
Modules:
make -C $(KERNELDIR) M=$(PWD) modules
Clean:
rm -rf *.o
rm -rf *.mod.*
(3)解释:两个文件的代码以及Makefile文件书本都有详细说明,在此不再赘述,只作大概的说明。
- hello.c中的hello_num为导出符号,导出符号可以时变量或者函数接口名称,使用EXPORT_SYMBOL(xxx); 申明。
- hello-1.ko中若要调用hello.ko导出的模块符号hello_num,需在hello-1.c中申明:extern int hello_num;
- 此外hello-1.c中还使用了模块传入参数,使用module_param(变量名,类型,S_IRUGO);申明。
- 关于Makefile的说明:$(uname -r)得到当前运行的内核版本,$(pwd)为当前的路径,其具体语法含义见书本。
四、模块导出符号及其调用方式:
在<三>中已经准备好了两个模块的源文件代码,这时候就可以进行模块符号的导出及调用了,其具体的执行过程为:
- hello.c代码中需申明导出符号hello_num
- hello-1.c中需使用extern引用hello-1.c的导出符号
执行模块导出符号的调用方式一:
1.先编译hello.c生成模块文件hello.ko,然后su之后,insmod hello.ko加载这个模块。
2.将hello/下编译生成的Module.symvers复制到hello-1/下,该文件包含有hello.c的导出符号对应地址,hello-1.ko需要这个文件来查找导出符号。
3.编译hello-1.c生成模块文件hell-1.ko,然后insmod hello-1.ko i=xxx加载这个模块,其中i=xxx为可选,i初始化为2,不传入参数也可以。
4.我的机器下没有直接打印出信息,使用dmesg查看系统日志,可以看到模块加载的打印信息。
注意:2与3的顺序一定不能反,否则编译找不到hello_num地址。
执行模块导出符号的调用方式二:
1.先编译hello.c生成模块文件hello.ko,然后su之后,insmod hello.ko加载这个模块。
2.在hello-1的Makefile文件中添加如下两行语句:
KBUILD_EXTRA_SYMBOLS+= /home/liulu/liulu_drivers/hello/Module.symvers
export $(KBUILD_EXTRA_SYMBOLS)
这两行语句显示的再编译时指明了所需要用的导出符号的地址,然后使用export导出。
3.编译hello-1.c生成模块文件hell-1.ko,然后insmod hello-1.ko i=xxx加载这个模块,其中i=xxx为可选,i初始化为2,不传入参数也可以。
4.我的机器下没有直接打印出信息,使用dmesg查看系统日志,可以看到模块加载的打印信息。
程序运行如图所示:
(1)insmod 两个模块,无提示,成功加载。
(2)dmesg查看系统日志:
(3)先rmmod hello-1,再rmmod hello,这跟模块注册注销的顺序一样,前后相反,再查看系统打印信息:
分析:在打开Module.symvers中可以看到如下信息
0x987c032a hello_num /home/liulu/liulu_drivers/hello/hello EXPORT_SYMBOL
而模块在需找导出符号的时候查看的路径其实还有系统usr/src/$(uname -r)/下的Module.symvers,打开这个文件,可以看到里面对应的都是各个导出符号的地址。也有网友介绍过将hello_num的这行语句直接添加到系统的Module.symvers文件中,这样就相当于hello_num全局通用,不需要上述两个步骤那样繁琐,遗憾的是,在我添加之后,导致系统编译报错,目前还不知道原因是什么。