分类:
2012-08-22 15:02:15
原文地址: Kernel Parameters 作者:tuyer
Linux内核参数是在Linux内核中由宏__setup定义的一系列参数。内核参数包括启动参数和内核模块参数,完整的内核参数列表可以参见Documents/kernel-parameters.txt。
一 关于__setup宏和参数的定义
__setup的定义在include/linux/init.h
#define __setup(str, fn) \ __setup_param(str, fn, fn, 0) |
__setup_param的定义为
#define __setup_param(str, unique_id, fn, early) \ static char __setup_str_##unique_id[] __initdata __aligned(1) = str; \ static struct obs_kernel_param __setup_##unique_id \ __used __section(.init.setup) \ __attribute__((aligned((sizeof(long))))) \ = { __setup_str_##unique_id, fn, early } |
其中使用的 obs_kernel_param结构体为
struct obs_kernel_param { const char *str; int (*setup_func)(char *); int early; }; |
可见,__setup宏的作用是使用str值和函数句柄fn初始化一个static结构体 obs_kernel_param。该结构体在链接后存在于.init.setup段。其实该段也就是所有内核参数所在的处。该段的起始地址是__setup_start,结束地址为__setup_en。
同样的还有一个early_param宏,也是设置一个内核参数,不过改参数是早期启动时相关的。
比如说定义一个内核参数来实现对init程序的指定。见init/main.c中
static int __init init_setup(char *str) { unsigned int i;
execute_command = str; /* * In case LILO is going to boot us with default command line, * it prepends "auto" before the whole cmdline which makes * the shell think it should execute a script with such name. * So we ignore all arguments entered _before_ init=... [MJ] */ for (i = 1; i < MAX_INIT_ARGS; i++) argv_init[i] = NULL; return 1; } __setup("init=", init_setup); |
二 kernel_param 结构体和参数的存储
内核参数结构体的定义,见include/linux/moduleparam.h
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; }; }; |
其中,联合体内定义的三个成员,第一个其实是字符类型的封装。
struct kparam_string { unsigned int maxlen; char *string; }; |
第二个是数组类型的封装。
struct kparam_array { unsigned int max; unsigned int *num; param_set_fn set; param_get_fn get; unsigned int elemsize; void *elem; }; |
同时 kernel_param还定义了两个方法,以函数指针存在。分别是设置和读取操作。
/* Returns 0, or -errno. arg is in kp->arg. */ typedef int (*param_set_fn)(const char *val, struct kernel_param *kp); /* Returns length written or -errno. Buffer is 4k (ie. be short!) */ typedef int (*param_get_fn)(char *buffer, struct kernel_param *kp); |
还剩下的常字符串类型成员name为内核参数的名称,而perm为权限????。
kernel_param结构体的初始化方法定义为
/* This is the fundamental function for registering boot/module parameters. perm sets the visibility in sysfs: 000 means it's not there, read bits mean it's readable, write bits mean it's writable. */ #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 } } |
和结构体 obs_kernel_param类似,该宏函数保持所有实例存在于__param段。该段的起始地址是__start___param,结束地址为__stop___param。具体链接脚本在include/asm-generic/vmlinux.lds.h
我们也给一个例子(net/ipv4/netfilter/nf_nat_ftp.c)
static int warn_set(const char *val, struct kernel_param *kp) { printk(KERN_INFO KBUILD_MODNAME ": kernel >= 2.6.10 only uses 'ports' for conntrack modules\n"); return 0; } module_param_call(ports, warn_set, NULL, NULL, 0); |
处理module_param_call之外,还有core_param也可以定义内核参数,不过内核参数不可以模块化,也不可以使用前缀命名(如“printk.”)。
三 内核命令行
内核命令行(command line),是内核参数传递的载体。在init/main.c中定义。
/* Untouched command line saved by arch-specific code. */ char __initdata boot_command_line[COMMAND_LINE_SIZE]; /* Untouched saved command line (eg. for /proc) */ char *saved_command_line; /* Command line for parameter parsing */ static char *static_command_line; |
四 启动时内核参数的处理
内核启动线程start_kernel中,第一次与内核参数有关的操作为
char * command_line; ........ setup_arch(&command_line) ........ |
该函数是体系结构特有的初始化。定义在arch/
对于x86是函数为
void __init setup_arch(char **cmdline_p) { ....... #ifdef CONFIG_CMDLINE_BOOL #ifdef CONFIG_CMDLINE_OVERRIDE strlcpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE); #else if (builtin_cmdline[0]) { /* append boot loader cmdline to builtin */ strlcat(builtin_cmdline, " ", COMMAND_LINE_SIZE); strlcat(builtin_cmdline, boot_command_line, COMMAND_LINE_SIZE); strlcpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE); } #endif #endif
strlcpy(command_line, boot_command_line, COMMAND_LINE_SIZE); *cmdline_p = command_line; ....... } |
其中builtin_cmdline定义为,其实就是内核编译时指定的内嵌命令行
static char __initdata builtin_cmdline[COMMAND_LINE_SIZE] = CONFIG_CMDLINE; |
总结以上代码就是:
如果内核支持命令行重载(编译时指定命令行,即内嵌命令行)
复制内嵌命令行到cmdline_p(指向 kernel_start函数中的command_line)
如果内核不支持命令行重载,且内嵌命令行为不为空
合并内嵌命令行和启动命令行,并复制给cmdline_p
对于ARM架构
由于ARM架构对内核参数的传递方法和x86有很大的不同,所以这一部分也和之前有很大的不同。ARM是通过tag链表的方式来传递内核参数的。具体可以参考另一篇Linux Kernel Study : ARM Linux Tag Lists
下一步就是对获得的command_line进行处理了。继续在start_kernel函数中。
...... setup_command_line(command_line); ...... |
该函数的定义为
static void __init setup_command_line(char *command_line) { saved_command_line = alloc_bootmem(strlen (boot_command_line)+1); static_command_line = alloc_bootmem(strlen (command_line)+1); strcpy (saved_command_line, boot_command_line); strcpy (static_command_line, command_line); } |
主要是为 saved_command_line和 static_command_line分配内存,备份 boot_command_line到saved_command_line,复制command_line到static_commmand_line。
现在所有命令行都初始化完毕,开始分析命令行并做相应设置。继续在start_kernel函数中。
...... printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line); parse_early_param(); parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption); ...... |
在打印内核命令行之后,调用的parse_early_param();其定义如下:
void __init parse_early_param(void) { static __initdata int done = 0; static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];
if (done) return;
/* All fall through to do_early_param. */ strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE); parse_early_options(tmp_cmdline); done = 1; }
void __init parse_early_options(char *cmdline) { parse_args("early options", cmdline, NULL, 0, do_early_param); } |
而parse_args函数的原型在include/linux/moduleparam.h,定义在kernel/params.c 。
/* Called on module insert or kernel boot */ extern int parse_args(const char *name, char *args, struct kernel_param *params, unsigned num, int (*unknown)(char *param, char *val)); |
在这里parse_early_param()主要的作用是处理内核命令行(boot_command_line)的内核参数。也就是处理在内核命令行中有定义的早期参数值(early=1),特别的还包括内核参数console和earlycon。都和输出流有关,内核启动时的打印信息就要求该设备的正确配置。
之后,第二步是处理其他内核参数(command_line)。
printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line); parse_early_param(); parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption); |
这里主要是根据command_line 的内核参数设置,如过有未知的参数,则作为init的参数传递给init。环境变量存在envp_init数组,命令行存储在argv_init数组。
static void run_init_process(char *init_filename) { argv_init[0] = init_filename; kernel_execve(init_filename, argv_init, envp_init); } |