陈莉君老师Linux内核分析与应用课程,一个helloworld内核模块的插入和删除。
目录
编写helloworld.c
代码如下:
#include<unistd.h>//主要对一些系统调用的封装
#include<linux/module.h>//支持内核的模块机制
static int __init lkm_init(void)//__init修饰符,告诉程序只进行初始化,初始化之后释放内存
{
printk("Hello World\n");//内核模块编写时不能使用printf,使用对应的printk,两者接口完全一样。
//printk支持消息打印的级别,一般支持八个级别。
return 0;
}
static void __exit lkm_evit(void)
{
printk("Goodbye");
}
module_init(lkm_init);//模块的入口函数
module_exit(lkm_exit);//模块的出口函数
MODULE_LICENSE("GPL");
一个内核模块大致架构:
首先包含module.h头文件支持内核的模块机制;
接下来编写入口函数和出口;
引导内核入口点,出口点,和许可证的声明。
编写Makefile
代码如下:
obj-m:=helloworld.o
#使用目标文件建立一个模块,最终生成模块就是helloworld.ko
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL:=$(shell uname -r)
#当前的内核版本
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#指定内核路径
#为了使makefile文件具有较强的移植性,定义两个路径和一个内核版本
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
#前面用tab而不是空格
#指定内核路径,编译后的文件放在当前目录,最后加上modules表示我们编译的是内核模块
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
保存文件名为大写的Makefile。
此处需要安装内核头文件,当前使用的linux系统为CentOS 7。
1.首先使用 uname -r
命令查看当前正在运行的内核版本号,例如:
[root@localhost ~]# uname -r
3.10.0-1160.el7.x86_64
2.然后以此版本号为参数执行以下指令来安装相应的内核头文件:
sudo yum install kernel-devel-$(uname -r)
执行该命令会自动下载并安装与当前正在运行的内核版本相对应的内核头文件。
[root@localhost ~]# sudo yum install kernel-devel-3.10.0-1160.el7.x86_64
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
* base: mirrors.aliyun.com
* extras: mirrors.bupt.edu.cn
* updates: mirrors.aliyun.com
软件包 kernel-devel-3.10.0-1160.el7.x86_64 已安装并且是最新版本
无须任何处理
安装完成后,你可以在 /usr/src/kernels/
目录下找到所需的内核源代码和头文件。注意,由于 CentOS 7 中可能存在多个内核版本,因此你需要确保安装了与你当前正在运行的内核版本相对应的内核头文件。
此时使用make指令报错:
[root@localhost module]# make
make -C /usr/src/kernels/3.10.0-1160.el7.x86_64 M=/usr/module modules
make[1]: 进入目录“/usr/src/kernels/3.10.0-1160.el7.x86_64”
CC [M] /usr/module/helloworld.o
/usr/module/helloworld.c:1:19: 致命错误:unistd.h:没有那个文件或目录
#include<unistd.h>
^
编译中断。
make[2]: *** [/usr/module/helloworld.o] 错误 1
make[1]: *** [_module_/usr/module] 错误 2
make[1]: 离开目录“/usr/src/kernels/3.10.0-1160.el7.x86_64”
make: *** [all] 错误 2
在内核编译中不能使用<unstd.h>头文件,需要更改头文件。
修改后的helloworld.c文件:
#include<linux/init.h>//包含了__init宏和__exit宏
#include<linux/kernel.h>//包含了我们常用的内核函数
#include<linux/module.h>
static int __init lkm_init(void)
{
printk("Hello World\n");
return 0;
}
static void __exit lkm_evit(void)
{
printk("Goodbye");
}
module_init(lkm_init);
module_exit(lkm_exit);
MODULE_LICENSE("GPL");
以上头文件在路径:/usr/src/kernels/3.10.0-1160.el7.x86_64/include/linux/可以找到。
重新make后生成.ko文件。
[root@localhost module]# make
make -C /usr/src/kernels/3.10.0-1160.el7.x86_64 M=/usr/module modules
make[1]: 进入目录“/usr/src/kernels/3.10.0-1160.el7.x86_64”
CC [M] /usr/module/helloworld.o
Building modules, stage 2.
MODPOST 1 modules
CC /usr/module/helloworld.mod.o
LD [M] /usr/module/helloworld.ko
make[1]: 离开目录“/usr/src/kernels/3.10.0-1160.el7.x86_64”
此时终端中未打印helloworld。
此时我们与终端打交道的方式不再通过终端,而是通过proc目录下,它可以用于访问一些有关于内核的状态,进程的信息。
/proc/meminfo 这个文件中保存了一些物理内存和交换空间的一些信息。
/proc/kmsg 文件中包含了所有日志信息,各种级别的日志信息都在这里。
使用指令 dmesg 查看所有的日志信息。
插入模块指令:
[root@localhost module]# sudo insmod helloworld.ko
需要使用超级用户权限。
指令 lsmod查看当前系统中的模块。
[root@localhost module]# lsmod
Module Size Used by
helloworld 12423 0
tcp_lp 12663 0
nls_utf8 12557 1
isofs 39844 1
xt_CHECKSUM 12549 1
ipt_MASQUERADE 12678 3
nf_nat_masquerade_ipv4 13463 1 ipt_MASQUERADE
tun 36164 1
devlink 60067 0
此时可以看到我们的helloworld模块已经插入了。
再使用dmesg打印日志,就可以看到输出了。
[16565.493291] helloworld: loading out-of-tree module taints kernel.
[16565.493375] helloworld: module verification failed: signature and/or required key missing - tainting kernel
[16565.508772] Hello World
删除模块:
[root@localhost module]# sudo rmmod helloworld
只需要模块名称不需要后缀了。
查看模块没有了。
[root@localhost module]# lsmod
Module Size Used by
tcp_lp 12663 0
nls_utf8 12557 1
isofs 39844 1
xt_CHECKSUM 12549 1
ipt_MASQUERADE 12678 3
nf_nat_masquerade_ipv4 13463 1 ipt_MASQUERADE
tun 36164 1
devlink 60067 0
ip6t_rpfilter 12595 1
ip6t_REJECT 12625 2
再次打印日志。
[16565.508772] Hello World
[17063.649270] Hello World
[16948.283659] Goodbye/n
[17083.085464] Goodbye/n
以上是一个简单的系统模块的插入和删除。