什么是内核模块?
其全称为动态可加载内核模块。之所以提供模块机制,是因为Linux是“单块内核”的操作系统,单内核的最大优点是效率高,因为所有的内容都集中在一起,但其缺点是可扩展性和可维护性相对较差,模块机制就是为了弥补这一缺陷。Linux内核是模块化组成的,它允许我们方便地在运行时动态地向内核中插入或从中删除代码。
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时加载,被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。
特点:
装入的内核模块和其他内核部分完全一样,具有相同的访问权限,因此,差的内核模块会导致系统崩溃;
模块编程和内核版本密切相连,内核版本和模块版本的不兼容,也可能导致系统崩溃;
这些代码被一并组合在一个单独的二进制镜像中,这样基本内核镜像就尽可能的小,而且灵活性很好否则,在需要添加新硬件或者升级设备驱动时,必须重新构建内核。
用户层编程和内核模块编程的区别
选项 | 应用程序 | 内核模块程序 |
---|---|---|
使用函数 | libc 库 | 内核函数 |
运行空间 | 用户空间 | 内核空间 |
运行权限 | 普通用户 | 超级用户 |
入口函数 | libc 库 | 内核函数 |
运行空间 | main() | module_init |
出口函数 | exit() | module_exit |
编译 | gcc | makefile |
链接 | gcc | insmod |
运行 | 直接运行 | insmod |
入口函数 | libc 库 | 内核函数 |
调试 | gdb | kdbug 、 kdb 、 kgdb |
下面编写简单的内核模块
还是从最经典的hello world写起。
/*************************************************************************
> File Name: hello.c
> Author: Tanswer_
> Mail: 98duxm@gmail.com
> Created Time: 2017年02月27日 星期一 10时53分58秒
************************************************************************/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk("hello world\n");
return 0;
}
static void hello_exit(void)
{
printk("Goodbye world\n");
}
module_init(hello_init);
module_exit(hello_exit);
简单来看看代码
语法和我们平时写的C代码差异比较大,我也还没有适应,有机会再继续写。必要的两个函数:加载函数和卸载函数,分别还有另一种写法。printk是内核态下的打印函数。还可以加模块的声明与描述,比如:MODULE_AUTHOR("Tanswer");//作者
。我们详细看一下头文件,在编译前需要配一下环境。
头文件
#include <linux/module.h> 所有模块都要使用的
#include <linux/kernel.h> 包含了常用的内核函数
#include <linux/init.h> 包含了宏_init和_exit,它们允许释放内核占用的内存
内核头文件的位置:/usr/src/kernels/
用户层头文件的位置:/usr/include
我进入/usr/src/后发现里面是空的,并没有源码树,所以执行 #yum install kernel-devel
安装源码树。安装完成后,我发现我的uname -r
和 源码树目录显示的内核版本号不一致
这就导致了我写的Makefile出现了问题,见下面代码。
Makefile
obj-m := hello.o
CURRENT_PATH:=$(shell pwd)
KERNEL_NUM:= $(shell uname -r)
#KERNEL_PATH:=/usr/src/kernels/$(KERNEL_NUM)
KERNEL_PATH:=/usr/src/kernels/3.10.0-514.6.2.el7.x86_64
all:
# make -C $(KERNEL_PATH) M=$(CURRENT_DIR) modules
make -C $(KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(KERNEL_PATH) M=$(CURRENT_PATH) clean
第一次我写的是注释掉的那句,结果make 的时候显示路径名不正确,上面的原因我还不知道,懂得请赐教。所以我直接将路径名写上去了,就是现在的那句。我再次make,看下图:
又出现了两个错误,我逐个解决,上面那个错误,提示说please install libelf-dev or elfutils-libelf-devel
那我就乖乖听话装上它,然后我再次make:
上面那个错误已经解决,剩下这个以前用makefile的时候也没遇到过,我google了一下,发现有提这个问题的,但是没有解决方法。我没办法,有点失望就随手点开Makefile来看,然后……我就发现了一个小错误。。。Makefile代码中 -C 后指定的是 Linux 内核源代码的目录,而 M= 后指定的是 hello.c 和 Makefile 所在的目录,但是我第一次写成了CURRENT_DIR,就是我注释掉的那句,可明明我定义的变量是CURRENT_PATH,然后我就改过来了。再次make,终于终于终于
编译正确。
补充(转):
.ko 是kernel object 的缩写,是Linux 2.6内核使用的动态连接文件,在Linux系统启动时加载内核模块
链接和运行
1.insmod hello.ko 加载模块
2.dmesg 查看结果
3.rmmod 卸载模块
补充:常用的模块编程命令
1.lsmod命令查看系统中加载了的所有模块以及模块间的依赖关系
2.cat /proc/modules来查看加载模块信息
3.内核中已加载模块的信息也存在于 /sys/module 目录下,加载 hello.ko 后,内核中将包含 /sys/module/hello 目录,该目录下又包含一个 refcnt 文件和一个 sections 目录,在 /sys/module/hello 目录下运行 tree -a 可以看到他们之间的关系
4.使用 modinfo < 模块名 > 命令可以获得模块的信息,包括模块的作者,模块的说明,某块所支持的参数以及 vermagic