__setup宏简介

来源:互联网 发布:内网22的端口怎么设置 编辑:程序博客网 时间:2024/06/15 01:17

Origin: http://blog.csdn.net/wushuan10141/article/details/5914258


内核组件用__setup宏来注册关键字及相关联的处理函数,__setup宏在include/linux/init.h中定义,其原型如下:

#define __setup(str, fn)__setup_param(str, fn, fn, 0) (__setup_param定义请参见include/linux/init.h)其中:str是关键字,fn是关联处理函数。__setup只是告诉内核在启动时输入串中含有str时,内核要去执行fn。Str必须以“=”符结束以使parse_args更方便解析。紧随“=”后的任何文本都会作为输入传给 fn。

下面的例子来自于net/core/dev.c,其中netdev_boot_setup作为处理程序被注册给“netdev=”关键字:

__setup("netdev=", netdev_boot_setup);

在这个情景中作了替换是这样的

static char __setup_str_netdev_setup[] = "netdev=";

static struct kernel_param __setup_netdev_setup =

{ __setup_str_netdev_setup, console_setup}

不同的关键字可以注册相同的处理函数,例如在net/ethernet/eth.c中为“ether=”关键字注册了同样的处理函数netdev_boot_setup。当代码作为模块被编译时,__setup宏被忽视,你可以在include/linux/init.h中看到__setup宏是怎样变化的,不管后续包含它的文件是否是模块,include/linux/init.h都是独立的。

start_kernel两次调用parse_args解析启动配置字符串的原因是启动选项事实上分为两类,且每次调用值能够兼顾到其中一类:

缺省选项:绝大多数选项归于此类,这些选项由__setup宏定义并在第二次调用parse_args时处理。

先期(处理)选项:

#define early_param(str, fn)__setup_param(str, fn, fn, 1) 在内核启动阶段,有些选项要在其它选项之前被处理,内核提供了early_param宏以代替__setup宏申明此类选项。这些选项由parse_early_params函数解析。early_param宏和__setup宏仅有的不同就是前者设置了一个特殊标志让内核能够区分两种不同的状况。

启动时选项在内核2.6中的处理方式已经改变,但并非所有的内核代码都因此而更新。在最近一次改变之前,还仅用__setup宏。因此,遗留下来将被更新的代码现在使用__obsolete_setup宏。但用户用__obsolete_setup宏定义的选项给内核时,内核打印一条警告消息说明它已是废弃状态,并提供一个文件指针和随后被公告的源代码行信息。

 

图7-1概述了几个宏之间的关系:它们都包裹了普通的__setup_param函数。

 

图7-1 setup_param宏及其包裹物

注意:传给__setup宏的程序被放到.init.setup内存节,这样,在“启动时初始化代码”一节中可以很清晰的看出这样做的效果。

在arch/arm/kernel/vmlinux.lds.S关于ld 链接器的脚本文件有这样的一段 __setup_start = .;

.setup.init : { *(.setup.init) }

__setup_end = .;

有如下的理解:

1、 所有标识为__init的函数在链接的时候都放在.init.text这个区段内, 在这个区段中,函数的摆放顺序是和链接的顺序有关的,是不确定的。

2、所有的__init函数在区段.initcall.init中还保存了一份函数指针, 在初始化时内核会通过这些函数指针调用这些__init函数指针,并在整个初始化完成后,释放整个init区段(包括.init.text,.initcall.init等),注意,这些函数在内核初始化过程中的调用顺序只和这里的函数指针的顺序有关,

和1)中所述的这些函数本身在.init.text区段中的顺序无关。

在2.4内核中,这些函数指针的顺序也是和链接的顺序有关的,是不确定的。

在2.6内核中,initcall.init区段又分成7个子区段,分别是

.initcall1.init

.initcall2.init

.initcall3.init

.initcall4.init

.initcall5.init

.initcall6.init

.initcall7.init

当需要把函数fn放到.initcall1.init区段时,只要声明 core_initcall(fn); 即可。

其他的各个区段的定义方法分别是:

core_initcall(fn)--->.initcall1.init

postcore_initcall(fn)--->.initcall2.init

arch_initcall(fn)--->.initcall3.init

subsys_initcall(fn)--->.initcall4.init

fs_initcall(fn)--->.initcall5.init

device_initcall(fn)--->.initcall6.init

late_initcall(fn)--->.initcall7.init

而与2.4兼容的initcall(fn)则等价于device_initcall(fn)。

各个子区段之间的顺序是确定的,即先调用.initcall1.init中的函数指针,再调用.initcall2.init中的函数指针,等等。 而在每个子区段中的函数指针的顺序是和链接顺序相关的,是不确定的。在内核中,不同的init函数被放在不同的子区段中,因此也就决定了它们的调用顺序。这样也就解决了一些init函数之间必须保证一定的调用顺序的问题。


原创粉丝点击