Chinaunix首页 | 论坛 | 博客
  • 博客访问: 630688
  • 博文数量: 51
  • 博客积分: 773
  • 博客等级: 军士长
  • 技术积分: 2392
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-07 21:32
文章分类
文章存档

2018年(1)

2013年(16)

2012年(34)

分类: LINUX

2012-11-28 22:39:24

引导期间的内核选项

Linux运行用户将内核配置选项传递给引导记录,引导记录再把选项传给内核,进而通过引导配置参数微调内核。在系统引导期间,将调用parse_args两次解析引导期间输入的配置参数。

parse_args函数解析的输入字符串参数是"变量名称=值"的形式,根据解析出的关键字并启用适当的处理函数。加载模块时,也会用到parse_args解析命令列参数。

注册关键字

内核组件利用定义在include/linux/init.h中的__setup宏注册关键字和相关联的处理函数:

__setup(string, function_handler)   

string是关键字,而function_handler是相关联的处理函数。内核初始化时parse_args函数解析到关键字string是,就执行相关联的function_handler函数,但string必须以=字符结束,而任何跟在=字符后面的字符串将被作为函数输入参数传递给function_handler函数处理。这些参数存放在内存区块.setup.init中。

如arch\arm\kernel\process.c文件中,实例只需要输入关键字即可:

static int __init nohlt_setup(char *__unused)
{
    hlt_counter = 1;
    return 1;
}

static int __init hlt_setup(char *__unused)
{
    hlt_counter = 0;
    return 1;
}

__setup("nohlt", nohlt_setup);
__setup("hlt", hlt_setup);

在网络子系统的net/core/dev.c文件中,注册了netdev=关键字和对应的处理函数netdev_boot_setup函数:

int __init netdev_boot_setup(char *str)
{
    int ints[5];
    struct ifmap map;

    str = get_options(str, ARRAY_SIZE(ints), ints);
    if (!str || !*str)
        return 0;

    /* Save settings */
    memset(&map, 0, sizeof(map));
    if (ints[0] > 0)
        map.irq = ints[1];
    if (ints[0] > 1)
        map.base_addr = ints[2];
    if (ints[0] > 2)
        map.mem_start = ints[3];
    if (ints[0] > 3)
        map.mem_end = ints[4];

    /* Add new entry to the list */
    return netdev_boot_setup_add(str, &map);
}

__setup("netdev=", netdev_boot_setup);

而在net/ethernet/eth.c文件中,ether=关键字通用被注册到了同一个处理函数netdev_boot_setup。

__setup("ether=", netdev_boot_setup);

当一端代码被编译为模块时,__setup宏会被护理,即定义为空操作。

#ifndef MODULE

……

#define __setup(str, fn)                    \
    __setup_param(str, fn, fn, 0)

……

#else

……

#define __setup(str, func)             /* nothing */

#endif

在init\main.c文件中start_kernel函数中:

asmlinkage void __init start_kernel(void)
{

              ……

     parse_early_param();
     parse_args("Booting kernel", static_command_line, __start___param,
            __stop___param - __start___param,
           &unknown_bootoption);

            ……

}

start_kernel两次调用parse_args以解析引导配置参数的原因在于引导期间的参数实际是分为两类的,每一次调用分别针对其中的一类:

  • 默认选项:多数参数都属于这一类,这些参数由__setup宏定义,有parse_args第二次被调用时处理
  • 早期选项:内核引导期间,有些选项需要更早处理。这些参数是由early_param宏代替__setup宏定义的,这些选项由parse_early_params负责。early_param和__setup宏的唯一区别是early_param会设置一个特殊标记,使内核能区分这两种情况,而此标记是obs_kernel_param数据结构的一部分。

#define __setup(str, fn)                    \
    __setup_param(str, fn, fn, 0)

/* NOTE: fn is as per module_param, not __setup!  Emits warning if fn
* returns non-zero. */
#define early_param(str, fn)                    \
    __setup_param(str, fn, fn, 1)

在start_kernel函数中第二次调用parse_args函数时,会检查有module_param宏填入的模块选项,这些选项会存储在kernel_param数据结构中,module_param宏会确保这些数据结构都会被存放在特定的内存区块中(__param),有指针__start___param

和__stop___param限定。

image

__setup_start……__setup_end:这个区域会在引导阶段结束时释放掉,用户在运行期间不能看到或修改这些选项;

__start___param……__stop___param:这个区域不会被释放,其内容会被输出到/sys文件系统,这些选项可以显示给用户。

有__setup()和early_param()宏定义的关键字会被放入__setup_start……__setup_end区域,只是early_param宏定义的关键字会被设置early标记。

struct obs_kernel_param {
    const char *str;
    int (*setup_func)(char *);
    int early;
};

str是关键字,setup_func是关联的处理函数,early则是early标记。__setup_param宏回家所有的obs_kernel_params实例存储到专用的内存区域内,这样比较容易遍历,__setup_start和__setup_end指针分别指向这个区域的开端和尾端。而当这些区域不再使用时也便于快速释放内存。

阅读(3392) | 评论(1) | 转发(2) |
给主人留下些什么吧!~~

joepayne2013-06-07 14:11:03

兄台完全可以出一本中文版的《understanding linux network internals》了!膜拜一下!小弟我也看了几章节,由于实际操作较少,只能尽量去跟平时使用的接口去联系,僵硬地去理解做做笔记,看了几章就看不下去了。。。