Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1969291
  • 博文数量: 610
  • 博客积分: 11499
  • 博客等级: 上将
  • 技术积分: 5511
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-12 19:27
文章分类

全部博文(610)

文章存档

2016年(5)

2015年(18)

2014年(12)

2013年(16)

2012年(297)

2011年(45)

2010年(37)

2009年(79)

2008年(101)

分类: LINUX

2009-11-08 13:18:12


    下面进入Linux kernel部分,分析与bootloader参数传递对应的部分。
 
    移植Linux需要很大的工作量,其中之一就是HAL层的编写。在具体实现上,HAL层以arch目录的形式存在。显然,该层需要与bootloader 有一定的约定,否则就不能很好的支持。其实,这个地方应该思考一个问题,就是说,boot loader可以做到Linux kernel里面,但是这样带来的问题就是可移植性和灵活性都大为降低。而且,bootloader的功能并非操作系统的核心范畴,Linux的核心应该 始终关注操作系统的核心功能上,将其性能达到最优。所以,bootloader分离出来单独设计,是有一定的道理的。bootloader现在除了完成基 本功能外,慢慢地变得“肥胖”了。在高性能bootloader设计中,可能会把调试内核等的一些功能集成进来,这样在内核移植尚未完成阶 段,bootloader可以充当调试器的作用。功能趋于完善,也慢慢趋于复杂。废话不说,进入正题。
 
三、Linux kernel接受参数分析
 
    这部分主要分析如下问题:
 
    ·Linux kernel支持压缩映象和非压缩映象两种方式启动,那么这两种流程和函数入口有何不同?
    ·如何使用非压缩映象?做一下测试。
    ·zImage是如何生成的?其格式如何?
    ·启动之后,Linux kernel如何接收参数?
 
    这里不具体区分每个问题,按照理解和开发的思路来进行。
 
 1、思考:前面做的基本实验中,并没有采用压缩映象。因为程序规模太小,压缩带来的时间开销反而降低了性能。但是对Linux kernel来说,映象还是比较大的,往往采用了压缩。但是,同样有需求希望Linux kernel小一些,不采用压缩方式来提高内核启动的速度,对时间要求比较苛刻。那么,这样就出现了两种情况:压缩映象和非压缩映象。由此带来的问题就在 于:如果是压缩映象,那么必须首先解压缩,然后跳转到解压缩之后的代码处执行;如果是非压缩映象,那么直接执行。Linux必须对这两种机制提供支持,这 里就需要从整体上来看一下生成的映象类型了。
 
    因为vivi的Makefile都是直接来源于Linux,前面对vivi的Makefile已经分析清楚了,这里看Linux的Makefile就容易多了,大同小异,而且还有丰富的文档支持。
 
(1)非压缩映象
 
$make vmlinux
 

[armlinux@lqm linux-2.4.18]$ ls -l vmlinux
-rwxrwxr-x 1 armlinux armlinux 1799697 Sep 11 14:06 vmlinux
[armlinux@lqm linux-2.4.18]$ file vmlinux
vmlinux: ELF 32-bit LSB executable, ARM, version 1 (ARM), statically linked, not stripped

 
    这里生成的是vmlinux,是ELF文件格式。这个文件是不能烧写存储介质的,如果想了解ELF文件格式,需要参考专门的文章。当然,这里,如果想要使 用非压缩映象,可以使用arm-linux-objcopy把上述ELF格式的vmlinux转化为二进制格式的vmlinux.bin,这样就可以直接 烧写了。
 
    于是我做了如下的修改,在Makefile中增加了:
 

vmlinux: include/linux/version.h $(CONFIGURATION) init/main.o init/version.o linuxsubdirs
        $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o \
                --start-group \
                $(CORE_FILES) \
                $(DRIVERS) \
                $(NETWORKS) \
                $(LIBS) \
                --end-group \
                -o vmlinux
        $(NM) vmlinux | grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | sort > System.map
        $(OBJCOPY) -O binary -R .comment -R .stab -R .stabstr -S vmlinux vmlinux.bin

 
    同时在clean file的列表中增加vmlinux.bin。这样就可以生成vmlinux.bin了,前面的基础实验都讲过了。然后烧写vmlinux.bin到nand flash的kernel分区,引导启动,正常,而且不会出现解压缩提示:
 

NOW, Booting Linux......
VIVI has completed the mission of
From now on, Linux kernel takes charge of all

Linux version 2.4.18-rmk7-pxa1 (armlinux@lqm) (gcc version 2.95.3 20010315 (release)) #2 Tue Sep 11 14:06:14 CST 2007

 
    可见,可以通过非压缩映象格式启动。
 
(2)压缩映象
 
    下面看看压缩映象是如何得到的。顶层的Makefile没有压缩映象的生成,显然就在包含的子Makefile中。容易查知在arch/arm/下的Makefile,可见:
 

bzImage zImage zinstall Image bootpImage install: vmlinux
        @$(MAKEBOOT) $@

 
    也就是说,有bzImage、zImage几种。其中arch/boot下有:
 

SYSTEM =$(TOPDIR)/vmlinux

Image: $(CONFIGURE) $(SYSTEM)
        $(OBJCOPY) -O binary -R .note -R .comment -S $(SYSTEM) $@

bzImage: zImage

zImage: $(CONFIGURE) compressed/vmlinux
        $(OBJCOPY) -O binary -R .note -R .comment -S compressed/vmlinux $@
        @echo " ^_^ The kernel image file is:" $(shell /bin/pwd)/$@

 
    这里发现如果采用make Image,则生成的非压缩映象的二进制格式,可以直接烧写,可见前面第一步的工作是浪费了,Linux内核还是很完善的,提供了这种方式,所以,如果想要生成非压缩二进制映象,那么就要使用make Image。
 
    另外,这里提供了两种压缩的映象,其实就是一种,这里能够看到的就是如果采用make zImage或者make bzImage,就要把compressed/vmlinux处理为二进制格式,可以下载使用。下面就看compressed/vmlinux是什么。进 入compressed文件夹,看看Makefile:
 

vmlinux:    $(HEAD) $(OBJS) piggy.o vmlinux.lds
        $(LD) $(ZLDFLAGS) $(HEAD) $(OBJS) piggy.o $(LIBGCC) -o vmlinux

 
    很明显了,这里的vmlinux是由四个部分组成:head.o、head-s3c2410.o、misc.o、piggy.o。关于这几个文件是干什么用的,看看各自的编译规则就非常清晰了:
 
 

$(HEAD): $(HEAD:.o=.S) \
                        $(wildcard $(TOPDIR)/include/config/zboot/rom.h) \
                        $(wildcard $(TOPDIR)/include/config/cpu/32.h) \
                        $(wildcard $(TOPDIR)/include/config/cpu/26.h)
                $(CC) $(AFLAGS) -traditional -c $(HEAD:.o=.S)

piggy.o: $(SYSTEM)
                $(OBJCOPY) -O binary -R .note -R .comment -S $(SYSTEM) piggy
                gzip $(GZFLAGS) < piggy > piggy.gz
                $(LD) -r -o $@ -b binary piggy.gz
                rm -f piggy piggy.gz

font.o: $(FONTC)
                $(CC) $(CFLAGS) -Dstatic= -c -o $@ $(FONTC)

vmlinux.lds: vmlinux.lds.in Makefile $(TOPDIR)/arch/$(ARCH)/boot/Makefile $(TOPDIR)/.config
                @sed "$(SEDFLAGS)" < vmlinux.lds.in > $@

clean:; rm -f vmlinux core piggy* vmlinux.lds

.PHONY: clean

misc.o: misc.c $(TOPDIR)/include/asm/arch/uncompress.h $(TOPDIR)/lib/inflate.c

 
    可见,vmlinux是把顶层生成的非压缩的ELF映象vmlinux进行压缩,同时加入了加压缩代码部分。真正的压缩代码就是lib/inflate.c。可以看看,主要是gunzip,具体的压缩算法就不分析了。
 
    至此,就可以用下图作出总结了:
    bootloader把存储介质中的kernel映象下载到mem_base+0x8000的位置,执行完毕后,跳转到这一位置,执行此处的代码。这一位置的入口可能有两种情况,第 一种是kernel映象为非压缩格式,通过make Image获得,那么真正的入口就是arch/arm/kernel/head_armv.S(ENTRY(stext));第二种是kernel映象为 压缩格式,通过make zImage获得,那么真正的入口就是arch/arm/boot/compressed/head.S(ENTRY(_start))。这个地方并不是kernel判断,也不需要判断。道理很简单,cpu只会按照读入的代码执行,两种情况下执行的代码不同,自然也就有两种不同的过程了。
 
(3)探讨zImage的magic number的位置
 
    可以看出,如果是zImage,那么程序的入口是arch/arm/boot/compressed/head.S。分析程序头部:
 

.align
start:
                .type start,#function

                //重复如下指令8次
                .rept 8
                mov r0, r0
                .endr
                //跳转指令,跳到下面第一个标号1处
                b 1f

                //这就是第10条指令的位置,也就是偏移为4*9个字节
                .word 0x016f2818 @ Magic numbers to help the loader
                .word start @ absolute load/run zImage address
                .word _edata @ zImage end address
1: mov r7, r1 @ save architecture ID
                mov r8, #0 @ save r0

 
    可见前面8条指令均为mov r0, r0,从前面的zImage的16进制格式中可以看出,前面8个字都是相同的,均为00 00 A0 E1,第9条指令就是b 1f,然后就应该是0x016f2818.这样就与前面程序的判断对应上了,也就是说,此处的magic number是固定位置,固定数值的,注释中也写的很清晰,那就是magic numbers to help the loader,也就是说帮助bootloader确定映象的文件格式。但是应该说明的是,在vivi的bootloader设计中,虽然检测zImage 的magic number,但是并没有进行未识别处理。也就是说,假定用ultra-edit32把此位置的0x016f2818破坏掉,其他不变,那么虽然vivi 提示无法识别zImage映象,但是并不影响实际的执行。当然,你也可以有其他的设计思路。不过设计的哲学思想是,要完成一件事情,并不只有一种方式。所 以,bootloader不能限死只是使用zImage格式,需要有一定的灵活性,为了引导内核启动,可以采用不同的方式。
 
(4)完成了前面的理解,下面就要重点看解析参数一部分了。这里不将zImage方式的启动作为重点分析内容,静下心来跟踪代码并不是难事。从 整体的角度理解,如果采用zImage,那么在执行完成解压缩之后,自然会调转到解压之后的kernel的第一条指令处。这时就是真正的启动内核了。所以 我们可以看arch/arm/kernel/head-armv.S,此处做的工作可以参考taoyuetao的分析,完成的功能比较简单。这里就感兴趣 的参数问题分析,需要注意的是,
 

/*
 * Kernel startup entry point.
 *
 * The rules are:
 * r0 - should be 0
 * r1 - unique architecture number
 * MMU - off
 * I-cache - on or off
 * D-cache - off
 *
 * See linux/arch/arm/tools/mach-types for the complete list of numbers
 * for r1.
 */

 
   可见R0是0,R1是mach type,这些都是必须要设定的。在这里,并没有限定R2必须为参数的起始地址。kernel本身并没有使用R0-R2,如果设定了R2,在这里也不会修 改其值。后面的工作也没有设计接收参数,最后直接跳到start_kernel(【init/main.c】)
 

asmlinkage void __init start_kernel(void)
{
    char * command_line;
    unsigned long mempages;
    extern char saved_command_line[];
/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */

    lock_kernel();
    printk(linux_banner);
    setup_arch(&command_line);
    printk("Kernel command line: %s\n", saved_command_line);
    parse_options(command_line);

 
    从开头分析,首先是lock_kernel,这里是SMP相关,我的是单CPU,所以实际上该函数为空。然后打印版本信息,在vivi中已经分析过这个机 制了,两者相同。下面的setup_arch就是分析的重点了,它要获取命令行启动参数,然后打印获得的命令行参数,然后进行语法解析选项。我们关注的重 点就在setup_arch上了。参数设置都在【arch/arm/kernel/setup.c】,这个函数也不例外,进入setup.c。
 

void __init setup_arch(char **cmdline_p)
{
    struct tag *tags = NULL;
    struct machine_desc *mdesc;
    char *from = default_command_line;

    ROOT_DEV = MKDEV(0, 255);

    setup_processor();
    mdesc = setup_machine(machine_arch_type);
    machine_name = mdesc->name;

    if (mdesc->soft_reboot)
        reboot_setup("s");

    if (mdesc->param_offset)
        tags = phys_to_virt(mdesc->param_offset);

    
/*
     * Do the machine-specific fixups before we parse the
     * parameters or tags.
     */

    if (mdesc->fixup)
        mdesc->fixup(mdesc, (struct param_struct *)tags,
             &from, &meminfo);

    
/*
     * If we have the old style parameters, convert them to
     * a tag list before.
     */

    if (tags && tags->hdr.tag != ATAG_CORE)
        convert_to_tag_list((struct param_struct *)tags,
                 meminfo.nr_banks == 0);

    if (tags && tags->hdr.tag == ATAG_CORE)
        parse_tags(tags);

    if (meminfo.nr_banks == 0) {
        meminfo.nr_banks = 1;
        meminfo.bank[0].start = PHYS_OFFSET;
        meminfo.bank[0].size = MEM_SIZE;
    }

    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;

    memcpy(saved_command_line, from, COMMAND_LINE_SIZE);
    saved_command_line[COMMAND_LINE_SIZE-1] = '\0';
    parse_cmdline(&meminfo, cmdline_p, from);
    bootmem_init(&meminfo);
    paging_init(&meminfo, mdesc);
    request_standard_resources(&meminfo, mdesc);

    
/*
     * Set up various architecture-specific pointers
     */

    init_arch_irq = mdesc->init_irq;

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
    conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
    conswitchp = &dummy_con;
#endif
#endif
}

 
    这里面涉及到3个比较复杂的结构体,包括param_struct、tag、machine_desc。第一步的操作是关于根设备号,暂时不探讨;第二步 工作setup_processor,是设置处理器,这是多处理器相关部分,暂时不探讨;第三步工作是setup_machine,这里就需要了解了。
 
   首先,machine_arch_type没有定义,仅仅在头部有定义,这是全局变量,两者之间一定存在联系:
 

unsigned int __machine_arch_type;

 
   看看头文件,应该有#include ,但是未编译时并没有,可以确定是编译前完成的。这里只有看Makefile了。因为setup.c在 这里,首先看同层的Makefile。这一层没有关于mach-types.h的信息,然后到上一层Makefile,发现了:
 

MRPROPER_FILES += \
        arch/arm/tools/constants.h* \
        include/asm-arm/arch \
        include/asm-arm/proc \
        include/asm-arm/constants.h* \
        include/asm-arm/mach-types.h

# We use MRPROPER_FILES and CLEAN_FILES now
archmrproper:
        @/bin/true

archclean:
        @$(MAKEBOOT) clean

archdep: scripts/mkdep archsymlinks
        @$(MAKETOOLS) dep
        @$(MAKEBOOT) dep

 
   说现在使用MRPROPER_FILES,但是下面没有出现,故而应该看几个宏的定义:
 

MAKEBOOT = $(MAKE) -C arch/$(ARCH)/boot
MAKETOOLS = $(MAKE) -C arch/$(ARCH)/tools

 
    由此知道,对应的子文件夹包括boot和tools,boot是与启动相关,不太可能;而前面也看到,tools下有mach-types,所以判断在tools下面,看看tools/Makefile:
 

all: $(TOPDIR)/include/asm-arm/mach-types.h \
        $(TOPDIR)/include/asm-arm/constants.h

$(TOPDIR)/include/asm-arm/mach-types.h: mach-types gen-mach-types
        awk -f gen-mach-types mach-types > $@

 
    由此判断出,mach-types.h是如何生成的,主要是利用awk脚本处理生成。生成之后与s3c2410有关的部分为:
 

#ifdef CONFIG_S3C2410_SMDK
# ifdef machine_arch_type
# undef machine_arch_type
# define machine_arch_type __machine_arch_type
# else
# define machine_arch_type MACH_TYPE_SMDK2410
# endif
# define machine_is_smdk2410() (machine_arch_type == MACH_TYPE_SMDK2410)
#else
# define machine_is_smdk2410() (0)
#endif

 
    由此就知道了,这里的machine_arch_type为193,所以此函数实际上执行:mdesc = setup_machine(193);它要填充结构体machine_desc,如下:
 

struct machine_desc {
    
/*
     * Note! The first four elements are used
     * by assembler code in head-armv.S
     */

    unsigned int        nr;        /* architecture number    */
    unsigned int        phys_ram;    /* start of physical ram */
    unsigned int        phys_io;    /* start of physical io    */
    unsigned int        io_pg_offst;    
/* byte offset for io
                         * page tabe entry    */


    const char        *name;        /* architecture name    */
    unsigned int        param_offset;    /* parameter page    */

    unsigned int        video_start;    /* start of video RAM    */
    unsigned int        video_end;    /* end of video RAM    */

    unsigned int        reserve_lp0 :1;    /* never has lp0    */
    unsigned int        reserve_lp1 :1;    /* never has lp1    */
    unsigned int        reserve_lp2 :1;    /* never has lp2    */
    unsigned int        soft_reboot :1;    /* soft reboot        */
    void            (*fixup)(struct machine_desc *,
                     struct param_struct *, char **,
                     struct meminfo *);
    void            (*map_io)(void);/* IO mapping function    */
    void            (*init_irq)(void);
};

 
    另外,还提供了一系统的宏,用于填充该结构体:
 

/*
 * 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 MAINTAINER(n)

#define BOOT_MEM(_pram,_pio,_vio) \
        phys_ram: _pram, \
        phys_io: _pio, \
        io_pg_offst: ((_vio)>>18)&0xfffc,

#define BOOT_PARAMS(_params) \
        param_offset: _params,

#define VIDEO(_start,_end) \
        video_start: _start, \
        video_end: _end,

#define DISABLE_PARPORT(_n) \
        reserve_lp##_n: 1,

#define BROKEN_HLT /* unused */

#define SOFT_REBOOT \
        soft_reboot: 1,

#define FIXUP(_func) \
        fixup: _func,

#define MAPIO(_func) \
        map_io: _func,

#define INITIRQ(_func) \
        init_irq: _func,

#define MACHINE_END \
};

 
    EDUKIT填充了一个结构体,用如下的方式:
 

MACHINE_START(SMDK2410, "Embest EduKit III (S3C2410x)")
    BOOT_MEM(0x30000000, 0x48000000, 0xe8000000)
    BOOT_PARAMS(0x30000100)
    FIXUP(fixup_smdk)
    MAPIO(smdk_map_io)
    INITIRQ(s3c2410_init_irq)
MACHINE_END

 
    看到有特殊的设置部分,那就是开始为之分配了一个段,段的名字是.arch.info,也就是说把这部分信息单独作为一个段来进行处理。下面把这个宏展开如下:
 

const struct machine_desc __mach_desc_smdk2410 = {
    nr: 193,
    name: "EDUKIT-III (s3c2410)",
    phys_ram: 0x30000000,
    phys_to: 0x48000000,
    io_pg_offset: 0x3a00,
    param_offset: 0x30000100,
    fixup: fixup_smdk,//实际上为空
    map_io: smdk_map_io,
    init_irq: s3c2410_init_irq,
};

 
    可见,基本的信息已经具备了,而且从这里,我们也可以看出,启动参数地址由这个段就可以完成,不需要传递了。当然,必须保证bootloader的值,与此处的相同。这样,也就说明如果不使用R2传递参数的起始地址,那么这个地方就需要把这个结构体设置好。
 
    下面看看这个函数完成什么功能:
 

static struct machine_desc * __init setup_machine(unsigned int nr)
{
    extern struct machine_desc __arch_info_begin, __arch_info_end;
    struct machine_desc *list;

    
/*
     * locate architecture in the list of supported architectures.
     */

    for (list = &__arch_info_begin; list < &__arch_info_end; list++)
        if (list->nr == nr)
            break;

    
/*
     * If the architecture type is not recognised, then we
     * can co nothing...
     */

    if (list >= &__arch_info_end) {
        printk("Architecture configuration botched (nr %d), unable "
         "to continue.\n", nr);
        while (1);
    }

    printk("Machine: %s\n", list->name);

    return list;
}

 
    这个地方就是要把上面这一系列的信息连贯起来,那么就不难理解了。上述的宏已经完成了.arch.info段,这个段实际上在内存中就是一个 machine_desc形式组织的信息(对Linux内核来说,并不一定仅仅有一个结构块),上述函数的两个变量__arch_info_begin和 __arch_info_end很明显是有链接脚本传递进来。于是查看近层的链接脚本(【arch/arm/vmlinux-armv.lds.in】, 可以发现:
 

.init : { /* Init code and data */
                _stext = .;
                __init_begin = .;
                        *(.text.init)
                __proc_info_begin = .;
                        *(.proc.info)
                __proc_info_end = .;
                __arch_info_begin = .;
                        *(.arch.info)
                __arch_info_end = .;

                __tagtable_begin = .;
                        *(.taglist)
                __tagtable_end = .;

 
    所以上述的功能就很简单了,就是查看是否有mach-type为193的结构存在,如果存在就打印出name,这也就是开机启动后,出现Machine: Embest EduKit III (S3C2410)的原因了。
 
    接下来关注:
 

    if (mdesc->param_offset)
        tags = phys_to_virt(mdesc->param_offset);

 
   很明显,这里的mdesc->param_offset并不为0,而是0x30000100,所以要做一步变换,就是物理地址映射成虚拟地址。把这 个地址附给tags指针。然后就是判断是param_struct类型还是tags类型,如果是param_struct类型,那么首先转换成tags类 型,然后对tags类型进行解析。
 

    if (tags && tags->hdr.tag != ATAG_CORE)
        convert_to_tag_list((struct param_struct *)tags,
                 meminfo.nr_banks == 0);

    if (tags && tags->hdr.tag == ATAG_CORE)
        parse_tags(tags);

 
    要注意parse_tags函数是非常重要的,它有隐含的功能,不太容易分析。跟踪上去,主要看这个函数:
 

/*
 * 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是如何来的。首先看【include/asm-arm/setup.h】,看看宏的定义,也就是带有__tag,就归属为.taglist段。
 

#define __tag __attribute__((unused, __section__(".taglist")))
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn }

 
    利用__tag有构造了一个复杂的宏__tagtable,实际上就是定义了tagtable列表。现在看setup.c中的宏形式示例:
 

__tagtable(ATAG_CMDLINE, parse_tag_cmdline);

 
    展开之后为:
 

static struct tagtable __tagtable_ATAG_CMDLINE __tag = {
    ATAG_CMDLINE,
    parse_tag_cmdline
};

 
    于是,段.taglist就是这样一系列的结构体。那么上述的函数实际上就是把传递进来的tag与此表比较,如果tag标记相同,证明设置了此部分功能,就执行相应的解析函数。以ATAG_CMDLINE为例,就要执行:
 

static int __init parse_tag_cmdline(const struct tag *tag)
{
#ifndef CONFIG_NO_TAG_CMDLINE
    strncpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
#endif
    default_command_line[COMMAND_LINE_SIZE - 1] = '\0';
    return 0;
}

 
    这样也就是实现了把tag中的命令行参数复制到了default_command_line中。
 
    在返回来到函数【arch/arm/kernel/setup.c】,看函数setup_arch,定义中有:
 

char *from = default_command_line;

 
    说明from指向数组default_command_line。于是知道,当你完成tag解析的时候,所有传递过来的参数实际上已经复制到了相应的部 分,比如命令行设置复制到了default_command_line。其他类似,看相应的解析行为函数就可以了。因为现在vivi只是传递了命令行,所 以只是分析清楚这个。后面执行:
 

memcpy(saved_command_line, from, COMMAND_LINE_SIZE);

 
    这就比较容易理解了,就是将传递进来的命令行参数复制到saved_command_line,后面还可以打印出此信息。再往后的工作已经与此情景关系不大,所以不再进行详细分析。
 
    至此,vivi与Linux kernel的参数传递情景分析就完成了。

回目录 vivi开发笔记【专辑】

发表于: 2007-09-12,修改于: 2007-12-02 09:12,已浏览2211次,有评论0条 推荐 投诉





网友评论

网友: piaoxiang 时间:2007-07-28 08:14:44 IP地址:122.4.41.★



2007-07-28

    通过后面的vivi学习才发现,为什么会需要LINUX_INCLUDE_DIR这个宏来包含内核的头文件。很简单,vivi就是Linux kernel的小“儿女”,里面有些C文件的使用仍然用了“<>”包含的方式。而mizi公司没有经过仔细的验证分析,认为加上这个宏就不会出现编译问题。于是,为什么网上有的人把该宏定义为编译器的include目录,有些人把它定义为kernel的include目录,编译都没有问题,就不难理解了。如果你把该宏去掉编译,仍然没有问题(也许我测试的不到位)。但是我考虑的是,vivi是bootloader,应该具有引导不同版本内核的能力,所以还是要通过测试把该宏相关的内容去除比较合适。我在后续工作中已经去掉了该宏,暂时还没有发现编译问题。

    另外,增加的蜂鸣器关闭的汇编程序也不太合适。关于它的处理应该是由[init/main.c]------[arch/s3c2410/smdk.c] board_init()-----set_gpios()来完成的。也就是说vGPBCON的值不合适。应该修改[include/platform/smdk2410.h]默认的#define vGPBCON            0x00044555。这需要查看Datasheet,了解一下GPBCON各个位的含义,然后根据自己的开发板进行修改。其他的初始状态不合适,也要考虑修改此头文件中的初始值。

    需要注意的一个小地方是,在vivi的头文件中,如果寄存器前加字母v,表示value,即取值。如果加个o,则表示offset,即偏移值。这样读程序就很容易理解了。

    vivi也就逐步清晰起来了。




网友: spring11 时间:2007-08-31 23:40:06 IP地址:59.78.25.★



写得太好了,这是我看到的最详细的一篇!!

好想转到我的百度博客上,HOHO,不过这么好的文章还是每次都到里来吧:)

Blog作者的回复:
谢谢





网友: 本站网友 时间:2007-11-11 01:28:09 IP地址:210.72.218.★



佩服Super的学习态度和深度,也让我看到自己的很多不足,如果能和你成为同事,真是件辛事:)希望在以后的日子能和你多多交流,重要的是向你学习,我会给你发邮件的:)请多关照哦

Blog作者的回复:
^_^





网友: bob_zhang2004 时间:2007-11-12 23:28:48 IP地址:211.103.74.★



太好了, 我们也正在讨论中,正在拜读你的文章, 



也 欢迎到我们的帖子,大家一起再讨论一下:



Blog作者的回复:
共同讨论。





网友: bob_zhang2004 时间:2007-11-13 10:52:18 IP地址:58.211.114.★



>> 3、主时钟源来自外部晶振或者外部时钟。复位后,MPLL虽然默认启动,但是如果不向MPLLCON中写入value,那么外部晶振直接作为系统时钟。

>> EDUKIT-III的外部晶振有两个,一是用于系统时钟,为12MHz;一个用于RTC,为32.768KHz。

>> 以前实验没有向MPLLCON写入数值,所以系统时钟都是12MHz。



>> 从这里也可以发现一个问题,如果外部晶振开始没有焊上,那么系统是无法正常启动的。因为按照上述规则,

>> 复位后还没有写入MPLLCON,这时又没有可以使用的时钟源,所以不会启动。也就是硬件完成后,这个12MHz的晶振是一定要焊上的,

>> 才能进行后续的硬件测试工作。



您这里所说的外部时钟 , 是指什么呢? 能否举个现实中的例子。 



1》不是一般情况下, 有外部晶振就够了呢? 

2》  >> 但是如果不向MPLLCON中写入value,那么外部晶振直接作为系统时钟

我的疑问是: 那我要是往 MPLLCON写入value 呢? 那谁会作为系统时钟呢?      这个时候 12MHz还有意义吗?

Blog作者的回复:
我想,你对硬件可能不是很熟悉。
1、首先,硬件系统需要一个时钟信号。我们常用石英晶振作为这个时钟信号源。你可以看看你的板子,应该有个银白色帽子的东西,那就是晶振。再深入了解,你需要看看晶振的物理原理,了解为什么能够产生时钟信号了。
2、 为了产生高频,在硬件上一般采用锁相环电路,这也是高频电路的知识。简单的说,12MHz如果作为一级时钟源,只能产生12MHz的主频,要想提到 200MHz,那就需要倍频。就是相当于12×n=200.锁相环电路完成这个n的作用,这就是MPLL的作用。等到得到了200MHz的时钟源,那么现 在的系统时钟就是1/(200MHz)了。
3、因为硬件设计的特点,具体电路就比较复杂了。简单说,要想使MPLL真正起作用,就需要一个触发事 件,而这个事件就是要往MPLLCON中写入value,确定分频比,这样电路才能接通。(主要是硬件设计部分,电路的细节我也不是太清晰,但是这种设计 在51单片机等上面是很广泛的。)也就是说,你没有理解硬件上的设计,12MHz是最初的时钟源啊,即使经过了倍频,那200MHz是二级的时钟源,这个 时钟体系也是一个树状的体系结构,简称时钟树。如果把12MHz去掉,那么200MHz的源是不可能得到的。
4、刚才说的是12MHz的晶振,另外还有一个32.768KHz的晶振,他们都是晶振,只不过频率不同,用途也不同。32.768KHz用于RTC,仅仅用作这个目的。它的这部分电路和12MHz的主频电路是不同的。
5、这里的术语可能不太严谨,所以产生了问题。外部晶振对应的是频率f,f=12MHz的话,那么对应的时钟就是1/f。二者在说法上是等同的。

简 单总结一下,外部晶振和外部时钟实际是一个东西,说法不太严谨就是了,都是提供时钟树的硬件组件。关于高频的时钟设计上,要用分级的观点来考虑。一级时钟 源一般是物理的晶振,直接产生,二级时钟源则是通过锁相环电路转换产生。很显然,少了一级物理晶振,也就不可能产生二级高频时钟了。一般是二级设计,如果 是多级,就会出现时钟同步等等比较复杂的问题了。





网友: bob_zhang2004 时间:2007-11-14 07:23:34 IP地址:211.103.74.★



非常感谢你的回答, 我对硬件懂得不多, datasheet还能看懂 , 现在就是想再深入了解一下硬件原理, 这样对kernel和driver很有好处。 



听了你的阐述, 我明白了。我最后的理解是这样的: Mpll只是一个倍频输出电路, 通过它 可以 12MHz的一级时钟输入倍频输出 ,产生二级时钟,供其他的外设来使用而已。 

而 MPLLCON 寄存器的作用仅仅是提供倍频参数, 用户必须写入(在我们的环境中是 u-boot初始化的时候,写入 0x5c040) ,这样MPLL 倍频电路才能生效, 产生 200MHz的输出。 



如果用户不写入 MPLLCON 寄存器(即使有默认值),MPLL根本就不生效, 也就没有二级时钟了, 相当于 所有的外设都得 使用 12MHz的频率了。 



所以这样看起来 , 12MHz的外部晶振必须焊接上, 按您所说 ,如果没焊上 , 板子连唯一的一级时钟都没有了, 自然无法启动了。 



++++++++++++++++++++++++++++++++++++++++++++++++

Blog作者的回复:
对,就是这个意思。





网友: bob_zhang2004 时间:2007-11-14 07:31:17 IP地址:211.103.74.★



对于 PLL VALUE SELECTION TABLE 表我还是有点疑惑:望指教:

图请看这里 http://blogimg.chinaunix.net/blog/upfile2/071112215834.jpg



1>  MDIV和 HDIV ,SDIV 默认值分别是 : 0x5c , 0x4 , 0x00 

但是为什么表里没有0x5c 呢? 倒是有0x52 , 

PDIV 和 SDIV , 也没有对应的 0x04 和 0x0 啊? 





Blog作者的回复:
看看公式:Mpll(Fclk)=(m×Fin)/(p×(2^s))【m=MDIV+8, p=PDIV+2,s=SDIV】
这 里面有两个需要注意。一个是Fin,就是说这是一级时钟源的频率,F代表Frequency,in代表input。Mpll实际上是Main锁相环电路的 本地输出频率,实际上就是系统的Fclk。现在已知Fin为12MHz,要想获得Mpll,取决于三个参数。但是这三个参数可以是任意的,并没有死规定, 也就是我前面说的,这是一个多解方程式。
手册上也说过,表中的数据是可靠的,应用没有问题。如果我想要得到200MHz,但是表中并没有这个输出 频率,而官方提供的vivi中使用了这个200MHz,说明这个数据也是可靠的。所以在s3c2410中就普遍的使用可以生成200MHz的这三个参数 了。可以说,这三个参数是源于vivi的。之所以选择200MHz,也是为了精确定时。比如sdram的刷新频率的选择,等等。
总 之,datasheet给定了公式和table。table只是官方经过测试比较稳定的一些参数组合,并非全部。还可以有任意多的组合方式,但是呢,官方 不保证你自己选择的参数在硬件上稳定可靠。而vivi也是官方的,它提供了datasheet上没有的200MHz输出的一组参数,所以在200MHz这 个时钟频率上就普遍的采用了0x5c 0x4 0x00。因为选择200MHz在很多与时间相关的设置上的方便性,就使得200MHz成为2410上最为 普遍的选择了。





网友: bob_zhang2004 时间:2007-11-14 10:29:22 IP地址:58.211.114.★



明白了, 原来是没有写出来啊, 怪不得。 



这回明白了 Fin 是什么意思, 之前只是知道肯定是 12MHz , 但就是不知道什么意思, 呵呵。 



多谢了。 





网友: 时间:2007-11-22 13:07:37 IP地址:202.199.138.★



科研的精神和乐于助人的精神都值得学习,非常感谢!




网友: scanmiss 时间:2007-11-22 16:35:17 IP地址:218.5.3.★



hi, 这两个礼拜几乎每天都来,有关vivi的理解得真的不错,我现在也在写一个s3c2410的bootloader, 但是出现问题了,我用的是nor flash, sdram 64M,地址为0x30000000, 从0x30000000-0x3020000用做显示缓冲及其他功能, kernel (2.6.22)放在0x3020000+0x8000的地方, kernel参数放在0x3020000+0x100的地方,跳转到0x3020000+0x8000后就一点消息都没有,不知道是怎么回事?窜口连一点信息都不给,(提示信息到跳转到内核之前,之后的就没有了),是不是内核只能放在0x30000000,或者在什么地方有设这个值的?

Blog作者的回复:
内核默认是解压到mem_base+0x8000处。而这个mem_base没有通过bootloader的参数导入(参数只会导入mem的大小,默认情况下,mem_base是固定的)。所以,如果一定要在0x30200000,就需要修改内核的代码了。





网友: scanmiss 时间:2007-11-22 17:46:35 IP地址:218.5.3.★



谢谢你的回复,

刚才我把kernel放在0x3000000的地方,但窜口也还是一点信息都没有,

我用make zImage做的内核zImage下载到nor flash, 在启动内核之前,我把mem_base+0x8000(0x30000000+0x8000)的数据打印出来如下:

e1a00000                e1a00000                e1a00000                e1a00000

e1a00000                e1a00000                e1a00000                e1a00000

ea000002                016f2818                00000000                0016b184

e1a07001                e1a08002                e10f2000                e3120003

1a000001                e3a00017                ef123456                e10f2000

e38220c0                e121f002                00000000                00000000

e28f00d0

和zImage的内容相同,窜口我用的参数传入(这用的是tag 的方式"console=ttySAC0,115200n8", 用param_struct时用的是" "noinitrd  console=ttySAC0"") 两者都不行,我用的是ADS 1.2 开发的,由于对汇编不熟,基本上是用vivi的汇编代码.

启动的代码如下:____________________________________________________

int boot_kernel(ulong from, size_t size, int media_type)

{

        int ret;

        int i;

        ulong boot_mem_base;    /* base address of bootable memory */

        ulong to;

        ulong mach_type;

        

        void (*call_linux)(int zero, int arch, unsigned long params_addr) = 

          (void (*)(int, int, unsigned long))(LINUX_KERNEL_BASE + LINUX_KERNEL_OFFSET);



        boot_mem_base = LINUX_KERNEL_BASE;



    /* copy kerne image */

        to = boot_mem_base + LINUX_KERNEL_OFFSET;



        DBGMSG(DEBUG, ("Copy linux kernel from 0x%08lx to 0x%08lx, size = 0x%08lx ... ",

                from, to, size));



        ret = copy_kernel_img(to, (char *)from, size, media_type);



        if (ret) 

        {

                DBGMSG(DEBUG, ("failed\n"));

                return -1;

        } 

    else 

        {

                DBGMSG(DEBUG, ("done\n"));

        }



        if (*(ulong *)(to + 9*4) != LINUX_ZIMAGE_MAGIC) 

        {

                DBGMSG(DEBUG, ("Warning: this binary is not compressed linux kernel image\n"));

                DBGMSG(DEBUG, ("zImage magic = 0x%08lx\n", *(ulong *)(to + 9*4)));

        } 

        else 

        {

                DBGMSG(DEBUG, ("zImage magic = 0x%08lx\n", *(ulong *)(to + 9*4)));

        }



         /* Setup linux parameters and linux command line */

        setup_linux_param(boot_mem_base + LINUX_PARAM_OFFSET);



        /* Get machine type, see arch/arm/tools/mach-types */

        mach_type = MACH_TYPE;



        DBGMSG(DEBUG, ("MACH_TYPE = %d\n", mach_type));



        /* Go Go Go */

        DBGMSG(DEBUG, ("NOW, Booting Linux......\n"));

        clean_before_call_linux();

        

        DBGMSG(DEBUG, ("Maybe we can check some date!addr:0x%08lx \n", to));

        

        for(i = 0; i < 100; )

        {

            DBGMSG(DEBUG, ("%08lx        ", *(ulong *)(to + i)));

            i = i + 4;    

            if(i%16 == 0)

            {

                 DBGMSG(DEBUG, ("\n"));

            }

        }

        

        call_linux(0, mach_type, (unsigned long)(boot_mem_base + LINUX_PARAM_OFFSET));        



        DBGMSG(DEBUG, ("If here, error!\n"));

        

        return 0;   

}

______________________________________________________



Blog作者的回复:
命令行传递参数加上mem=64M。我在使用uboot的时候发现,如果不加入mem,则2.6.22的内核引导后,串口没有任何输出。





网友: scanmiss 时间:2007-11-22 18:03:12 IP地址:218.5.3.★



晕倒,我的机器上有4个窜口,我用的窜口是"console=ttySAC1,115200n8"

刚才才恍然大悟,我没有用mem=64M

Blog作者的回复:
只用115200就可以了。默认就是8n1的模式。





网友: scanmiss 时间:2007-11-22 18:52:35 IP地址:218.5.3.★



我把地址改成0x30200000后,果然内核就起不来了,

但是程序是跳转到0x30200000执行啊,机器码一个一个读进来执行,放在什么地方没什么关系吧?还是内核找不到参数表,所以就起不来了?

Blog作者的回复:
看看vivi开发笔记17,再读读lufuchong的bootm解析。
zImage bin文件分为三个部分,实现自解压。搬移地址是非常重要的,不能发生冲突。
这个地方与内核是相关的,并不是boot loader单方面的事情。





网友: tianhao_1983 时间:2007-12-18 11:19:17 IP地址:221.11.46.★



这里交流的好热闹呀,看到这么多的人有如此互相学习互相帮助的积极性,真是高兴,希望向calmarrow请教学习!!




网友: 不死书生 时间:2008-01-01 21:17:19 IP地址:60.191.189.★



中国人如果多一点像你这样的人,那是中国的幸事,少一点浮华,多一点实在,中国的未来就要靠你们这样的人来支撑着。

Blog作者的回复:
过誉了,只是安心做自己的事情





网友: 低调 时间:2008-04-19 17:16:09 IP地址:60.22.211.★



哥们,问一下。vivi的makefile中的



init/ version.o: init/ version.c include/compile.h

    $(CC) $(CFLAGS) -DUTS_MACHINE='"$(ARCH)"' -c -o init/ version.o init/ version.c



其中的 -DUTS_MACHINE  是什么意思啊?   是给$(CC) 的参数吗?   



是arm体系的意识吧?

可是$(CC) 已经定义成arm-linux-gcc了  还要给它传递体系吗?   

为什么编译hello.c等简单的程序时,不用这个-DUTS_MACHINE  呢?

多谢!

Blog作者的回复:
man gcc ==> -D name=define





网友: 低调 时间:2008-04-20 11:19:33 IP地址:60.22.214.★





ifdef CONFIGURATION

..$(CONFIGURATION):

    @echo

    @echo "You have a bad or nonexistent" .$(CONFIGURATION) ": running 'make" $(CONFIGURATION)"'"

    @echo

    $(MAKE) $(CONFIGURATION)

    @echo

    @echo "Successful. Try re-making (ignore the error that follows)"

    @echo

    exit 1



dummy:



else



dummy:



endif



这是vivi的makefile中的代码

请问..$(CONFIGURATION)中的那个".."是什么意思啊?谢谢

Blog作者的回复:
可以把该定义规则删除,没有用处





网友: 本站网友 时间:2008-04-22 16:14:55 IP地址:218.104.217.★



vivi 请教您一个关于重定位的问题,

例如现在我有三个数据段运行顺序为

.data

.bss

.text

 

其中.text为重定位段,被定位到.data后面。

实际的内存存放位置为

.data

.text

.bss

 

我们在执行链接脚本计算.bss段起始地址大小时需要知道它前面一段的大小就是.text段的大小,但这个时候我们还不能使用SIZEOF(.text)来计算。链接的时候就会报错,请问您有没有什么好办法解决这个问题。就是在执行脚本语言的时刻可不可以在没有定义.text段时就在前面引用它, 谢谢了 。

 





网友: 本站网友 时间:2008-04-22 16:19:08 IP地址:218.104.217.★



CalmArrow其实我问的问题主要就是LD在执行脚本语言时只进行一次链接可不可以先计算出每个数据段的大小,然后在根据这些大小以及我们配置的运行与加载顺序生成一个二进制文件??? 谢谢了 呵呵 

Blog作者的回复:
我认为这个问题的解决方案是按照内存的顺序编写好链接规则,这样可以通过定义的段的起始地址和终止地址得到段的位置和大小,然后增加一个搬移代码,完成搬移就可以了吧。
AT91RM9200提供的boot就是这种思路。
读一下http://blog.chinaunix.net/u/21948/showart_481779.html,会有帮助。
希望能够解决你的问题。





网友: 低调 时间:2008-04-25 23:38:13 IP地址:60.22.202.★



首先,感谢CalmArrow前面两个问题的回复。

这两天被vivi的分区弄得晕头转向,在网上找了些文章还是有点晕。

能帮我们讲讲vivi分区,part show看到的分区,bon分区,mtd分区到底都是 干什么的吗?为什么 还要通过bon命令重新设置分区呢?  不能 在vivi源代码中改写吗?   多谢了:)

Blog作者的回复:
采用bon分区主要是为了把分区表信息传递给内核,这样内核就不需要再次配置分区表。通信是双方的,所以内核必须有对bon分区的支持,具体的传递方式则是约定为最后一个分区的最后16KB区域作为共享区域来实现的。具体比较复杂,自己读下源代码吧。





网友: 低调 时间:2008-04-26 11:34:36 IP地址:60.23.123.★



请问斑竹:

vivi的bon分区是干什么的?

为什么我无论怎么使用bon 命令“胡乱”分区,内核也能照样运行,而且yaffs文件系统也能照样运行呢?

多谢!

Blog作者的回复:
你的内核应该采用了MTD分区,而不是bon分区。





网友: 低调 时间:2008-05-03 09:56:39 IP地址:60.22.213.★



感谢!




网友: 低调 时间:2008-05-03 13:49:40 IP地址:60.22.213.★



vivi都向内核传递了什么?

我先把我理解的都列出来,缺的和错的,请 CalmArrow和诸位兄弟帮补充一下,谢谢。

1,传递了param_struct。

    (1)page_size。为什么要传递这个呢?是因为vivi中设置的那个页表映射吗?这个页表映射kernel也要使用吗?kernel中不也会重新设置页表吗?

    (2)nr_pages。  

     (3)commandline 。内核启动的时候,使用哪个串口打印信息是由commandline中的“console=?”决定的吗?

2,传递了machine type通过设置r1.

3,设置了r1=0.为什么呀?

4,为什么没有传递media_type?内核怎么知道文件系统是在nand中还是在其他地方?

5,内核和vivi都有哪些约定?

    (1)内核和vivi的MTD分区要一致。

    (2)zImage一定要放在mem_base+0x8000处吗?

    (3)param_struct也要放在那个固定位置吗?



哈哈,我扔一块砖头  ,等着CalmArrow和诸位兄弟的玉哦:)



  

   




网友: joshuazzh 时间:2008-06-27 10:18:01 IP地址:222.35.60.★



看到您的博客如此有条理、思路清晰,自己真觉得自惭形秽!

您给我做了好榜样

见贤思齐,见不贤而内自醒!

谢谢你

我研究生入学马上一年了,这学期开始刚接触linux,到现在做过的工作有在恒颐H2410F的开发板上移植vivi、移植2.6.14.1内核、制作cramfs和yaffs文件系统、编写简单的脚本文件、在板子上进行串口网口socket编程,实现UATR1接收到的数据通过网口发送至internet远端主机等一些工作

做完之后没有好好整理思路笔记,现在自己都有很多记不清了

我是伴随您的博客一起学习linux的,

从入门到现在经常光顾您的博客,

第一次给您留言,

以后还有很多问题向您请教!





网友: 本站网友 时间:2008-08-06 16:54:59 IP地址:218.104.217.★



CalmArrow您好

请教您个问题,我们的开发环境原来GCC的版本用的是3.4.4现在换成用2.95.2在编译同一个工程的时候他在链接的时候报错:

错误消息如下:E:/host/acoreos2x/arch/x86/bin/dcore-ld: final link failed: Bad value

make: *** [E/target/workspace/os/i386_le_soft_debug/make/os.elf] Error 1

make: Target `all' not remade because of errors.



请问是不是2.9与3.4所执行的ld脚本有区别?谢谢




网友: clf1985 时间:2008-08-15 12:54:19 IP地址:58.39.1.★



从今天开始,学习你的贴子。写的很好,思路很清晰。




网友: NEWCOMER 时间:2008-08-22 13:47:54 IP地址:219.128.48.★



BON分区是老式的,基本没用;MTD分区是存放系统文件的。

现在分区的时候采用bon, 安装系统时实际是yaffs, bon算是辅助性的。你可以看vivi源代码。vivi.tgz

以上也是网上找来的.但是加进去你的文章可以解释BON 和MTD的问题


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