Chinaunix首页 | 论坛 | 博客
  • 博客访问: 402823
  • 博文数量: 62
  • 博客积分: 1483
  • 博客等级: 上尉
  • 技术积分: 779
  • 用 户 组: 普通用户
  • 注册时间: 2009-02-24 12:25
文章分类

全部博文(62)

文章存档

2012年(2)

2011年(6)

2010年(6)

2009年(48)

我的朋友

分类: LINUX

2009-10-06 17:29:39

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/
阅读(2431) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~