hello.c
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("DAVID");
MODULE_DESCRIPTION("Hello world module");
static int __init hello_init(void)
{
printk(KERN_ERR "hello world!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_EMERG "hello exit!\n");
}
module_init(hello_init); //模块加载
module_exit(hello_exit); //模块卸载
Makefile
ifneq ($(KERNELRELEASE),)
obj-m :=hello.o
else
KDIR:= /lib/modules/2.6.18-53.el5/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c .symvers
endif
模块可选信息
(1)许可证声明
宏MODULE_LICENSE用来告知内核,该模块带有一个许可证,没有这样的说明,加载模块时内核会抱怨。有效的许可证有"GPL"、"v2"、"GPL and additional rights"、"Dual BSD/GPL"、"Dual MPL/GPL"和"Proprietary"。
(2)作者声明(可选)
MODULE_AUTHOR()
(3)模块描述(可选)
MODULE_DESCRIPTION(Hello World Module )
(4)模块版本(可选)
MODULE_VERSION(V1.0)
(5)模块别名(可选)
MODULE_ALIAS(a simple module)
(6)模块参数
通过宏module_param指定模块参数,模块参数用于在加载模块时传递参数给模块。
module_param(name,type,perm)
name是模块参数的名称,type是这个参数的类型,perm是模块参数的访问权限。
type的常见值:
bool(布尔型),int(整型),charp(字符串型)
perm常见值:
S_IRUGO(任何用户都对/sys/module中出现的该参数具有读权限)
S_IWUSR(允许root用户修改/sys/module中出现的参数)
param.c
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
static char *name = "DAVID";
static int age=30;
module_param(age, int ,S_IRUGO);
module_param(name, charp ,S_IRUGO);
static int __init hello_init(void)
{
printk(KERN_EMERG "Name:%s\n",name);
printk(KERN_EMERG "Age:%d\n",age);
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_EMERG"Module exit!\n");
}
module_init(hello_init);
module_exit(hello_exit);
Makefile
ifneq ($(KERNELRELEASE),)
obj-m :=param.o
else
KDIR:= /lib/modules/2.6.18-53.el5/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c .symvers
endif
下图中的命令insmod param.ko age=12,中的age=12就是传递的模块参数。
KDIR:= /lib/modules/2.6.18-53.el5/build #内核源代码的路径,build这个其实是个连接文件,会连接到源代码目录(需要变)
make -C $(KDIR) M=$(PWD) modules #进入到$(KDIR)目录下使用它自己的makefile,M=$(PWD)表示内核模块在当前目录下,modules表示编译的是内核拨快
clean:
rm -f *.ko *.o *.mod.o *.mod.c .symvers
3.模块的安装与卸载
加载 insmod (insmod hello.ko)
卸载 rmmod (rmmod hello)
查看 lsmod
加载 modprobe (modprobe hello)
modprobe如同insmod,也是加载一个模块到内核。它的不同之处在于它会根据文件/lib/modules/<$version>/modules.dep查看要加载的模块,看它是否还依赖于其他模块,如果是,modprobe会首先找到这些模块,把它们加载到内核。
4.内核符号的导出 可参考文章
/proc/kallsyms记录了内核中所有的导出符号的名字和地址。
[root@localhost 4-1-4]# cat /proc/kallsyms |grep add_integar
ee577000 t add_integar [calculate]
没导出来
[root@localhost 4-1-4]# cat /proc/kallsyms |grep sub_integar
ee57700c r __ksymtab_sub_integar [calculate]
ee577018 r __kstrtab_sub_integar [calculate]
ee577014 r __kcrctab_sub_integar [calculate]
ee577004 T sub_integar [calculate]
13db98c9 a __crc_sub_integar [calculate]
已经导出
出现关键字__ksymtab才表明符号已经导出
模块1需要依赖于模块2中的符号(如函数名)时,模块2就要导出相应的符号供模块1使用,否则模块1没法加载。
内核符号的导出使用:
EXPORT_SYMBOL(符号名)
EXPORT_SYSBOL_GPL(符号名)
其中EXPORT_SYSBOL_GPL只能用于包含GPL许可证的模块。
calculate.c
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
int add_integar(int a,int b)
{
return a+b;
}
int sub_integar(int a,int b)
{
return a-b;
}
static int __init sym_init()
{
return 0;
}
static void __exit sym_exit()
{
}
module_init(sym_init);
module_exit(sym_exit);
EXPORT_SYMBOL(add_integar); //导出供hello内核模块用
EXPORT_SYMBOL(sub_integar);
hello.c
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
extern int add_integar(int a,int b);
extern int sub_integar(int a,int b);
static int __init hello_init(void)
{
int res=add_integar(1,2);
printk(KERN_EMERG"hello init , res=%d\n",res);
return 0;
}
static void __exit hello_exit()
{
int res=sub_integar(2,1);
printk(KERN_EMERG"hello exit,res=%d\n",res);
}
module_init(hello_init);
module_exit(hello_exit);
Makefile
ifneq ($(KERNELRELEASE),)
obj-m := hello.o calculate.o
else
KDIR := /lib/modules/2.6.18-53.el5/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers
endif
5. 常见的问题
版本不匹配的问题:
内核模块的版本由其所依赖的内核代码版本所决定(即内核模块是用的哪个内核版本编译的),在加载内核模块时,insmod程序会将内核模块版本与当前正在运行的内核版本比较,如果不一致将会出现一些错误。
解决方法:
(1)使用modprobe --force -modversion 强行插入。
(2)确保编译内核模块时,使用的内核代码版本和之前正在运行的内核代码一致。
6.总结与对比
(1)与应用程序对比。内核模块有以下不同:
应用程序从头(main)到尾执行任务,执行结束后从内存中消失。内核模块则是先在内核中注册自己以便于服务将来的某个请求,然后它的初始化函数结束,此时模块仍然存在于内核中,直到卸载函数被调用,模块才从内核中消失
(2)内核打印
printk是内核中出现最频繁的函数之一,printk与printf的相同点是都是用来打印信息的,不同的是printk在内核中使用,printf在应用程序中使用。printk允许根据严重程度,通过附加不同的优先级来对消息分类。在<linux/kernel.h>中定义了8中记录级别。按照优先级递减顺序分别是
KERN_EMERG 用于紧急消息,常常是那些崩溃前的消息 <0>
KERN_ALERT 需要立刻行动的消息 <1>
KERN_CRIT 严重情况 <2>
KERN_ERR错误情况 <3>
KERN_WARNING 有问题的警告 <4>
KERN_NOTICE 正常情况但是仍然值得注意 <5>
KERN_INFO信息型消息 <6>
KERN_DEBUG用于调试消息 <7>
在没有指定优先级的printk默认使用
DEFAULT_MESSAGE_LOGLEVEL优先级,它在kernel/printk.c中定义:
#define DEFAULT_MESSAGE_LOGLEVEL 4 /*KERN_WARNING*/
(3)控制台的优先级配置
/proc/sys/kernel/printk
6 4 1 7
6:Console_loglevel
4:Default_message_loglevel
1:Minimum_console_level
7:Default_console_loglevel