0x3 可装载内核模块(LKM)

可装载内核模块(LKM)

我们的Linux kernel虽然成功启动了,但是其本身的功能似乎有些单调,那么我们不如自己编写可装载内核模块(Loadable Kernel Modules)来扩充内核的功能吧!

〇、预备知识

前面我们讲到,LKM同样是ELF格式文件,但是其不能够独立运行,而只能作为内核的一部分存在

同样的,对于LKM而言,其所处在的内核空间与用户空间是分开的,对于通常有着SMAP/SMEP保护的Linux而言,这意味着LKM并不能够使用libc中的函数,也不能够直接与用户进行交互

虽然我们同样能够使用C语言编写LKM,但是作为内核的一部分,LKM编程在一定意义上便是内核编程, 内核版本的每次变化意味着某些函数名也会相应地发生变化,因此LKM编程与内核版本密切相关

一、简单的测试模块

我们来编写这样一个简单的内核模块,其功能是在载入/卸载时会在内核缓冲区打印字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* main.c
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init kernel_module_init(void)
{
printk("<1>Hello the Linux kernel world!\n");
return 0;
}

static void __exit kernel_module_exit(void)
{
printk("<114514>Good bye the Linux kernel world! See you again!\n");
}

module_init(kernel_module_init);
module_exit(kernel_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("3zureus");

头文件

  • linux/module.h:对于LKM而言这是必须包含的一个头文件
  • linux/kernel.h:载入内核相关信息
  • linux/init.h:包含着一些有用的宏

通常情况下,这三个头文件对于内核模块编程都是不可或缺的

入口点/出口点

内核模块的初始化函数在编译时通过 module_init() 定义,在内核模块被载入时会调用所定义的函数,这里我们将初始化函数设定为 kernel_module_init

内核模块的卸载函数在编译时通过 module_exit() 定义,在内核模块被卸载时会调用所定义的函数,这里我们将卸载函数定义为 kernel_module_exit

其他…

  • __init__exit 宏:用来显式标识内核模块出入口函数
  • MODULE_AUTHOR() & MODULE_LICENSE():声明内核作者与发行所用许可证

二、编译内核模块:makefile 与 Kbuild

与一般的可执行文件所不同的是,我们应当使用 Makefile 来构建一个内核模块,并使用 Kbuild 说明编译规则

首先创建一个 Kbuild 文件,写入如下内容:

1
2
3
4
5
MODULE_NAME ?= hellokernel

obj-m += $(MODULE_NAME).o

$(MODULE_NAME)-y += main.o

简单说明一下这个 Kbuild:

  • MODULE_NAME ?= hellokernel :定义了一个局部变量 MODULE_NAME ,值为 hellokernel
  • obj-m += $(MODULE_NAME).o :指定了编译的结果应当为.ko文件,即可装载内核模块,同时指定了模块名为 hellokernel ,当模块编译时相应的源文件所编译得到的中间文件会先被链接为 hellokernel.o ,之后再构建 .ko 文件;其他可替代标识为: obj-y 编译进内核 ,obj-n 不编译
  • $(MODULE_NAME)-y += main.o :编译该内核模块所需要的文件,例如这里我们需要 main.c ,那么我们就在该变量中添加 main.o

接下来创建一个名为 Makefile 的文件,写入如下内容:

1
2
3
4
5
6
7
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL_SRC := /lib/modules/$(shell uname -r)/build

all:
make -C $(LINUX_KERNEL_SRC) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_SRC) M=$(CURRENT_PATH) clean

这里简单说明一下这个 makefile :

  • LINUX_KERNEL_SRC := /lib/modules/$(shell uname -r)/build :当前系统所使用的内核源码路径
  • make -C $(LINUX_KERNEL_SRC) M=$(CURRENT_PATH) modules-C 表示进入源码目录进行编译,M= 意味着当前正在编译一个外部模块、该变量用以指示外部模块的源码目录, modules 则意为进行内核模块编译操作
  • make -C $(LINUX_KERNEL_SRC) M=$(CURRENT_PATH) clean:同上,不过此时进行的是清理指令

然后就是直接make即可

1
2
3
4
5
6
7
8
9
$ make
make -C /lib/modules/5.15.90.1-microsoft-standard-WSL2/build M=/home/simu/kernelLearn/LKM modules
make[1]: Entering directory '/home/simu/kernelLearn/wslzip/WSL2-Linux-Kernel-linux-msft-wsl-5.15.90.1'
CC [M] /home/simu/kernelLearn/LKM/main.o
LD [M] /home/simu/kernelLearn/LKM/hellokernel.o
MODPOST /home/simu/kernelLearn/LKM/Module.symvers
CC [M] /home/simu/kernelLearn/LKM/hellokernel.mod.o
LD [M] /home/simu/kernelLearn/LKM/hellokernel.ko
make[1]: Leaving directory '/home/simu/kernelLearn/wslzip/WSL2-Linux-Kernel-linux-msft-wsl-5.15.90.1'

MD,沟槽的微软,WSL中没有提供/lib/modules/

WSL2的内核是修改过的,无法使用 ubuntu上游的内核头文件和modules文件,因此,我们需要手动编译并安装一个版本。

别用WLS搞。。。

Ubuntu20.04下班这个,真别用

78438d1996b40fe84e105469588db21

WSL补上内核头文件和modules文件

1. 下载对应版本的内核代码

1
2
$ uname -r
5.15.90.1-microsoft-standard-WSL2

WSL git仓库,找到对应的release:

1
2
$ wget https://github.com/microsoft/WSL2-Linux-Kernel/archive/refs/tags/linux-msft-wsl-5.15.90.1.tar.gz
$ tar -zxvf linux-msft-wsl-5.15.90.1.tar.gz

2. 编译和安装

cd进文件夹后

1
2
LOCALVERSION= make KCONFIG_CONFIG=Microsoft/config-wsl -j8
sudo LOCALVERSION= make KCONFIG_CONFIG=Microsoft/config-wsl modules_install -j8

3. 安装headers

1
sudo make headers_install ARCH=x86_64 INSTALL_HDR_PATH=/usr

4. else

可能那个文件夹名字会只叫版本号,记得重命名一下,改成uname -r的结果

1
$ sudo mv 5.15.90.1/ 5.15.90.1-microsoft-standard-WSL2/

看到这里,我建议你装个Vmware吧

17好像初步解决了和Hyper-V冲突的问题了