17.3. 第二阶段第二个阶段"Booting kernel",用来解析__start___param与__stop___param之间的struct kernel_param成员,继续查看一下内核的链接脚本: include/asm-generic/vmlinux.lds.h
/* Built-in module parameters. */ __param : AT(ADDR(__param) - LOAD_OFFSET) { VMLINUX_SYMBOL(__start___param) = .; *(__param) VMLINUX_SYMBOL(__stop___param) = .; . = ALIGN((align)); VMLINUX_SYMBOL(__end_rodata) = .; }
arch/arm/kernel/vmlinux.lds
__start___param = .; *(__param) __stop___param = .;所以它们之间包含的段由__param声明: include/linux/moduleparam.h
#define __module_param_call(prefix, name, set, get, arg, perm) /* Default value instead of permissions? */ static int __param_perm_check_##name __attribute__((unused)) = BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2)) + BUILD_BUG_ON_ZERO(sizeof(""prefix) > MAX_PARAM_PREFIX_LEN); static const char __param_str_##name[] = prefix #name; static struct kernel_param __moduleparam_const __param_##name __used __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) = { __param_str_##name, perm, set, get, { arg } }这是一个非常复杂的宏,显然它针对模块参数。它定义了一个kernel_param类型的变量,这个变量被放到了段__param。 struct kernel_param {
const char *name;
unsigned int perm;
param_set_fn set;
param_get_fn get;
union {
void *arg;
const struct kparam_string *str;
const struct kparam_array *arr;
};
};内核中并不推荐直接使用__module_param_call宏来定义kernel_param的实例,而是又扩展了两个宏module_param_named和module_param。 #define module_param_call(name, set, get, arg, perm) __module_param_call(MODULE_PARAM_PREFIX, name, set, get, arg, perm)
/* Helper functions: type is byte, short, ushort, int, uint, long,
ulong, charp, bool or invbool, or XXX if you define param_get_XXX,
param_set_XXX and param_check_XXX. */
#define module_param_named(name, value, type, perm) param_check_##type(name, &(value)); module_param_call(name, param_set_##type, param_get_##type, &value, perm); __MODULE_PARM_TYPE(name, #type)
#define module_param(name, type, perm) module_param_named(name, name, type, perm)通常模块中使用module_param_named和module_param声明参数变量。为了理解上面的所做所为,这里对内核模块的编译做一个详尽的分析和说明。Linux编译内核模块的命令为make modules(如果不是在内核根文件夹下编译,那么需要-C参数指定内核文件夹)。在Linux的Makefile中对应的动作如下: modules: $(vmlinux-dirs) $(if $(KBUILD_BUILTIN),vmlinux)
$(Q)$(AWK) '!x[$$0]++' $(vmlinux-dirs:%=$(objtree)/%/modules.order) > $(objtree)/modules.order
@echo ' Building modules, stage 2.';
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.fwinst obj=firmware __fw_modbuild注意到它是通过Makefile.modpost进行编译和链接的,总共分为六个步骤。其中编译时的命令为: quiet_cmd_cc_o_c = CC $@
cmd_cc_o_c = $(CC) $(c_flags) $(CFLAGS_MODULE) -c -o $@ $
#define ___module_cat(a,b) __mod_ ## a ## b
#define __module_cat(a,b) ___module_cat(a,b)
#define __MODULE_INFO(tag, name, info) static const char __module_cat(name,__LINE__)[] __used __attribute__((section(".modinfo"),unused)) = __stringify(tag) "=" info
#else /* !MODULE */
#define __MODULE_INFO(tag, name, info)
#endif自此,模块编译和编译入内核的两种方式从这里开始相揖别。对它们的处理完全不同。首先对模块编译相关的宏做一解释:__MODULE_INFO,它用来记录模块的相关信息,并且放在.ko文件的.modinfo代码节中。__MODULE_INFO通常并不由模块直接调用,而是通过宏再次对它进行了封装。 #define __MODULE_PARM_TYPE(name, _type) __MODULE_INFO(parmtype, name##type, #name ":" _type)而module_param_string,module_param_named和module_param又是对它的封装,用来定义参数。这三个宏被开放给模块使用。另外最通用的几个中在module.h中定义: /* Generic info of form tag = "info" */
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)
#define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias)
#define MODULE_LICENSE(_license) MODULE_INFO(license, _license)
#define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description)
#define MODULE_PARM_DESC(_parm, desc) __MODULE_INFO(parm, _parm, #_parm ":" desc)
#define MODULE_VERSION(_version) MODULE_INFO(version, _version)
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)为了深入对它的了解,一个__MODULE_PARM_TYPE(bisa, "string")的定义被扩展成如下信息,显然它被定义成一个静态数组,数组中定义了参数名,以及参数类型。 #define __stringify_1(x) #x
#define __stringify(x) __stringify_1(x)
static const char __mod_bisatype29[] __used __attribute__((section(".modinfo"),unused)) = "parmtype" "=" "bisa" ":" "string"通过modinfo命令可以查看内核模块的这些数组的内容: # modinfo xt_MARK.ko
filename: xt_MARK.ko
alias: ip6t_MARK
alias: ipt_MARK
description: Xtables: packet mark modification
author: Marc Boucher
license: GPL
depends:
vermagic: 2.6.28.6 preempt mod_unload ARMv6而第二种情况__MODULE_INFO宏被定义成空,所以被编译进内核的模块代码是没有对应的.modinfo节信息的。它使用struct kernel_param来表示参数的相关信息。并且这些信息被放在名为__param的节中。而内核在解析参数到"Booting kernel"阶段时,就会对这些节中的参数名一一匹配,如果通过Bootloader或者在CONFIG_CMDLINE中定义了该参数,那么此时将被解析,以备将来的内嵌的模块进行加载。