最近在学习内核模块文件的一些知识,记录下来。
ko模块是可重定位目标文件:
一个驱动可以作为一个模块动态的加载到内核里,也可以作为内核的一部分(builtin)静态的编译进内核。
下面我们主要说的是驱动作为一个模块动态的加载到内核里。
// //helloworld.c
#include
#include
//内核模块初始化函数
static int __init hello_init(void)
{
printk("Hello World\n");
return 0;
}
//内核模块退出函数
static void __exit hello_exit(void)
{
printk("exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
当我们编译生成.ko模块时,会在该目录下产生这样一个附加文件helloworld.mod.c,这个文件:
// helloworld.mod.c
__visible struct module __this_module
__section(".gnu.linkonce.this_module") = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
这个后缀为.mod.c的文件会初始化模块 struct module 的这几个成员:
struct module {
......
/* Unique handle for this module */
char name[MODULE_NAME_LEN];
/* Startup function. */
int (*init)(void);
/* Destruction function. */
void (*exit)(void);
/* Arch-specific module values */
struct mod_arch_specific arch;
......
}
通过__section(“.gnu.linkonce.this_module”)我们可以知道将该struct module实例添加到内核模块ELF文件中“.gnu.linkonce.this_module”section中。
readelf -S helloworld.ko
W (write), A (alloc)
readelf -p16 helloworld.ko
在这里我们发现.mod.c后缀文件的模块初始化函数和清理函数名字是init_module和cleanup_module,而我们编写的内核模块程序中是module_init和module_exit:
.init = init_module,
.exit = cleanup_module,
module_init(hello_init);
module_exit(hello_exit);
接下来我们来看看源码实现细节(动态编译成模块):
// linux-4.10.1/include/linux/module.h
/* Each module must use one module_init(). */
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __exittest(void) \
{ return exitfn; } \
void cleanup_module(void) __attribute__((alias(#exitfn)));
这使用了gcc提供的别名属性(attribute(alias)):
将init_module函数的别名设置为initfn,而initfn就是我们模块程序中的初始化函数。
将cleanup_module的别名设置为exitfn,而exitfn就是我们模块程序的清理函数。
其中两个函数的类型为:
// linux-4.10.1/include/linux/init.h
/*
* Used for initialization calls..
*/
typedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);
在我们编写模块程序时,会在初始化函数加上__init前缀,在清理函数加上__exit前缀。
#define __init __section(.init.text) __cold notrace __latent_entropy
#define __exit __section(.exit.text) __exitused __cold notrace
这两个前缀是为了将初始化函数和清理函数放置在ko二进制文件正确的section中。
小结
.gnu.linkonce.this_module section 提供了一个struct module 的实例,其中存储了模块的名字,初始化函数和清理函数,根据该section内核可以判断是否为内核模块,如果没有该section将拒绝加载该文件。
ELF文件中出现的这个section其实是模块的编译工具链完成的,模块的构造工具链为我们在.ko文件中添加了一个“.gnu.linkonce.this_module”section,并初始化了其中的一些成员。在模块加载过程中, load_module函数将使用这个section中的数据来初始化struct module *mod变量。
SYSCALL_DEFINE3(init_module)
-->load_module()
-->layout_and_allocate()
-->setup_load_info()
在模块的加载过程了会调用setup_load_info()函数,内核通过find_sec函数查找到“.gnu.linkonce.this_module”section在Section header table中所对应的索引值info->index.mod,这样就得到了“.gnu.linkonce.this_module”section在内存中的实际地址。注意这是“.gnu.linkonce.this_module”section的临时地址,后面会重新搬运到最终地址。
struct load_info {
Elf_Ehdr *hdr;
unsigned long len;
Elf_Shdr *sechdrs;
char *secstrings, *strtab;
......
struct {
unsigned int sym, str, mod, vers, info, pcpu;
} index;
};
内核用find_sec来寻找某一section在Section header table中的索引值,遍历Section header table中所有的entry(忽略没有SHF_ALLOC标志的section,因为这样的section最终不占有实际内存地址),对每一个entry,先找到其所对应的section name,然后和name进行比较,如果相等,就找到对应的section,返回该section在Section header table中的索引值。
其中需要用到ELF文件中的ELF header和section header。
/* Find a module section: 0 means not found. */
static unsigned int find_sec(const struct load_info *info, const char *name)
{
unsigned int i;
for (i = 1; i < info->hdr->e_shnum; i++) {
Elf_Shdr *shdr = &info->sechdrs[i];
/* Alloc bit cleared means "ignore it." */
if ((shdr->sh_flags & SHF_ALLOC)
&& strcmp(info->secstrings + shdr->sh_name, name) == 0)
return i;
}
return 0;
}
/*
* Set up our basic convenience variables (pointers to section headers,
* search for module section index etc), and do some basic section
* verification.
*
* Return the temporary module pointer (we'll replace it with the final
* one when we move the module sections around).
*/
static struct module *setup_load_info(struct load_info *info, int flags)
{
......
struct module *mod;
......
//根据section的名字.gnu.linkonce.this_module找到其索引值 info->index.mod
info->index.mod = find_sec(info, ".gnu.linkonce.this_module");
/* This is temporary: point mod into copy of data. */
mod = (void *)info->sechdrs[info->index.mod].sh_addr;
......
return mod;
}
在这个阶段中struct module *mod指针指向了实际的struct module实例,从上文我们可以知道该实例提供了模块的名称和指向初始化以及清理函数的指针,但其他成员此时初始化为NULL或者0。
备注:加载内核模块过程后面会将标记有SHF_ALLOC的section重新搬移到新的内存空间中,.gnu.linkonce.this_module”section是一个带有SHF_ALLOC标志的可写数据section,因此.gnu.linkonce.this_module”section也会重新搬移到新的内存空间中。这个过程在layout_sections函数中,后面会有文章详细介绍这个函数,这里不过多介绍。
SYSCALL_DEFINE3(init_module)
-->load_module()
-->layout_and_allocate()
-->setup_load_info()
-->layout_sections()
struct module *mod指针在后面将会重新指向“.gnu.linkonce.this_module”section在内存中的最终地址。
setup_load_info函数也有备注说明:
* Return the temporary module pointer (we'll replace it with the final
* one when we move the module sections around).
在上文中我们提到,定义该结构体struct module 为__this_module。
__visible struct module __this_module
__section(".gnu.linkonce.this_module") = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
内核中定义了一个宏THIS_MODULE:
// linux-4.10.1/include/linux/export.h
#ifdef MODULE
extern struct module __this_module;
#define THIS_MODULE (&__this_module)
#else
#define THIS_MODULE ((struct module *)0)
#endif
在.ko模块程序中使用__this_module和THIS_MODULE都一样:
#include
#include
//内核模块初始化函数
static int __init hello_init(void)
{
printk("module_name = %s\n", __this_module.name);
printk("module_name = %s\n", THIS_MODULE->name);
return 0;
}
//内核模块退出函数
static void __exit hello_exit(void)
{
printk("exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
该文章主要介绍内核模块gnu.linkonce.this_module section的相关知识。
Linux 4.10.0
深入Linux设备驱动程序内核机制
深入Linux内核架构