Kernel Parameters

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/<ARCH>/kernel/setup.c中。

对于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_linesaved_command_line,复制command_linestatic_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),特别的还包括内核参数consoleearlycon。都和输出流有关,内核启动时的打印信息就要求该设备的正确配置。

之后,第二步是处理其他内核参数(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);

}


总结:
1. 对于在bootloader中,通过tag链表传递的参数,在setup_arch()函数中先处理。
2. 对于通过静态编译进内核的启动命令行参数,在setup_arch()函数中处理完tag链表中的参数后,进行处理,但如果在tag链表中也加入了启动命令行,且在arm中,多未对machine_desc->fixup函数未定义,故tag链表中的启动命令行会覆盖静态的命令行,并通过parse_cmdline()进行处理,且所定义的处理函数通过__early_param(name,fn)放置在.early_param.init段中。
3. 对于通过宏early_param(str, fn) 定义的内核参数,其处理函数通过obs_kernel_param结构体,放置在.init.setup段中,且相应earlay字段置1。其处理在函数parse_early_param()被调用。
4. 对于通过宏__setup(str, fn)定义的内核参数,其处理函数也通过obs_kernel_param结构体,放置在.init.setup段中,但相应earlay字段清0。其处理函数通过parse_args()->unknown_bootoption()->obsolete_checksetup()被调用。
5. 对于通过宏core_param(name, var, type, perm)、module_param(name, type, perm)定义的内核内核参数,不同于上面介绍的内核参数,它们是通过定义相应的字符串处理函数来进行相应的处理,不一定对应内核一个固定的变量,而core_param等宏就是定义相应的变量,而这些变量通过parse_args()->unknown_bootoption()进行设置。
注:

  对很多参数可通过tag链表、启动命令行等进行设置,参数处理函数也可通过不同的宏置于不同的段中,注意上面的处理顺序,后面的处理会覆盖前面的处理。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值