我有一个同时具有两个外部内核模块和一个用户空间守护程序的应用程序。我想在启动时从用C编写的守护程序代码中加载模块,然后在干净退出时将其卸载。我可以用比system("modprobe module");使用相应的方式更干净的方式加载它们rmmod吗?
system("modprobe module");
rmmod
最小的可运行示例
使用此简单的参数打印机模块,在QEMU + Buildroot VM和Ubuntu 16.04主机上进行了测试。
我们使用init_module/ finit_module和remove_module Linux系统调用。
init_module
finit_module
remove_module
Linux内核为模块插入提供了两个系统调用:
和:
man init_module
证明文件:
系统调用finit_module()类似于init_module(),但是从文件描述符fd中读取要加载的模块。当可以从内核模块在文件系统中的位置确定内核模块的真实性时,它很有用;在可能的情况下,可以避免使用经过密码签名的模块来确定模块的真实性的开销。param_values参数与init_module()相同。
finit是较新的版本,仅在v3.8中添加。更多理由:https : //lwn.net/Articles/519010/
finit
glibc似乎没有为他们提供C包装器,因此我们仅使用创建自己的syscall。
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; }
GitHub上游。
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,并且由于它是为极简主义而设计的,因此我们可以尝试从那里推断出它是如何完成的。
insmod
在版本1.24.2中,入口点位于modutils/insmod.cfunction上insmod_main。
modutils/insmod.c
insmod_main
该IF_FEATURE_2_4_MODULES是对老年人的Linux内核2.4模块可选支持,所以我们就可以忽略它。
IF_FEATURE_2_4_MODULES
这只是modutils.c功能bb_init_module。
modutils.c
bb_init_module
bb_init_module 尝试两件事:
mmap
try_to_mmap_module
这总是设置image_size为.ko文件的大小,这是一个副作用。
image_size
.ko
malloc
xmalloc_open_zipped_read_close
如果该文件是zip,则此函数可以选择先解压缩文件,否则仅对malloc进行解压缩。
我不明白为什么要执行此压缩业务,因为我们甚至不能依靠它,因为try_to_mmap_module似乎无法解压缩。
终于来了电话:
init_module(image, image_size, options);
image放在内存中的可执行文件在哪里,而选项就像""我们在调用时insmod file.elf不带其他参数的情况一样。
image
""
insmod file.elf
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
如果它不存在,我认为是glibc,但是man init_module说:
glibc不支持init_module()系统调用。glibc标头中未提供任何声明,但经过一段古怪的历史,glibc确实为此系统调用导出了一个ABI。因此,为了使用此系统调用,在代码中手动声明接口就足够了。或者,您可以使用syscall(2)调用系统调用。
明智地使用BusyBox的建议并使用syscallglibc提供的建议和使用,并为系统调用提供C API。