1、内核模块基础
1.1什么是内核模块
Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用这些组件呢, 方法1:把所有的组件都编译进内核文件,即:zImage或bzImage,但这样会导致一个问题:占用内存过多.因为如果有些组件不需要经常使用,但是它一直存在于内存中,这显然是对内存资源的浪费。
有没有一种机制能让内核文件本身并不包含某组件,而是在该组件需要被使用的时候,动态地添加到正在运行的内核中呢?
这是内核模块就产生了,它具有以下特点:
模块本身并不被编译进内核文件(zImage或者bzImage)。
可以根据需求,在内核运行期间动态的安装或卸载。
1.2内核模块的安装及卸载
以USB下载工具dnw_usb模块为例
安装 insmod
例:insmod /home/dnw_usb.ko
例:insmod /home/dnw_usb.ko
卸载 rmmod
例:rmmod dnw_usb
例: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);
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 := 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“等
申明该模块遵守的许可证协议,如:“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)
静态的符号表,即内核映像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使用公共内核符号表来解析模块中未定义的符号。公共内核符号表中包含了所有的全局函数和全局
变量的地址。当模块被装入内核后,它所导出的任何内核符号都会变成内核符号表的一部分。
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许可证模块可见
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);
#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);
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