结构体struct module内核的其中之一的模块,多个模块形成的链表是模块的集合。通过insmod(实际执行module_init系统调用)把本模块插入集合,相当于插入链表当中。
如上所述会逐步替换,最终会替换成如下:(所以说以上的宏使用都是等价的,都可以使用)
至于如下的这个是什么意思? 我还没弄明白。
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
其中level参数即0~7(实际是0~7s)共14个级别,
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
这样内核在编译时会生产14个名为”.initcall.(0~7s).init”的section,例如.initcall0.init、.initcall2s.init等。被不同宏修饰的函数被放到对应的section中,例如上例的fs_initcall(acpi_event_init)最终会被放到.initcall5.init。
我们向内核注册一个模块,一般会使用命令insmod,该命令实际上调用了系统调用module_init(),在该系统调用函数中,首先调用module_init(),参数传递过来的初始化函数.
如:
static int __init init_function(){…..}
module_init(init_function);
在模块自动加载或手动注册的时候,会自动调用init_function函数,切记init_function的函数原型(static int __init init_function(){…..}),一定不可以修改,必须这么写,这是内核的规定。
__init, __initdata等属性标志, 是要把这种属性的代码放入目标文件的.init.text节, 数据放入.init.data节──这一过程是通过编译内核时为相关目标平台提供了xxx.lds链接脚本, 来指导ld完成的。 对模s3c2410来说, 可以参考arch/s3c2410/kernel/vmlinux.lds.S文件。
对编译成module的代码和数据来说, 当模块加载时, __init属性的函数就被执行;
对静态编入内核的代码和数据来说, 当内核引导时, do_basic_setup()函数调用do_initcalls()函数, 后者负责所有.init节函数的执行。
和module_init(init_function); 对应的函数是module_exit(init_function);
如:
static void __exit exit_function(){…..}
module_exit(exit_function);
__exit修饰词标记函数只在模块卸载时使用。
如果模块被直接编进内核则该函数就不会被调用。如果内核编译时没有包含该模块,则此标记的函数将被简单地丢弃。
下面是一个简单的例子
include
#include
//“Dual BSD/GPL”或者 “Dual MPL/GPL”或者 “GPL ,GPL v2,”其中之一
MODULE_LICENSE(*****);
static int __init init_function(void)
{
//加载模块时调用
return 0;
}
static void __exit exit_function(void)
{
//模块卸载时调用
}
//切记一定要记得写下面2个方法。最好要在函数的最后写,避免不必要的函数声明
module_init(init_function);
module_exit(exit_function);
链接脚本的如下代码:
.initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
__initcall_start = .;
INITCALLS
__initcall_end = .;
}
生成了initcall表,起始地址和结束地址存在__initcall_start和__initcall_end中。很多资料说根据level参数的值不同,内核在不同阶段调用这些函数。但从代码来看,我认为并非如此,内核只分两个阶段调用,即early initcall阶段和剩余阶段(level = 0~7s)。感兴趣的朋友可以看看上面INITCALLS宏的展开以及do_initcalls()、do_pre_smp_initcalls()两个函数,很容易就能明白。
在构建data segment的最后部分,我们看到如下代码:
.bss : AT(ADDR(.bss) - LOAD_OFFSET) {
__init_end = .;
__bss_start = .; /* BSS */
*(.bss.page_aligned)
*(.bss)
. = ALIGN(4);
__bss_stop = .;
_end = . ;
/* This is where the kernel creates the early boot page tables */
. = ALIGN(PAGE_SIZE);
pg0 = . ;
}
它告诉我们.bss section位于data segment的最后,变量pg0存放的是“Provisional kernel Page Tables”的地址,
模块便与一个struct module结构体相关联,并成为内核的一部分。
下面是结构体struct module的完整定义,接下来会逐个解释:
struct module
{
enum module_state state;
struct list_head list;
char name[MODULE_NAME_LEN];
struct module_kobject mkobj;
struct module_param_attrs *param_attrs;
const char *version;
const char *srcversion;
const struct kernel_symbol *syms;
unsigned int num_syms;
const unsigned long *crcs;
const struct kernel_symbol *gpl_syms;
unsigned int num_gpl_syms;
const unsigned long *gpl_crcs;
unsigned int num_exentries;
const struct exception_table_entry *extable;
int (*init)(void);
void *module_init;
void *module_core;
unsigned long init_size, core_size;
unsigned long init_text_size, core_text_size;
struct mod_arch_specific arch;
int unsafe;
int license_gplok;
#ifdef CONFIG_MODULE_UNLOAD
struct module_ref ref[NR_CPUS];
struct list_head modules_which_use_me;
struct task_struct *waiter;
void (*exit)(void);
#endif
#ifdef CONFIG_KALLSYMS
Elf_Sym *symtab;
unsigned long num_symtab;
char *strtab;
struct module_sect_attrs *sect_attrs;
#endif
void *percpu;
char *args;
};
本文来自CSDN博客,转载请标明出处:学习笔记/ARM汇编及相关/驱动开发中__init,__exit等作用.mht