arm-linux(kernel-2.6.13)的启动过程(1.1/2)
本来想两次分析完,现在不可能了,内容太多了。
start_kernel()好庞大阿,一点一点的看。
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
lock_kernel();
禁止抢占,使用自旋锁,我的cpu(2440)是单核,自旋锁无效。
page_address_init();
配置里没有 CONFIG_HIGHMEM (不大于896M)选项,所以这个函数是空的。
printk(KERN_NOTICE);
printk(linux_banner);
这是一个令人抓狂的函数,下面是他的部分解释。
We try to grab the console_sem. If we succeed, it's easy - we log the output and
call the console drivers. If we fail to get the semaphore we place the output
into the log buffer and return.
printk和printf不同,他首先输出到系统的一个缓冲区内,大约4k,如果登记了console,则调用console->wirte函数输出,否则就一直在buffer里呆着。所以,用printk输出的信息,如果超出了4k,会冲掉前面的。在系统引导起来后,用dmesg看的也就是这个 buffer中的东东。
setup_arch(&command_line);
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;
setup_processor();
打印一些cpu信息,设置elf_hwcap(setup.c里的全局量)变量为list->elf_hwcap。
mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
machine_arch_type 很重要,他在mach-types.h中定义,他是唯一的,由配置文件决定或者uboot传递进来的参数决定。
如果在配置文件中,我们选择了很多个支持的板子类型,那么这个变量将由uboot传递过来的参数决定。比如我们定义了A9M2410和QQ2440。
在mach-types.h中,有:
#ifdef CONFIG_MACH_A9M2410
# ifdef machine_arch_type
# undef machine_arch_type
# define machine_arch_type __machine_arch_type
# else
# define machine_arch_type MACH_TYPE_A9M2410 这里被成功的定义,如果只有这个板子的话,就结束了,
# endif
# define machine_is_a9m2410() (machine_arch_type == MACH_TYPE_A9M2410)
#else
# define machine_is_a9m2410() (0)
#endif
#ifdef CONFIG_ARCH_QQ2440 但是到了这里,发现这个板子的支持也被定义了,那么
# ifdef machine_arch_type
# undef machine_arch_type 这里就会成功
# define machine_arch_type __machine_arch_type 从而使用这个变量,这个变量在那里呢??? ^_^,看看第一篇分析启动的文章吧。
# else
# define machine_arch_type MACH_TYPE_QQ2440
# endif
# define machine_is_qq2440() (machine_arch_type == MACH_TYPE_QQ2440)
#else
# define machine_is_qq2440() (0)
#endif
获取机器描述符指针。打印机器信息,保存名字到machine_name。为了方便,把 machine_desc *mdesc 拿过来。
/*
* Set of macros to define architecture features. This is built into
* a table by the linker.
*/
#define MACHINE_START(_type,_name) \
const struct machine_desc __mach_desc_##_type \
__attribute__((__section__(".arch.info"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
每个mach-xxx.c文件都会有机器描述符的定义,但是真正被编译进来的只是在配置文件里选上的 。可以选很多个。
const struct machine_desc __mach_desc_QQ2440
__attribute__((__section__(".arch.info"))) = {
.nr = MACH_TYPE_QQ2440,
.name = “QQ2440”,
.phys_ram = S3C2410_SDRAM_PA,
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = sbc2440_init_irq,
.map_io = sbc2440_map_io,
.init_machine = sbc2440_init,
.timer = &s3c24xx_timer,
};
可以往下看了。
if (mdesc->soft_reboot)
reboot_setup("s");
没有定义,所以do nothing!
if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);
现在运行在虚拟地址,要想访问taglist要绕道了,加上一个偏移就ok了。
tags = 0x30000100 + (0xc0000000UL - 0x30000000UL)
如果在机器描述符里没有指定mdesc->boot_params,就用初始化的那个tag_list。
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
如果使用的是旧格式的参数传递方式的话,转化成tag的格式,如果转化之后不是tag格式,说明参数有问题,拒绝使用uboot的参数,
转而使用init_tags。
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
很高兴没有fixup成员。
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
parse_tags(tags);
}
这里主要调用parse_tags() 处理标记列表,因为此时的meminfo还没有初始化,成员 nr_banks 是0。
嵌入式开发详解 p243 有对tag结构的描述
tags->hdr.tag == ATAG_CORE 表示标记列表的开始,看parse_tags(tags) = parse_tags(0xc0000100)
/*
* Parse all tags in the list, checking both the global and architecture
* specific tag tables.
*/
static void __init parse_tags(const struct tag *t)
{
for (; t->hdr.size; t = tag_next(t))
if (!parse_tag(t))
printk(KERN_WARNING
"Ignoring unrecognised tag 0x%08x\n",
t->hdr.tag);
}
这段代码扫描的条件是t->hdr.size 不为0,也就是说不要 “开始“ 和 “结束“ 的tag,留下中间有价值的tag。
比如描述内存的tag_mem32,描述命令行的tag_cmdline等等。如果不能识别这个tag,就打印信息。
看 parse_tag(t),希望这个函数返回非0值。
/*
* Scan the tag table for this tag, and call its parse function.
* The tag table is built by the linker from all the __tagtable
* declarations.
*/
static int __init parse_tag(const struct tag *tag)
{
extern struct tagtable __tagtable_begin, __tagtable_end;
struct tagtable *t;
for (t = &__tagtable_begin; t < &__tagtable_end; t++)
if (tag->hdr.tag == t->tag) {
t->parse(tag);
break;
}
return t < &__tagtable_end;
}
看看 tagtable是真么内容,可见里面有一个parse函数指针,用于处理这个具体的tag。
struct tagtable {
u32 tag;
int (*parse)(const struct tag *);
};
功能是,在__tagtable_begin ~ __tagtable_end之间扫描,如果tag.hdr.tag与t.tag成员相匹配,
就调用相应的语法处理函数,如果扫描结束后都没能处理这个tag,说明这个tag不能识别,是个错误的标记,返回非0数值。
在lds里:
__arch_info_end = .; 在这个符号的后面。
__tagtable_begin = .;
*(.taglist)
__tagtable_end = .;
把所有的的.taglist属性的数据都包含了进来,这就是处理从uboot传递过来的 tag_list的函数集合。我们看看都有那些处理函数。
从dump文件找到了下面的内容。
c001d74c <__arch_info_end>:
c001d74c: 54410009 strplb r0, [r1], #-9
c001d750: c000e734 andgt lr, r0, r4, lsr r7
从这里开始
c001d754 <__tagtable_parse_tag_revision>:
c001d754: 54410007 strplb r0, [r1], #-7
c001d758: c000e710 andgt lr, r0, r0, lsl r7
c001d75c <__tagtable_parse_tag_serialnr>:
c001d75c: 54410006 strplb r0, [r1], #-6
c001d760: c000e6dc ldrgtd lr, [r0], -ip
c001d764 <__tagtable_parse_tag_initrd2>:
c001d764: 54420005 strplb r0, [r2], #-5
c001d768: c000e6a8 andgt lr, r0, r8, lsr #13
c001d76c <__tagtable_parse_tag_initrd>:
c001d76c: 54410005 strplb r0, [r1], #-5
c001d770: c000e660 andgt lr, r0, r0, ror #12
c001d774 <__tagtable_parse_tag_ramdisk>:
c001d774: 54410004 strplb r0, [r1], #-4
c001d778: c000e5f8 strgtd lr, [r0], -r8
c001d77c <__tagtable_parse_tag_videotext>:
c001d77c: 54410003 strplb r0, [r1], #-3
c001d780: c000e590 mulgt r0, r0, r5
c001d784 <__tagtable_parse_tag_mem32>:
c001d784: 54410002 strplb r0, [r1], #-2
c001d788: c000e538 andgt lr, r0, r8, lsr r5
c001d78c <__tagtable_parse_tag_core>:
c001d78c: 54410001 strplb r0, [r1], #-1
c001d790: c000e4d8 ldrgtd lr, [r0], -r8
c001d794 <__tagtable_end>:
可以发现命令行不再处理之列,我对 c000e538 和 c000e734 这两个指针 有兴趣(相信你也是),去看看。
c000e538 :快速定位这个函数
static int __init parse_tag_mem32(const struct tag *tag)
{
if (meminfo.nr_banks >= NR_BANKS) { 显然这里不会成立。
printk(KERN_WARNING
"Ignoring memory bank 0x%08x size %dKB\n",
tag->u.mem.start, tag->u.mem.size / 1024);
return -EINVAL;
}
add_memory(tag->u.mem.start, tag->u.mem.size);
return 0;
}
注意这个函数在setup.c中,不要忘记,有两个meminfo结构哦!
看add_memory,这个函数是我苦苦追求的函数^_^,终于抓到你了。
static void __init add_memory(unsigned long start, unsigned long size)
{
/*
* Ensure that start/size are aligned to a page boundary.
* Size is appropriately rounded down, start is rounded up.
*/
size -= start & ~PAGE_MASK;
meminfo.bank[meminfo.nr_banks].start = PAGE_ALIGN(start);
meminfo.bank[meminfo.nr_banks].size = size & PAGE_MASK;
meminfo.bank[meminfo.nr_banks].node = PHYS_TO_NID(start); 未定义CONFIG_DISCONTIGMEM,PHYS_TO_NID()返回0。
meminfo.nr_banks += 1;
}
经过这样的处理,setup.c文件中的meminfo可就不在是
static struct meminfo meminfo __initdata = { 0, };
而是
static struct meminfo meminfo __initdata = { 1,{0x30000000,0x4000000,0},{}, };
表示当前有一个内存区域,物理地址是从0x30000000开始,大小是64M
回顾下这个meminfo结构。
# define NR_BANKS 8
struct meminfo {
int nr_banks;
struct {
unsigned long start;
unsigned long size;
int node;
} bank[NR_BANKS];
};
再次强调有两个meminfo,这里的是setup.c中的,只供这个文件使用
static struct meminfo meminfo __initdata = { 0, };
在看另外的那个指针
c000e734 :
static int __init parse_tag_cmdline(const struct tag *tag)
{
strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
return 0;
}
char *from = default_command_line;
static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE; (setup.c中)
CONFIG_CMDLINE="noinitrd root=31:2 rw init=/linuxrc console=ttySAC0" (在配置文件中)
char saved_command_line[COMMAND_LINE_SIZE]; (main.c中)
#define COMMAND_LINE_SIZE 1024 (asm/setup.h)可以在这里更改大小。
也就是说命令行最多可以有1023个字符。
default_command_line 原来的内容是我们配置文件中确定的,但是现在,他被tag->u.cmdline.cmdline覆盖了。
可见,从uboot传递过来的命令行参数的优先级要高于配置文件的默认命令行。好了,从现在开始忘记配置文件中的命令行吧。
好了,我们要的结果出来了,返回setup_arch()。
init_mm.start_code = (unsigned long) &_text;
init_mm.end_code = (unsigned long) &_etext;
init_mm.end_data = (unsigned long) &_edata;
init_mm.brk = (unsigned long) &_end; 从这儿之后的内存可以动态的分配了。
填充 init_mm 的成员,这些数值在lds里面。分别是代码段,数据段和bss段。 init_mm 是swapper用的 mm_struct
memcpy(saved_command_line, from, COMMAND_LINE_SIZE);
saved_command_line[COMMAND_LINE_SIZE-1] = '\0';
上面的代码先把uboot传递过来的命令行参数保存起来,以备后用。
parse_cmdline(cmdline_p, from);
这个函数不得不看,后面会用到它初始化的数据。贴过来。
cmdline_p是start_kernel函数传递过来的参数,from指向uboot传递过来的命令行
static char command_line[COMMAND_LINE_SIZE]; (setup.c私有的)
static void __init parse_cmdline(char **cmdline_p, char *from)
{
char c = ' ', *to = command_line;
int len = 0;
for (;;) { 无限扫描
if (c == ' ') {
//寻找c=空格 的条件,空格表示一个新的命令行选项,假设:mem=xxx noinitrd root=yyy init=/linuxrc console=ttySAC0
//所以,这个扫描是一个一个的扫描命令行里的参数的。
extern struct early_params __early_begin, __early_end; 这些变量在lds中
struct early_params *p;
for (p = &__early_begin; p < &__early_end; p++) { 扫描这个区间的所有early_params结构。
int len = strlen(p->arg); 测量这个字符串的长度。比如"mem="长度是4
if (memcmp(from, p->arg, len) == 0) { 这里也pass,这里不pass就意味着不能解析。
if (to != command_line) 防止得到两个空格
to -= 1;
from += len; 跳过这个选项,得到具体数据,现在from指向“xxx noinitrd...“
p->fn(&from); 调用这个函数处理这个xxx
while (*from != ' ' && *from != '\0') 跳过xxx部分,因为这是mem=xxx已经处理完了,可以扔掉了。
from++;
break; 终止这次处理,针对mem=xxx的处理,现在from指向“ noinitrd ...“,注意最前面的空格。
}
}
}
c = *from++; 这次c又得到的是空格。第2次,取到的是noinitrd的n
if (!c) 如果到了uboot命令行参数的结尾,或者 命令行参数太长,都会结束扫描
break;
if (COMMAND_LINE_SIZE <= ++len)
break;
*to++ = c; 保存空格,第2此保存了n,依次类推。
}
*to = '\0';
*cmdline_p = command_line; 给cmdline_p赋值,这个指针是start_kernel里的。
}
struct early_params {
const char *arg; //字符串指针
void (*fn)(char **p); //私有的函数
};
在lds中
__setup_end = .;
__early_begin = .;
*(__early_param) 具有这样属性的段放在了这里。
__early_end = .;
比如:
__early_param("mem=", early_mem);
#define __early_param(name,fn) \
static struct early_params __early_##fn __attribute_used__ \
__attribute__((__section__("__early_param"))) = { name, fn }
经过扩展后:
static struct early_params __early_early_mem __attribute_used__ \ 不了解__attribute_used__
__attribute__((__section__("__early_param"))) = {"mem=", early_mem }
c001d9ec <__early_begin>:
c001d9ec: c0268d20
c001d9f0: c000e45c c000e45c
c001d9f4 <__early_early_initrd>:
c001d9f4: c0268d28 eorgt r8, r6, r8, lsr #26
c001d9f8: c000e384 andgt lr, r0, r4, lsl #7
c001d9fc <__early_early_ecc>:
c001d9fc: c026a008 eorgt sl, r6, r8
c001da00: c000fa18 andgt pc, r0, r8, lsl sl
c001da04 <__early_early_cachepolicy>:
c001da04: c026a010 eorgt sl, r6, r0, lsl r0
c001da08: c000f8dc ldrgtd pc, [r0], -ip
c001da0c <__early_early_nowrite>:
c001da0c: c026a020 eorgt sl, r6, r0, lsr #32
c001da10: c000f9e0 andgt pc, r0, r0, ror #19
c001da14 <__early_early_nocache>:
c001da14: c026a028 eorgt sl, r6, r8, lsr #32
c001da18: c000f9a8 andgt pc, r0, r8, lsr #19
c001da1c <__early_end>:
可以发现能够在这里处理的命令行参数有mem= initrd= ecc= cachepolicy= nowrite nocache这六个。
parse_cmdline做了三件事,首先它解析了from所指向的完整的内核参数中关于内存的部分,其次它将没有解析的部分复制到command_line中,最后它将start_kernel()传进来的内核参数指针指向command_line
内核参数中的“mem=xxxM@xxx”将会被parse_cmdline解析,并根据结果设置meminfo,而其余部分则被复制到command_line中
就是说,能解析的都解析了,不能解析的留下来,等着以后解析,保存在command_line中,可以发现uboot的命令行参数具有最高的优先级,如果
指定mem=xxxM@yyy的话,将覆盖掉标记列表的mem32配置,如果多次使用mem= mem=的话,将得到多个内存bank,通过代码来看是这样,没有验证过。
当然我的uboot传递过来的命令行参数没有6个里面的任何一个,所以没有在这里被处理。
回到setup_arch()
paging_init(&meminfo, mdesc);
这是个庞大的函数,里面有很多好东西......
参考资料:
http://sxbo.blog.sohu.com/
阅读(2478) | 评论(0) | 转发(0) |