内核模块
基础知识
模块是一种向LInux内核添加设备驱动程序,文件系统及其其他组件的有效方法,不需要编译新内核或者重启系统
模块有如下优点:
- 通过使用模块,内核发布者能够预先编译大量驱动程序,而不会致使内核映像的尺寸发送膨胀
- 内核开发者可以将实验性的代码打包到模块中,模块可以卸载,修改代码或者重新打包后可以重新装载
模块添加和删除
从用户的角度来看,模块可以通过两个不同的系统程序添加到运行的内核中。它们分别是modprobe和insmod。modprobe在识别出目标模块所依赖的模块之后,在内核也会使用insmod,在用户空间对模块的处理也是基于insmod。
在处理该系统调用时,模块代码首先复制到内核内存中。接下来是重定位工作和解决模块中未定义的引用。因为模块使用了持久编译到内核中的函数,在模块本身编译时无法确定这些函数的地址,所以需要在这里处理未定义的引用。
处理未解决的引用,为与内核的剩余部分协作,模块必须使用内核提供的函数。这些可能是通用的辅助函数,比如几乎内核每一部分都会使用的printk或kmalloc。
- insmod只加载一个单一的模块到内核中,而该模块可能只依赖内核中已存在的代码,并不关注所有依赖代码是通过模块动态加载,还是持久编译到内核中。
- modprobe在识别出目标模块所依赖的模块之后,在内核也会使用insmod。在用户空间对模块的处理基于insmod
- 在加载模块时所需要的操作,与通过ld和ld.so借助于动态库链接应用程序的操作。从外部来看,模块只是普通的可重定位目标文件。fe命令的输出表明模块文件是可重定位的,这是用户空间程序设计中一专业术语。可重位文件的函数都不会引用绝对地址,而只是指向代码中的相对地址,因此可以在内存的任意偏移地址加载,当然在映像加载到内存时,映像中的地址要由动态连接器ld.so进行适当的修改即可。重定位的工作由内核自身执行,而不是动态装载器。
- ramfs模块允许在内存中建立一个文件系统(称为RAM磁盘),因而须调用register filesystem函数将自身添加到内核中可用文件系统的列表。此模块还使用内核代码中generic_file_read/generic_file_write标准函数,基本大多数内核文件系统都会使用这些普通函数。
导出函数查看
内核提供了一个所有导出函数的列表。该列表给出了所有导出函数的内存地址和对应函数名,可以通过proc文件系统访问,即文件/proc/kallsyms
T表示符号位于代码段,D表示符号位于数据段
动手实践-内核模块的插入删除
代码
helloworld.c
hello_init
为模块的初始化入口hello_exit
为模块的退出回调module_init
和module_exit
为指定初始化入口和模块卸载回调MODULE_LICENSE
指定GLP
开源协议,不然很多内核函数不准使用,并且打印警告信息
#include <linux/module.h>
#include <linux/kernel.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);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple example kernel module");
Makefile编译
或者直接使用这句指令
make -C /lib/modules/ ( u n a m e − r ) / b u i l d M = (uname -r)/build M= (uname−r)/buildM=(pwd) modules
obj-m:=helloworld.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL:=$(shell uname -r)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-5.15.0-91-generic # 需要根据自己的需要,进行修改
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
如下是cmake版本
# 指定最低的 CMake 版本要求
cmake_minimum_required(VERSION 3.10)
# 设置项目名称
project(MyKernelModule)
# 指定内核源代码目录(可能需要根据你的系统修改此路径)
set(KERNEL_SOURCE_DIR /lib/modules/$(uname -r)/build)
# 设置模块名称
set(MODULE_NAME my_module)
# 添加内核模块的源文件
set(MODULE_SRC my_module.c)
# 添加内核模块作为构建目标
add_custom_target(
${MODULE_NAME}.ko ALL
COMMAND make -C ${KERNEL_SOURCE_DIR} M=${CMAKE_CURRENT_SOURCE_DIR} modules
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
SOURCES ${MODULE_SRC}
)
# 添加清理目标
add_custom_target(
clean_module
COMMAND make -C ${KERNEL_SOURCE_DIR} M=${CMAKE_CURRENT_SOURCE_DIR} clean
)
加载测试
加载
# 这是最普通的加载,
sudo insmod helloworld.ko
# 自动加载
sudo depmod
sudo modprobe helloworld
查看加载结果
sudo lsmod | grep helloworld
helloworld 12288 0
查看模块信息
root@VM-16-3-ubuntu:~/projects/test/modult# modinfo helloworld
filename: /lib/modules/5.15.0-91-generic/helloworld.ko
description: A simple example kernel module
author: Your Name
license: GPL
srcversion: BD8DFFD87E8FF6F61A8F9C8
depends:
retpoline: Y
name: helloworld
vermagic: 5.15.0-91-generic SMP mod_unload modversions
查看内核日志
sudo dmesg | grep ello
[ 237.298841] helloworld: loading out-of-tree module taints kernel.
[ 237.298843] helloworld: module verification failed: signature and/or required key missing - tainting kernel
[ 237.299466] Hello world!
删除模块
sudo rmmod helloworld
sudo lsmod | grep helloworld
sudo dmesg
[6328199.995893] Hello, World!
[6328337.483051] Goodbye, World!
生成符号
使用nm指令可以查看ko文件内所有生成的符号
T表示符号位于代码段,D表示符号位于数据段
root@VM-16-3-ubuntu:~/projects/test/modult# nm helloworld.ko
0000000000000000 T cleanup_module
U __fentry__
0000000000000000 t hello_exit
0000000000000000 t hello_init
0000000000000000 T init_module
0000000000000018 r _note_8
0000000000000000 r _note_9
U _printk
0000000000000000 D __this_module
000000000000002b r __UNIQUE_ID_author119
000000000000006b r __UNIQUE_ID_depends121
0000000000000000 r __UNIQUE_ID_description120
000000000000003c r __UNIQUE_ID_license118
0000000000000080 r __UNIQUE_ID_name119
0000000000000074 r __UNIQUE_ID_retpoline120
0000000000000048 r __UNIQUE_ID_srcversion122
0000000000000090 r __UNIQUE_ID_vermagic118
0000000000000000 r ____versions
U __x86_return_thunk