linux内核模块开发

1.内核模块的概念
Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用需要的组件呢:
方法一:把所有的组件都编译进内核文件,即:zImage或bzImage,但这样会导致两个问题:一是生成的内核文件过大;二是如果要添加或删除某个组件时,需要重新编译这个内核。
方法二:组件在需要时被使用,动态的添加到正在运行的内核中,内核文件zImage或bzImage本身并不包含该组件,在linux中叫做"内核模块"。文件以.o(内核2.4)或.ko(内核2.6)的文件形式存在。
2.内核模快的特点
内核模块具有如下特点
(1)模块本身并不编译进内核文件(zImage或bzImage)
(2)可以根据需要,在内核运行期间动态的安装或卸载。

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


范例程序分析:
安装模块时被系统自动调用的函数,通过module_init宏来指定,卸载模块时被系统自动调用的函数,通过module_exit宏指定。模块加载函数和卸载函数是内核模块程序所必须的,模块加载函数是内核模块的入口函数,相当于应用程序的main函数。

模块可选信息

(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)

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就是传递的模块参数。

2012年04月16日 - 至庸 - THE BOLG OF CHEN
 
在linux2.6下编译内核模块通常使用的是makefile
makefile范例:
ifneq ($(KERNELRELEASE),)    #假如变量$(KERNELRELEASE)不等于空,执行下面的语句,否则执行else下面的语句。
obj-m :=hello.o      #根据需要变化
else
KDIR:= /lib/modules/2.6.18-53.el5/build     #内核源代码的路径,build这个其实是个连接文件,会连接到源代码目录(需要变)
all:
 make -C $(KDIR) M=$(PWD) modules    #进入到$(KDIR)目录下使用它自己的makefile,M=$(PWD)表示内核模块在当前目录下,modules表示编译的是内核拨快
clean:
 rm -f *.ko *.o *.mod.o *.mod.c .symvers
endif

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)

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值