内核模块入门之深入浅出

出学内核模块,略做总结。希望对广大菜鸟有所帮助。

为了不浪费大牛们的时间,在开头先列出文章中将要讲到的几个知识点,都了解的可以飘过哈!

一、内核模块代码的特点

二、内核模块的Makefile 的编写

三、内核模块的安装、卸载

四、模块的可选信息

五、内核模块导出

六、内核加载常见的问题

一、内核模块代码的特点

   何谓内核模块?为什么需要内核模块?

   你当然可以不需要内核模块,只需要将代码编入内核中即可。但这又导致了内核将越来越庞大。使用内核模块还有一个特点,那就是“即插即用”,可以在系统运行是加载和卸载,大大方便了模块的使用和开发。

那么,内核模块在代码编写上有啥特点呢?

 

复制代码
(1)必须有一下函数:

//一个规范,并非一定要如下格式定义函数名:
int hello_init() //入口
void hello_exit() //出口



(2)模块尾部必须如下声明:

moudule_init(hello_init) //申明模块入口函数
moudule_exit(hello_exit) //模块出口


(3)没有main函数。


(4)使用printk打印,而不是printf
复制代码

一个简单而又经典的hello world例子:

复制代码
#include <linux/moudle.h>
#include <linux/init.h>

static int __init hello_init()
{
printk("Hello World! ");
return 0;
}

static void __exit hello_exit()
{
printk("<7>hello <0>exit ");
}

moudule_init(hello_init);
moudule_exit(hello_exit);
复制代码

怎么样?看完代码你是不是会大喊一声:尼玛,怎么会这么简单?尼玛,他就是那么简单啊。

二、内核模块的Makefile 的编写

了解了内核模块的基本格式后,开始来编写makefile。

以下是一个最简单的内核模块的makefile格式:

复制代码
#ifneq:不等于空  (第一次为空,走else,第二次才走下面)
ifneq ($(KERNELRELEASE),)

#内核模块名字
obj-m := hello.o
hello-objs := main.o add.o

else

#内核源码的路径
KDIR := /lib/modules/2.6.18-53.el5/build

all:

#
# -C : 表示进入$(KDIR)目录后使用目录中的makefile进行编译
# M=$(PWD): 表示当前目录,modules:目标模块
make -C $(KDIR) M=$(PWD) modules

clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers


endif
复制代码

按照这个模块,我们只要对“内核模块名”和“内核源码的路径”两项根据你的实际情况进行修改即可。

尼玛,怎么又是那么简单?不是忽悠广大网友吧?

这里LZ以RP保证,正的就是这么简单。想学点难的?别急,好戏还在后头呢。

三、内核模块的安装、卸载

编译完模块后,我们当然需要来安装、卸载,使用如下命令:

加载:insmod (insmod hello.ko)
卸载:rmmod (rmmod hello)
查看:lsmod
加载:modprobe (modprobe hello)

modprobe 与 insmod 都是加载一个模块到内核中,区别在于:
modprobe会根据文件modules.dep查看要加载的模块是否依赖于其他模块,如果是,modprobe会首先安装依赖的模块加载到内核中。

四、模块的可选信息

1.你或许会在内核模块中看到以下代码:

MOUDLE_LICENSE("GPL");                //指定这个模块遵守的协议
MOUDLE_AUTHOR("Pearry Zhou"); //模块作者
MOUDLE_DESCRIPTION("XX Module"); //模块描述
MOUDLE_VERSION("V1.0"); //模块版本
MOUDLE_ALIAS("a simple module"); //模块别名

2.模块参数

我们知道int main(int argc, char** argv), argc表示命令行输入的参数个数,argv中保存你输入的参数。

那么内核模块中可以通过命令行输入参数么?当然可以。

通过宏moudle_param指定模块参数,模块参数用于在加载模块时传递参数给模块。

moudle_param(name,type,perm)
这样你就定义了一个 变量名为name,类型为type,访问权限为perm的变量。

前两个就不用解释了吧?

第三个,perm常见的值有:
S_IRUGO : 任何用户都对/sys/module中出现的该参数有读权限
S_IWUSR : 允许root用户修改/sys/module中出现的该参数

例如:

int a = 3;
char *st;
module_param(a,int,S_IRUGO);
module_param(st,charp,S_IRUGO);

(注:charp为字符串指针)

模块参数的使用:

insmod xx.ko a=4    //例子中的参数a被修改为4.你也可以在函数中把a打印出来查看结果

是不是很神奇?赶紧动手尝试下吧。邓爷爷教导我们:实践是检验真理的唯一标准。这句话TMD就是说给我们程序员听的啊,有没有发现,很多时候写完代码发现很多低级错误都是因为自己“想当然”的这么认为而没有去实践过导致的?(比如 int *p; ... p +=2; p真的是p的地址+2么?赶紧膜拜邓爷爷吧!没有邓爷爷)

五、内核模块导出

1.为什么要有“内核模块导出”这个概念?

当你的模块中调用了其他模块的接口时,即使你已经加载了所依赖的模块,
在加载你的模块的时候还是会出现加载失败(提示:insmod:-1 Unknow symbl in module),
这个时候,就必须用到内核模块导出。


2.如何导出?

假设一个模块中存在函数:
int add_int(int a,int b);
int sub_int(int a,int b);

则使用如下宏导出:

EXPORT_SYMBOL(add_int);
EXPORT_SYMBOL(sub_int);

导出之后,其他模块即可使用extern引用(当然所依赖模块必须已经加载)。

类似的导出宏还有:EXPORT_SYMBOL_GPL //只能用于GPL许可证的模块。

六、内核加载常见的问题

1.版本不匹配

(1)出现如下错误表示版本不匹配
insmod hello.ko
disagrees about version ofsymbol struct_module
insmod:error inserting 'hello.ko':-1 Invalid module format


(2)解决方法

I.使用modprobe --force-modversion命令强行插入

II.查看内核版本命令:uname -r
确保内核模块版本与当前内核版本相同,更换内核版本等手段

因为在编译内核模块的时候需要在makefile中指定内核源代码,
所以只要指定的内核源码与使用的当前内核版本相同即可,可在代码的makefile最前面看到版本号:

VERSION = 3
PATCHLEVEL = 2
SUBLEVEL = 6

即内核版本为3.2.6

总之:代码中makefile中的版本号,要与uname -r显示出来的版本号相同。

原文地址:https://www.cnblogs.com/spinsoft/p/3288317.html