内核编程入门:简析 linux modules
这是篇Linux编程入门级的文章。
1 什么是module?
什么是module?打一个简单的比方,要是把Linux内核比成一个书架,书架上所有的书的内容就是就是内核所提供的功能,那我们每把一本书放进书架就相当于像内核插入一个module。要是插入一本有内容独立的书,那么内核就相当于插入一个功能独立的module。整个书架的书描述的内容增加了,整个内核支持的东西也增加了。拿走一本书就相当于卸载一个module,书架里的所有的书总体描述的内容减少了,内核支持的东西也减少了。当然书里面可以没有任何内容,module也是如此,可以不提供任何功能,甚至module可以轻易的屏蔽其他内核里面的功能,做rootkit时常用。
这样,我们总结出,可以动态的(不用重新编译内核的情况下)修改内核的功能的程序,称为模块(module)。
2 模块的写法
2.4的内核模块的编写和2.6有点点区别,这里顺便也说明一下。
a 内核模块的写法
内核module与一般的C应用程序从代码比较明显的区别就是入口点函数不一样(对于内核模块运行与内核空间,应用程序运行于用户空间我们这里这篇文章先不进行讨论)。下面我先比较一下一个普通的C程序中打印helloworl与 在2.6版本的内核中打印helloworld.o有什么不同。
打印Helloworld普通C应用程序源码如下:
#include <stdio.h>
/*
+—————————————————————————————+
| 红毛羽的博客: http:/blog.csdn.net/hongmy525 |
| Power by hongmy525 E-Mail:hongmy525@163.com |
+—————————————————————————————+
*/
int main(int argc, char * argv[] )
{
ptintf(" Hello,World!/n");
ptintf(" MyBlog http:/blog.csdn.net/hongmy525/n");
return 0;
}
内核的版本为2.6的情景下打印helloworld的模块的源码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
/*
+—————————————————————————————+
| 红毛羽的博客: http:/blog.csdn.net/hongmy525 |
| Power by hongmy525 E-Mail:hongmy525@163.com |
+—————————————————————————————+
*/
static int My_init(void)
{
printk(KERN_ALERT "Hello,World!/n");
return 0;
}
static void My_exit(void)
{
printk(KERN_ALERT "This is my first module!/n GoodBye Now!/n");
ptintk(KERN_ALERT "MyBlog http:/blog.csdn.net/hongmy525/n");
}
module_init(My_init);
module_exit(My_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Hongmy525");
这是内核版本为2.6的内核中内核模块的写法,当你把它编译好装载的时候由宏module_init调用函数My_init, My_init 这个函数是你的模块的入口,在这里My_init的功能是打印“Hello,World!”!当你卸载模块的时候由module_exit宏调用My_exit函数,这是函数的出口。退出时打印"This is my first module!/n GoodBye Now!"。这里使用的是printk而不是printf,因为在内核中不能直接这样调用C库的函数。头文件module.h和kernel.h是内核模块必须的投文件,MODULE_LICENSE("GPL")表示说这个模块遵循GPL规则,MODULE_AUTHOR是指模块的作者。
以前2.4的内核模块中,
#include <linux/module.h>
/*
+—————————————————————————————+
| 红毛羽的博客: http:/blog.csdn.net/hongmy525 |
| Power by hongmy525 E-Mail:hongmy525@163.com |
+—————————————————————————————+
*/
static int init_module(void)
{
printk( "Hello,World!/n");
return 0;
}
static int cleanup_module(void)
{
printk("This is my first module!/n GoodBye Now!/n");
ptintk("MyBlog http:/blog.csdn.net/hongmy525/n");
return 0;
}
很显然,主要的入口函数不同,这里2.4是用init_module作伪入口函数,cleanup_module作伪出口函数,这两个函数是2.4内核的模块中必须的。打印的信息在/var/log/message中,可以用命令”dmesg”来查看模块打印的信息。
b 编译
编译前的准备:在写好了模块以后,想要编译它,首先得安装内核源码,简单的办法下载源码到/usr/src/目录中,解压,进入那个目录编译一遍内核就好,编译内核的文章在网上也很多,可以找找,不过最好找版本基本对应的,下载源码编译的时候使用内核默认的.config文件相对就会安全一些了。不然编译的时候没有办法找到所需的源代码,自然就编译不了。若你在当前内核下编译的module,拷贝到其他内核中运行,也有可能运行不了。
在编译应用程序时我们可以用命令(在linux环境下):gcc –o helloworld helloworld.c就能把源码用gcc编译成二进制文件,但是2.6版本的内核编译内核模块得使用的是makefile,关于makefile这里也先不详细的介绍,以后可能以后会介绍一下。这里给出一个编译上述2.6版本内核内核模块的makefile。
obj-m := helloworld.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD)
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
把Makefile和helloworld.c放在同一个目录下,在shell下执行”make”命令就能把helloworld.c编译成helloworld.ko了。如果你的源码文件是“XXX.c”,你要把XXX.c编译成XXX.ko,那么把makefile里面的这句话“obj-m := helloworld.o”里面的helloworld.o改成XXX.o就可以了。还有一点需要说明一下,在2.4的内核中所有的模块的后缀都是.o,而2.6的内核中内核模块的后缀是.ko,普通模块的后缀是.o。
(注意makefile里面源码的空白处是不能用空格健上网空格来填补的必须用”Tab”键来填补空格)2.6中的内核模块只能用makefile来编译(这里有点模糊,可能还有其他办法,没查:),而编译模块在2.4版本的内核中可以用命令来编译,也可以用makefile来实现。
给出一个2.4版本的内核Makefile:
CC=gcc
CFLAGS = -O2 -DMODULE -D__KERNEL__ -Wall /
-I/usr/src/linux-2.4/include
helloworld.o: helloworld.c
$(CC) $(CFLAGS) -c helloworld.c
install:
/sbin/insmod helloworld.o
remove:
/sbin/rmmod helloworld
clean:
rm -f helloworld.o
这个Makefile其实很简单,和2.6大同小异,我就不详细的解释了。
安装模块: make install
卸载模块: make remove
也可以用make clean来删除模块。
2.4内核编译模块的命令是:
gcc –o -O2 -DMODULE -D__KERNEL__ -Wall -I/usr/src/linux-2.4/include -c helloworld.c
其实就是makefile给解出来了。
3 安装和卸载module
假设我们的模块名字是XXX.ko或者XXX.o
a、用命令装载和卸载模块
装载模块:
insmod XXX.ko (2.4版本中为XXX.o)
卸载模块:
rmmod XXX
你也可以查看已经安装的模块,用:
lsmod
b 、 modproper工具
modproper这个工具通常是用来管理内核目录中的模块,包括安装和卸载还有查看模块的依赖性等。
安装模块:
modproper XXX.ko [ module parameters]
(module parameters是模块的参数,更详细的信息可以查询《Linux Kernel Development》by Robert Love)
卸载模块:
modproper –r XXX
modproper还能查看内核模块的依赖关系,用命令:
modproper –A XXX
4 其他模块的属性
a 模块参数
Linux提供了一个简单的框架,它驱动程序声明参数,用户可以在系统启动或者模块装载指定参数的值,这些参数对于驱动程序属于全局变量。模块参数同时也出现在sysfs文件系统中。
b导出符号表
当模块被装载时,它被动态链接到内核.像用户空间的动态库一样,只有把函数导出才能 被调用. 在内核中, 导出内核函数使用这两个声明: called EXPORT_ SYMBOL() 和 EXPORT_SYMBOL_GPL().
导出内核的符号表后被看作还是导出的内核的接口,有时,也称为内核的API.(详细请看《Linux kernel Development》Second Edition第16章第7小节)
c 添加内核选项
"kbuild"系统是2.6内核的新特性,这个特性提供了把这个驱动模块添加到内核编译选项里的功能,下面我们看看如何利用这个特性。如果你的的模块在/drivers//char下,你会找到一个kconfig的文件,向kconfig文件中加入一条命令,source "drivers/char/XXXX/Kconfig",如果你是在/drivers//char下新建的一个文件夹XXXX,那你需要在drivers/char/Kconfig中添加
source "drivers/char/XXXX/Kconfig"
这样一句话,然后在Kconfig中加入:
config HelloWorld
tristate " HelloWorld support"
default n
help
If you say Y here, support for the XXXX with computer interface will be compiled into the kernel and accessible via device node. You can also say M here and the driver will be built as a module named HelloWorld.ko.
If unsure, say N.
config HelloWorld定义了配置目标,tristate说是选择“Y”把它编进内核,选择“M”是把它编成模块,选择“N”是不编译进内核。
参考文献:
《Linux kernel Development》Rebert Love Second Edition
《Linux Dvice Drivers》
《Linux 模块编程完全指南》