Linux内核模块开发

1、内核模块基础

1.1什么是内核模块

Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用这些组件呢, 方法1:把所有的组件都编译进内核文件,即:zImage或bzImage,但这样会导致一个问题:占用内存过多.因为如果有些组件不需要经常使用,但是它一直存在于内存中,这显然是对内存资源的浪费。

有没有一种机制能让内核文件本身并不包含某组件,而是在该组件需要被使用的时候,动态地添加到正在运行的内核中呢?
这是内核模块就产生了,它具有以下特点:
模块本身并不被编译进内核文件(zImage或者bzImage)。
可以根据需求,在内核运行期间动态的安装或卸载。

1.2内核模块的安装及卸载

以USB下载工具dnw_usb模块为例

安装 insmod
例:insmod /home/dnw_usb.ko

卸载 rmmod
例:rmmod dnw_usb

查看 lsmod
例:lsmod
lsmod是列举当存在内存中的所有模块名称,占用大小,有多少个用户正在使用等信息

2、内核模块设计

2.1范例代码分析

#include <linux/init.h>
#include <linux/module.h>
static int hello_init(void)
{
	printk(KERN_WARNING"Hello, world !\n");
	return 0;
}
static void hello_exit(void)
{
	printk(KERN_INFO "Goodbye, world\n");
} 
module_init(hello_init);
module_exit(hello_exit);

1、对比我们的应用程序可以发现,这段代码没有main()函数,大家都知道main()是一个入口函数,难道我们的模块代码不需要入口函数吗?肯定是要的,这个入口函数由module_init()来指明,当使用insmod函数来加载一个模块时,module_init()所指明的函数将会被得到调用,也就是hello_init()将会得到执行。

2、同时当使用rmmod去卸载一个模块时,module_exit()宏所指定的函数将得到调用。

3、需要包含头文件<linux/init.h>和<linux/module.h>

4、函数前面加了static后表示该函数失去了全局可见性,只在该函数所在的文件作用域内可见,其他作用域不能调用此函数

5、打印信息使用的不是printf而是printk,关于printk的用法见 http://blog.csdn.net/u013181595/article/details/73826315,一般提示性的信息会使用KERN_WARING,中间不需要逗号。

2.2Makefile编写

obj-m := helloworld.o

KDIR := /home/S5-driver/lesson7/linux-tq2440

all:
	make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
	
clean:
	rm -f *.o *.ko *.order *.symvers


obj-m 表示生产文件的名字,如果有多个.c文件则需要采用如下的写法:
obj-m := helloworld.o
helloworld-objs := file1.o file2.o file3.o

定义一个Makefile的变量KDIR,保存开发板上运行内核代码的路径,这里根据个人情况编写

生成目标,make -C $(KDIR)表示进入到内核代码里面,M=${PWD}表明模块代码的路径,就是当前路径,modules表示执行的命令是Make modules,最后指明交叉工具链

清除一些中间文件

2.3内核模块运行

当开发板采用NFS文件系统时,可以直接把生成的helloworld.ko文件拷贝到文件系统中,然后执行insmod:
有些情况会打印出2句警告信息,然后再打印出helloworld。

再执行rmmod,发现报错了,提示没有****目录,原因在于当卸载一个模块时,在lib/modules/下面有和内核版本一致的目录,如果没有可以新建:
mkdir -p /lib/modules/$(uname -r)

再卸载,可以看到打印出了GoodBye world。

3、模块开发可选项

3.1模块申明

1、MODULE_LICENSE(”遵守的协议”)
申明该模块遵守的许可证协议,如:“GPL“、”GPL v2“等

2、MODULE_AUTHOR(“作者”)
申明模块的作者

3、MODULE_DESCRIPTION(“模块的功能描述")
申明模块的功能

4、MODULE_VERSION("V1.0")
申明模块的版本

以MODULE_LICENSE为例,没有合法申请时,在前面测试的实例中有警告:
helloworld: module license 'unspecified' taints kernel.
意思就是说这个模块没有遵守协议,可能会破坏内核。在代码中加上MODULE_LICENSE宏:
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");

static int hello_init(void)
{
	printk(KERN_WARNING"Hello, world !\n");
	return 0;
}
static void hello_exit(void)
{
	printk(KERN_INFO "Goodbye, world\n");
} 
module_init(hello_init);
module_exit(hello_exit);

编译后放到开发板文件系统中,再次安装模块,没有刚才那个警告就说明成功了

3.2模块参数

在应用程序中int main(int argc, char** argv)argc表示命令行输入的参数个数,argv中保存输入的参数
1.那么内核模块中可以通过命令行输入参数么?答案:可以2.参数怎么传入,传入后保存在哪里?
在模块开发中通过宏module_param指定保存模块参数的变量。模块参数用于在加载模块时传递参数给模块。module_param(name,type,perm)
name:变量的名称type:变量类型,bool:布尔型 int:整型 charp:字符串型perm是访问权限。 S_IRUGO:读权限 S_IWUSR:写权限
例:int a = 3;char *st;module_param(a,int, S_IRUGO);module_param(st,charp, S_IRUGO);
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
int a=3;
char *st;

module_param(a,int,S_IRUGO|S_IWUSR);
module_param(st,charp,S_IRUGO|S_IWUSR);

static int hello_init(void)
{
	printk(KERN_WARNING"Hello, world !\n");
	printk("a=%d\n",a);
	printk("st=%s\n",st);
	return 0;
}
static void hello_exit(void)
{
	printk(KERN_INFO "Goodbye, world\n");
} 
module_init(hello_init);
module_exit(hello_exit);

编译后拷贝到开发板上再次安装模块:
#insmod helloworld.ko a=10 st=12345
可以看到打印出对于的a的和st的信息。


3.3符号输出

什么是内核符号?
Linux内核的符号表位于两个部分:
静态的符号表,即内核映像vmlinuz的符号表(System.map)
动态的符号表,即内核模块的符号表(/proc/kallsyms)

符号标志
T    External text
t     Local text
D    External initialized data
d    Local initialized data
B    External zeroed data
b    Local zeroed data
A    External absolute
a    Local absolute
U    External undefined
G    External small initialized data
g    Local small initialized data
I    Init section
S    External small zeroed data
s    Local small zeroed data
R    External read only
r    Local read only
C    Common
E    Small common
 
我们可以看到,大写标志都是全局的、可被外部引用的,而小写标志都是局部的、不能被外部引用的。
可以用nm命令查看可执行文件的符号表(nm - list symbols from object files)。
 
insmod使用公共内核符号表来解析模块中未定义的符号。公共内核符号表中包含了所有的全局函数和全局
变量的地址。当模块被装入内核后,它所导出的任何内核符号都会变成内核符号表的一部分。

为什么要导出模块中的内核符号?
如果一个内核模块实现的函数需要提供给其他的模块使用,不仅需要在其他模块中声明,还需要导出这个函数,例如:
EXPORT_SYMBOL(name); // 所有模块可见,可以被外部模块调用
EXPORT_SYMBOL_GPL(nae); // 含有GPL许可证模块可见


3.4实例

helloworld.c代码:
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
int a=3;
char *st;

extern add(int a, int b);
module_param(a,int,S_IRUGO|S_IWUSR);
module_param(st,charp,S_IRUGO|S_IWUSR);

static int hello_init(void)
{
	printk(KERN_WARNING"Hello, world !\n");
	printk("a=%d\n",a);
	printk("st=%s\n",st);
        printk("a+b=%d\n",add(2,3));
	return 0;
}
static void hello_exit(void)
{
	printk(KERN_INFO "Goodbye, world\n");
} 
module_init(hello_init);
module_exit(hello_exit);

add.c:
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");

int add(int a, int b)
{
        return a+b;
}
static int hello_init(void)
{
        return 0;
}
static void hello_exit(void)
{
	
} 
EXPORT_SYMBOL(add);
module_init(add_init);
module_exit(add_exit);

Makefile
obj-m := helloworld.o add.o

KDIR := /home/S5-driver/lesson7/linux-tq2440

all:
	make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
	
clean:
	rm -f *.o *.ko *.order *.symvers


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值