问题:
比如在printk.c有这样一句
__setup("console=",console_setup);
还有,在main.c中
__setup("root=",root_dev_setup);
人家的精彩回答:
你的这个问题,我从google上查找到了一些资料,再结合内核源代码,就在这里把这个问题说的清楚一点.
首先,这里有一个简短的回答,
从这上面的意思是这里会从main.c 中的checksetup
函数中运行,这个函数是这样的
static int __init checksetup(char *line)
{
struct kernel_param *p;
p = &__setup_start;
do {
int n = strlen(p->str);
if (!strncmp(line,p->str,n)) {
if (p->setup_func(line+n))
return 1;
}
p++;
} while (p < &__setup_end);
return 0;
}
这里的意思是从__setup_start
开始处到__setup_end
处中查找一个数据结构,这个数据结构中有str与setup_func这两个数据成员变量.
只要与这里面的str与输入的参数字符串相匹配,就会调用个这个字符串后面所指的内容,
对于你这里所说的 __setup("console=",console_setup);
就是你在启动linux内核的时候如果有这么一个参数输入console=ttyS1
,那内核就会
把默认的tty定位为ttyS1,这个在consol_setup
函数的字符串处理中完成,因为它最后是确定 prefered_console 的参数.
那把这在这里实现这个的内容是这样的,
__setup()
是一个宏定义,在include/linux/init.h这个文件中.
struct kernel_param {
const char *str;
int (*setup_func)(char *);
};
extern struct kernel_param __setup_start, __setup_end;
#define __setup(str, fn) \
static char __setup_str_##fn[] __initdata = str; \
static struct kernel_param __setup_##fn __attribute__((unused)) __initsetup = { __setup_str_##fn, fn }
在这个情景中作了替换是这样的
static char __setup_str_console_setup[] = "console=";
static struct kernel_param __setup_console_setup = { __setup_str_console_setup, console_setup}
这样你还可能不是很清楚,那你就要参考arch/i386/vmlinuz.lds这个关于ld 链接器的脚本文件有这样的一段
__setup_start = .;
.setup.init : { *(.setup.init) }
__setup_end = .;
这里的意思就是__setup_start
是一个节的开始,而__setup_end
是一个节的结束,这个节的名称是.setup.init
, 这个你可以用readelf -a
这个来看一下你的vmlinux-2.4.20-8(后面的数字与你的内核版本有关)这个文件,可以看到有一个叫.setup.init
的节, __setup_start
就是指这个节的开始,那这个节中有什么内容呢,其实就是一个数据结构, 一个就是str, 一个就是setup_func
,
举个例子,所有的这些都是用readelf
与od
命令得到的
我现在用的内核版本,它的.setup.init
的节在0x26dd60的文件偏移处.
[10] .data.init PROGBITS c0368040 268040 005d18 00 WA 0 0 32
[11] .setup.init PROGBITS c036dd60 26dd60 0001b0 00 WA 0 0 4
840: c0355d40 343 FUNC LOCAL DEFAULT 9 console_setup
再用下面一条命令
od --address-radix=x -t x4 vmlinux-2.4.20-8 |grep -A 20 26dd60 |head -20 | grep c0355d40
可以得到
26de40 c036943b c0355d10 c0369447 c0355d40
就可以得到下面的内容,
269440 b l i n k = nul c o n s o l e = nul
269450 r e s e r v e = nul nul nul nul nul nul nul nul
269460 ` dc4 6 @ ` dc4 6 @ c p u f r e q =
"console="
这个值果真就在这里.
(注:前面od 的选项--address-radix=
表示的是显示文件偏移量的格式,默认下是o就是八进制, -t 表示显示文件二进制的形式
默认是o6 就是八进制的6位长,而-a表示显示的是字符串格式.)
这是一点感受,与大家分享,希望大家提出宝贵意见.
//补充///
参见include/linux/init.h和vmlinux.lds
所有标识为__init
的函数在链接的时候都放在.init.text
这个区段内,
在这个区段中,函数的摆放顺序是和链接的顺序有关的,是不确定的。
所有的__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函数之间必须保证一定的调用顺序的问题。
这些顺序和make dep
没有关系。
(转自)
内核组件用__setup
宏来注册关键字及相关联的处理函数,__setup
宏在include/linux/init.h中定义,其原型如下:
__setup(string, function_handler)
其 中:string是关键字,function_handler
是关联处理函数。__setup
只是告诉内核在启动时输入串中含有string时,内核要去 执行function_handler
。String必须以“=”符结束以使parse_args
更方便解析。紧随“=”后的任何文本都会作为输入传给 function_handler
。
下面的例子来自于net/core/dev.c,其中netdev_boot_setup
作为处理程序被注册给“netdev=”
关键字:
__setup("netdev=", netdev_boot_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
时处理。early_param
宏以代替__setup
宏申明此类选项。这些选项由 parse_early_params
函数解析。early_param
宏和__setup
宏仅有的不同就是前者设置了一个特殊标志让内核能够区分两种不 同的状况。这个标志是我们将在“.init.setup
内存区”小节中看到的obs_kernel_param
结构的一部分。启动时选项在内核 2.6中的处理方式已经改变,但并非所有的内核代码都因此而更新。在最近一次改变之前,还仅用__setup
宏。因此,遗留下来将被更新的代码现在使用 __obsolete_setup
宏。但用户用__obsolete_setup
宏定义的选项给内核时,内核打印一条警告消息说明它已是废弃状态,并提供 一个文件指针和随后被公告的源代码行信息。
图7-1概述了几个宏之间的关系:它们都包裹了普通的__setup_param
函数。
转载来源:
因篇幅问题不能全部显示,请点此查看更多更全内容