1.LINUX内核模块基础
1.1 什么是内核模块?
- Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用这些组件呢?方法1:把所有的组件都编译进内核文件,即:zImage或bzImage,但这样会导致一个问题:占用内存过多。有没有一种机制能让内核文件本身并不包含某组件,而是在该组件需要被使用的时候,动态地添加到正在运行的内核中呢?
- 内核模块具有如下特点:
- 1)模块本身并不被编译进内核文件(zImage或者bzImage);
- 2)可以根据需求,在内核运行期间动态的安装或卸载。
1.2 安装与卸载
- 安装 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()
{
printk(KERN_WARNING"Hello world!\n");
return 0;
}
static void hello_exit()
{
printk(KERN_WARNING"hello exit!\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,一般提示性的信息会使用KERN_WARING,中间不需要逗号。
2.2Makefile编写
obj-m := helloworld.o
KDIR := /home/linux/workdir/kernel/linux-mini2440
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句警告信息,然后再打印出Hello world!。
- 再执行rmmod,发现报错了,提示没有****目录,原因在于当卸载一个模块时,在lib/modules/下面有和内核版本一致的目录,如果没有可以新建:mkdir -p /lib/modules/$(uname -r)再卸载,可以看到打印出了hello exit!。
3.内核模块可选信息
1.模块申明
- MODULE_LICENSE(”遵守的协议”)申明该模块遵守的许可证协议,如:“GPL“、”GPL v2“等
- MODULE_AUTHOR(“作者”)申明模块的作者
- MODULE_DESCRIPTION(“模块的功能描述")申明模块的功能
- MODULE_VERSION("V1.0")申明模块的版本
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:写权限
#include <linux/init.h>
#include <linux/module.h>
int a = 3;
char *p;
static int hello_init()
{
printk(KERN_WARNING"hello world!\n");
printk("a = %d\n",a);
printk("p = %s\n", p);
return 0;
}
static void hello_exit()
{
printk(KERN_WARNING"hello exit!\n");
}
MODULE_LICENSE("GPL");
module_param(a, int, S_IRUGO | S_IWUSR);
module_param(p, charp, S_IRUGO | S_IWUSR);
module_init(hello_init);
module_exit(hello_exit);
3.符号输出
- 内核模块中实现的函数需要被另外一个内核模块调用时,需要用到模块符号输出。
- 内核模块符号的输出使用的宏:
- EXPORT_SYMBOL(符号名)
- EXPORT_SYMBOL_GPL(符号名),说明:其中EXPORT_SYMBOL_GPL只能用于包含GPL许可证的模块。
#include <linux/init.h>
#include <linux/module.h>
extern add(int a, int b);
int a = 3;
char *p;
static int hello_init()
{
printk(KERN_WARNING"hello world!\n");
printk("a = %d\n",a);
printk("p = %s\n", p);
return 0;
}
static void hello_exit()
{
add(1, 3);
printk(KERN_WARNING"hello exit!\n");
}
// 模块信息声明
MODULE_LICENSE("GPL");
// 模块参数声明
module_param(a, int, S_IRUGO | S_IWUSR);
module_param(p, charp, S_IRUGO | S_IWUSR);
// 模块函数声明
module_init(hello_init);
module_exit(hello_exit);
#include <linux/init.h>
#include <linux/module.h>
int add(int a, int b)
{
return a + b;
}
static int add_init()
{
return 0;
}
static void add_exit()
{
}
// 模块信息声明
MODULE_LICENSE("GPL");
// 模块符号输出
EXPORT_SYMBOL(add);
// 模块函数声明
module_init(add_init);
module_exit(add_exit);
obj-m := helloworld.o add.o
KDIR := /home/S4_a/part3/lesson3/lesson-2440/linux-mini2440
all:
make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean:
rm -f *.o *.ko *.order *.symvers *.mod.c
- 定义一个Makefile的变量KDIR,保存开发板上运行内核代码的路径,这里根据个人情况编写。
- 生成目标,make -C $(KDIR)表示进入到内核代码里面,M=${PWD}表明模块代码的路径,就是当前路径,modules表示执行的命令是Make modules,最后指明交叉工具链。
- 清除一些中间文件。