如何从C代码加载Linux内核模块? [英] How to load Linux kernel modules from C code?
问题描述
我有一个同时具有两个外部内核模块和一个用户空间守护程序的应用程序.我想在启动时从用C编写的守护程序代码中加载模块,并在干净退出时将其卸载.我可以用比system("modprobe module");
更干净的方式加载它们,并使用相应的rmmod
卸载它们吗?
I have an application that has both two external kernel modules and a userspace daemon. I want to load the modules from the daemon code, written in C, at startup, and unload them on clean exit. Can I load them in a cleaner way than doing system("modprobe module");
and unload them using the corresponding rmmod
?
推荐答案
最小的可运行示例
在QEMU + Buildroot VM和Ubuntu 16.04主机上进行了测试
Tested on a QEMU + Buildroot VM and Ubuntu 16.04 host with this simple parameter printer module .
我们使用init_module
/finit_module
和remove_module
Linux内核为模块插入提供了两个系统调用:
The Linux kernel offers two system calls for module insertion:
-
init_module
-
finit_module
init_module
finit_module
和:
man init_module
文件证明:
finit_module()系统调用类似于init_module(),但是从文件描述符fd中读取要加载的模块.当可以根据内核模块在文件系统中的位置确定内核模块的真实性时,它很有用.在可能的情况下,可以避免使用加密签名的模块来确定模块的真实性的开销. param_values参数与init_module()相同.
The finit_module() system call is like init_module(), but reads the module to be loaded from the file descriptor fd. It is useful when the authenticity of a kernel module can be determined from its location in the filesystem; in cases where that is possible, the overhead of using cryptographically signed modules to determine the authenticity of a module can be avoided. The param_values argument is as for init_module().
finit
是较新的,仅在v3.8中添加.更多理由: https://lwn.net/Articles/519010/
finit
is newer and was added only in v3.8. More rationale: https://lwn.net/Articles/519010/
glibc似乎没有为它们提供C包装器,因此我们只使用syscall
创建自己的包装器.
glibc does not seem to provide a C wrapper for them, so we just create our own with syscall
.
insmod.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#define init_module(module_image, len, param_values) syscall(__NR_init_module, module_image, len, param_values)
#define finit_module(fd, param_values, flags) syscall(__NR_finit_module, fd, param_values, flags)
int main(int argc, char **argv) {
const char *params;
int fd, use_finit;
size_t image_size;
struct stat st;
void *image;
/* CLI handling. */
if (argc < 2) {
puts("Usage ./prog mymodule.ko [args="" [use_finit=0]");
return EXIT_FAILURE;
}
if (argc < 3) {
params = "";
} else {
params = argv[2];
}
if (argc < 4) {
use_finit = 0;
} else {
use_finit = (argv[3][0] != '0');
}
/* Action. */
fd = open(argv[1], O_RDONLY);
if (use_finit) {
puts("finit");
if (finit_module(fd, params, 0) != 0) {
perror("finit_module");
return EXIT_FAILURE;
}
close(fd);
} else {
puts("init");
fstat(fd, &st);
image_size = st.st_size;
image = malloc(image_size);
read(fd, image, image_size);
close(fd);
if (init_module(image, image_size, params) != 0) {
perror("init_module");
return EXIT_FAILURE;
}
free(image);
}
return EXIT_SUCCESS;
}
rmmod.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#define delete_module(name, flags) syscall(__NR_delete_module, name, flags)
int main(int argc, char **argv) {
if (argc != 2) {
puts("Usage ./prog mymodule");
return EXIT_FAILURE;
}
if (delete_module(argv[1], O_NONBLOCK) != 0) {
perror("delete_module");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Busybox源解释
Busybox提供了insmod
,并且由于它是为极简主义而设计的,因此我们可以尝试从那里推断出它是如何完成的.
Busybox provides insmod
, and since it is designed for minimalism, we can try to deduce how it is done from there.
在1.24.2版本上,入口点位于modutils/insmod.c
函数
On version 1.24.2, the entry point is at modutils/insmod.c
function insmod_main
.
IF_FEATURE_2_4_MODULES
是对较早的Linux内核2.4模块的可选支持,因此我们现在暂时可以忽略它.
The IF_FEATURE_2_4_MODULES
is optional support for older Linux kernel 2.4 modules, so we can just ignore it for now.
That just forwards to modutils.c
function bb_init_module
.
bb_init_module
尝试两件事:
-
mmap
通过try_to_mmap_module
将文件存储到内存中.
mmap
the file to memory throughtry_to_mmap_module
.
这总是将image_size
设置为.ko
文件的大小.
This always sets image_size
to the size of the .ko
file as a side effect.
如果失败,则使用xmalloc_open_zipped_read_close
将文件malloc
存入内存.
if that fails, malloc
the file to memory with xmalloc_open_zipped_read_close
.
此功能可以选择先解压缩文件(如果它是zip文件),否则仅对它进行malloc.
This function optionally unzips the file first if it is a zip, and just mallocs it otherwise.
我不明白为什么要执行此压缩业务,因为我们甚至不能依靠它,因为try_to_mmap_module
似乎没有解压缩任何东西.
I don't understand why this zipping business is done, since we can't even rely on it because the try_to_mmap_module
does not seem to unzip things.
最后打来电话:
init_module(image, image_size, options);
其中,image
是放入内存中的可执行文件,而如果我们不带其他参数调用insmod file.elf
,则选项只是""
.
where image
is the executable that was put into memory, and options are just ""
if we call insmod file.elf
without further arguments.
init_module
由以上提供:
#ifdef __UCLIBC__
extern int init_module(void *module, unsigned long len, const char *options);
extern int delete_module(const char *module, unsigned int flags);
#else
# include <sys/syscall.h>
# define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)
# define delete_module(mod, flags) syscall(__NR_delete_module, mod, flags)
#endif
ulibc
是嵌入式libc实现,它似乎提供了init_module
.
ulibc
is an embedded libc implementation, and it seems to provide init_module
.
如果它不存在,我认为是glibc,但正如man init_module
所说:
If it is not present, I think glibc is assumed, but as man init_module
says:
glibc不支持init_module()系统调用. glibc标头中未提供任何声明,但是经过一段古怪的历史,glibc确实为以下内容导出了ABI: 此系统调用.因此,为了使用该系统调用,在代码中手动声明接口就足够了.或者,您可以调用 使用syscall(2)进行系统调用.
The init_module() system call is not supported by glibc. No declaration is provided in glibc headers, but, through a quirk of history, glibc does export an ABI for this system call. Therefore, in order to employ this system call, it is sufficient to manually declare the interface in your code; alternatively, you can invoke the system call using syscall(2).
BusyBox明智地遵循了该建议,并使用了glibc提供的syscall
,并提供了用于系统调用的C API.
BusyBox wisely follows that advice and uses syscall
, which glibc provides, and which offers a C API for system calls.
这篇关于如何从C代码加载Linux内核模块?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!