编译器/链接器如何解析内核API,例如从linux上的模块调用的'printk'

Dew*_*mar 3 kernel kernel-module linux-device-driver linux-kernel

我编写了一个示例hello.ko内核模块:

#include <linux/module.h>      /* Needed by all modules */
#include <linux/kernel.h>      /* Needed for KERN_INFO */

int init_module(void)
{
        printk(KERN_INFO "Hello world.\n");
        return 0;
}

void cleanup_module(void)
{
        printk(KERN_INFO "Goodbye world 1.\n");
}
Run Code Online (Sandbox Code Playgroud)

在这里,我使用了"printk"方法,这是Linux公开的内核API.我可以在"/ proc/kallsyms"中看到Linux导出的符号.我很想知道gcc/ld如何链接被调用的内核API?gcc/ld是从"/ proc/kallsyms"或其他文件获取内核方法的地址并执行链接吗?如果是的话,gcc/ld怎么知道呢?我无法说出任何选择.

pmd*_*mdj 7

Linux内核的模块加载器基本上包含一个特殊用途的运行时链接器..ko文件实际上是一个像任何其他文件一样的对象文件,因此它带有一个符号表.如果你运行nm它(nm <path/to/some_module.ko>),你会看到许多标记为"U"的符号,即"未定义".这包括由模块用于核心内核函数的符号,如printk,__kmalloc,kfree等,但在许多情况下也可由其他模块来实现的符号.

加载模块时,内核会运行模块的未定义符号,并在(运行时)符号表中查找它们,修补相关的内存位置,就像任何其他链接器一样.如果符号表中还没有任何未定义的符号,则加载将失败(通过使用insmod而不是modprobe因为它不会加载依赖项而易于演示).它还将模块导出的任何符号添加到符号表中以供其他模块使用,跟踪依赖关系,这样您就无法抽出另一个模块使用的模块.Ilya Matveychikov已在评论中链接到模块加载器中的相关代码,如果您想了解所有血腥细节,这将有所帮助.