背景介绍
模块的全称是“动态可加载内核模块”( Loadable Kernel Module,LKM)
模块在内核空间运行
模块实际上是一种目标对象文件 ,不能独立运行,但是其代码可以在运行时链接到 系统中作为内核的一部分运行或从内核中取下, 从而可以动态扩充内核的功能
这种目标代码通常由一组函数和数据结构组成
内核模块的优点 :
- 使得内核更加紧凑和灵活 ;
- 修改内核时,不必全部重新编译整个内核。 系统如果需要使用新模块,只要编译相应的
模块,然后使用insmod将模块装载即可 ; - 模块的目标代码一旦被链接到内核,它的作 用域和静态链接的内核目标代码完全等价。
内核模块的缺点 :
- 由于内核所占用的内存是不会被换出的,所以链接进内核的模块会给整个系统带来一定的性能和内存利用方面的损失;
- 装入内核的模块就成为内核的一部分,可以修改内核中的其他部分,因此,模块的使用不当会导致系统崩溃
- 为了让内核模块能访问所有内核资源,内核必须维护符号表,并在装入和卸载模块时修改符号表;
- 模块会要求利用其它模块的功能,所以,内核要维护模块之间的依赖性。
内核模块与应用程序的区别:
属性 | c语言程序 | 模块 |
---|---|---|
运行 | 用户空间 | 内核空间 |
入口 | main() | module_init()指定 |
出口 | 无 | module_exit()指定 |
编译 | gcc-c | Makefile |
连接 | ld | insmod |
运行 | 直接运行 | insmod |
调试 | gdb | kdb kgdb |
常用命令:
dmesg命令被用于检查和控制内核的环形缓冲区。kernel会将开机信息存储在ring buffer中。您若是开机时来不及查看信息,可利用dmesg来查看。开机信息保存在/var/log/dmesg文件里。
lsmod命令用于显示已经加载到内核中的模块的状态信息。执行lsmod命令后会列出所有已载入系统的模块。Linux操作系统的核心具有模块化的特性,应此在编译核心时,务须把全部的功能都放入核心。您可以将这些功能编译成一个个单独的模块,待需要时再分别载入。
insmod命令用于将给定的模块加载到内核中。Linux有许多功能是通过模块的方式,在需要时才载入kernel。如此可使kernel较为精简,进而提高效率,以及保有较大的弹性。这类可载入的模块,通常是设备驱动程序。
实验过程
基本模块
cd ~/simple
sudo vi hello.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
static int __init hello_init(void){
printk(KERN_INFO "Hello world\n");
return 0;
}
static void __exit hello_exit(void){
printk(KERN_INFO "Goodbye world\n");
}
module_init(hello_init);
module_exit(hello_exit);
sudo vi Makefile
obj-m += hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
all下一行需要有一个“Tab”键,不要写成空格或 略去,不然系统无法识别,报错:nothing to be done for “all”
模块程序介绍 :
static int __init hello_init(void)
static void __exit hello_exit(void)
Static声明,因为这种函数在特定文件之外没有 其它意义
__init标记 :表明该函数只在初始化期间使用,模块装载后,将该函数占用的内存空间释放
__exit标记 :该代码仅用于模块卸载
sudo insmod hello.ko
lsmod
卸载模块
sudo rmmod hello.ko
参数传递
参数需要使用module_param宏来声明:
module_param的参数–变量名称,类型以及访问许可掩码
1.定义模块参数的方法:
module_param(name, type, perm);
其中,name
:表示参数的名字;type
:表示参数的类型; perm
:表示参数的访问权限;
2.数组类型模块参数的定义:
用逗号间隔的列表提供的值,声明一个数组参数:
module_param_array(name, type, num, perm);
其中,name
:表示数组的名字; type
:表示参数的类型;num
:表示数组中元素数量;perm
:表示参数的访问权限;
3.type支持的基本类型有:
bool :布尔类型
invbool:颠倒了值的bool类型;
charp :字符指针类型,内存为用户提供的字符串分配;
int :整型
long :长整型
short :短整型
uint :无符号整型
ulong :无符号长整型
ushort :无符号短整型
4.perm参数 设定访问权限
modlue_param和module_param_array中的perm用于设定该参数的访问权限;
perm表示该参数在sysfs文件系统中所对应的文件节点的属性;你用该使用
#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001
注意:如果一个参数被sysfs修改了,那么你的模块看到的参数值也被修改了,但是你的模块不会收到任何通知;你应当不要使模块参数可写,除非你准备好检测这个改变并因而作出反应;
示例
参数传递,是指的在加载模块的时候决定的
#insmod hello.ko test=2
以下是代码
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/moduleparam.h>
static int test;
module_param(test,int,0644);
static int __init hello_init(void){
printk(KERN_INFO "Hello world test=%d \n",test);
return 0;
}
static void __exit hello_exit(void){
printk(KERN_INFO "Goodbye world\n");
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Test");
MODULE_AUTHOR("jackherrick");
module_init(hello_init);
module_exit(hello_exit);
导出符号表
/proc/kallsyms
可以显示所有导出的符号
如果一个模块需要向其他模块导出符号( 方法或全局变量),需要使用
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
注意:符号必须在模块文件的全局部分导 出,不能在函数部分导出
来源:oschina
链接:https://my.oschina.net/u/4377796/blog/4336095