当前位置: 首页 > 面试题库 >

如何从C代码加载Linux内核模块?

华恩
2023-03-14
问题内容

我有一个同时具有两个外部内核模块和一个用户空间守护程序的应用程序。我想在启动时从用C编写的守护程序代码中加载模块,然后在干净退出时将其卸载。我可以用比system("modprobe module");使用相应的方式更干净的方式加载它们rmmod吗?


问题答案:

最小的可运行示例

使用此简单的参数打印机模块,在QEMU
+ Buildroot VM和Ubuntu 16.04主机上进行了测试。

我们使用init_module/ finit_moduleremove_module
Linux系统调用。

Linux内核为模块插入提供了两个系统调用:

  • init_module
  • finit_module

和:

man init_module

证明文件:

系统调用finit_module()类似于init_module(),但是从文件描述符fd中读取要加载的模块。当可以从内核模块在文件系统中的位置确定内核模块的真实性时,它很有用;在可能的情况下,可以避免使用经过密码签名的模块来确定模块的真实性的开销。param_values参数与init_module()相同。

finit是较新的版本,仅在v3.8中添加。更多理由:https :
//lwn.net/Articles/519010/

glibc似乎没有为他们提供C包装器,因此我们仅使用创建自己的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;
}

GitHub上游。

Busybox源码解释

Busybox提供了insmod,并且由于它是为极简主义而设计的,因此我们可以尝试从那里推断出它是如何完成的。

在版本1.24.2中,入口点位于modutils/insmod.cfunction上insmod_main

IF_FEATURE_2_4_MODULES是对老年人的Linux内核2.4模块可选支持,所以我们就可以忽略它。

这只是modutils.c功能bb_init_module

bb_init_module 尝试两件事:

  • mmap该文件通过进入内存try_to_mmap_module

这总是设置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不带其他参数的情况一样。

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

如果它不存在,我认为是glibc,但是man init_module说:

glibc不支持init_module()系统调用。glibc标头中未提供任何声明,但经过一段古怪的历史,glibc确实为此系统调用导出了一个ABI。因此,为了使用此系统调用,在代码中手动声明接口就足够了。或者,您可以使用syscall(2)调用系统调用。

明智地使用BusyBox的建议并使用syscallglibc提供的建议和使用,并为系统调用提供C API。



 类似资料:
  • 主要内容:initramfe虚拟文件系统GRUB 加载了内核之后,内核首先会再进行二次系统的自检,而不一定使用 BIOS 检测的硬件信息。这时内核终于开始替代 BIOS 接管 Linux 的启动过程了。 内核完成再次系统自检之后,开始采用动态的方式加载每个硬件的模块,这个动态模块大家可以想象成硬件的驱动(默认 Linux 硬件的驱动是不需要手工安装的,如果是重要的功能,则会直接编译到内核当中;如果是非重要的功能,比如硬件驱动会编译为模块

  • MANAGING THE LINUX KERNEL AND LOADABLE KERNEL MODULES 所有操作系统至少由两个主要组件组成。其中第一个也是最重要的是内核。 内核位于操作系统的中心,控制着操作系统所做的一切,包括管理内存,控制 CPU,甚至控制用户在屏幕上看到的内容。操作系统的第二个元素通常被称为用户区域,几乎包括其他所有元素。 内核被设计成一个受保护或特权的区域,只能由 roo

  • 问题内容: 我正在为Linux内核编写模块,并且我想在init函数中创建一些设备节点 我还希望内核为我的第一个节点分配一个次要编号,然后由我自己分配其他节点的次要编号。 我该如何在代码中做到这一点。我不想使用mknod从外壳创建设备 问题答案: 要更好地控制设备编号和设备创建,您可以执行以下步骤(而不是): 致电以获取一个主要号码和一系列次要号码。 使用创建设备类的设备。 对于每个设备,调用并将字

  • 问题内容: 我尝试安装内核模块。它已成功安装在容器内。确实令人惊讶,但是没有在容器内或主机系统中列出此模块。如何在容器中装入新的内核模块?(容器,主机) 问题答案: 容器通过系统调用与内核交互,并且不包含内核的任何部分或容器内部的内核模块。这就是为什么容器设计轻巧且便于携带的原因之一。xfsprogs也是用户空间程序,而不是内核模块。 如何在容器中加载新的内核模块?(CentOS容器,Ubuntu

  • 问题内容: 据我所知,要从内核空间通知用户空间,一种方法是使用轮询。这意味着内核驱动程序应首先提供轮询方法。下面的代码是从互联网上找到的,它确实有效! 我可以这样使它起作用: 然后 看结果。 但是如何添加轮询方法呢?我试了几次,但还是失败了。有人可以帮忙吗?谢谢! 问题答案: 您可以在内核本身中找到一些很好的示例。看下一个文件: 驱动程序/char/rtc.c fs / proc / kmsg.c

  • 问题内容: 当我经历了以下块的Linux的字符设备驱动程序代码,我发现结构的指针在。 我想知道指向的结构及其完整元素。 此结构有什么作用? 问题答案: 它是指向当前进程(即已发出系统调用的进程)的指针。 从文档: 当前过程 尽管内核模块不像应用程序那样顺序执行,但是内核执行的大多数操作都与特定进程有关。内核代码可以通过访问全局项current来了解驱动它的当前进程,该全局项current是指向st