• 最近,有一个任务,需要用到linux netfilter。时间很紧,只有1个星期搞这个事情。

    以前从来没有看过netfilter的代码。只是在几年前,曾经有个同事做过一个关于iptable的ppt。

    当时,也没有搞明白,只是觉得iptable的命令繁杂的很。

    现在,只给我这么短的时间,让我搞这个,感觉真是叫赶鸭子上架。

    没办法,只能硬着头皮做起来。不过,动起手来发现 其实没有想象那么困难。

    netfilter部分的代码,结构还是蛮清晰的。

     

    一般来讲,我们用netfilter做两部分的工作,一个是 filter 一个是NAT。

    filter 就是根据包的规则做过滤,像防火墙啦,负载或者流量控制啦。

    NAT 就是地址转换。(实际上NAT是属于netfilter下面的 conntrack模块的一部分,但是NAT更有名一些啦)

     

    netfilter做这个工作的方法就是在 5个点加钩子函数,于是,我们会看到每次提到netfilter都会看到的一幅图:

    netfilter

     

    5个点分别是:

    LOCAL_IN

    LOCAL_OUT

    PRE_ROUTING

    FORWARD

    POST_ROUTING

     

    通过函数 nf_register_hook 就可以把注册一个处理函数到这5个点上的其中一个。

     

    nf_register_hook函数的参数是这样一个结构:

    struct nf_hook_ops

    {

    struct list_head list;  //将该结构插入到一个双向链表中

    nf_hookfn *hook; //钩子函数

    struct module *owner;

    int pf;

    int hooknum;   //代表插入到上述5个点的哪一个

    int priority;  //优先级,插入到什么位置

    };

     

    优先级是这么定义的

     

    enum nf_ip_hook_priorities {

    NF_IP_PRI_FIRST = INT_MIN,

    NF_IP_PRI_CONNTRACK_DEFRAG = -400,

    NF_IP_PRI_RAW = -300,

    NF_IP_PRI_SELINUX_FIRST = -225,

    NF_IP_PRI_CONNTRACK = -200,

    NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD = -175,

    NF_IP_PRI_MANGLE = -150,

    NF_IP_PRI_NAT_DST = -100,

    NF_IP_PRI_BRIDGE_SABOTAGE_LOCAL_OUT = -50,

    NF_IP_PRI_FILTER = 0,

    NF_IP_PRI_NAT_SRC = 100,

    NF_IP_PRI_SELINUX_LAST = 225,

    NF_IP_PRI_CONNTRACK_HELPER = INT_MAX - 2,

    NF_IP_PRI_NAT_SEQ_ADJUST = INT_MAX - 1,

    NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,

    NF_IP_PRI_LAST = INT_MAX,

    };

     

    当每次调用 nf_register_hook 注册一个 nf_hook_ops 到一个点(hooknum)上,

    每个点都是一个双向链表,nf_register_hook会把nf_hook_ops 按照优先级

    插入到相应的位置上。所以,在iptable里面把这5个点 叫做5个chain。

     

    在filter,mangle 和nat中, nf_hook_ops里面的钩子函数, 又会去调用一个函数 ipt_do_table

    相应的table里面包含很多规则,所以,将之称为table。

     

    chain和table这两个名词,在我看iptable的时候,一度搞晕了。

    其实看看源码就知道怎么回事了,非常清晰。

     

    每个hook函数返回值为:

     

    NF_ACCEPT: 继续按正常的协议栈流程处理。

    NF_DROP: 丢弃包,中止协议栈的流程。

    NF_STOLEN: 这个包已被缓存,中止协议栈的流程。

    NF_QUEUE: 把包放到队列中(通常会把包转发到用户空间处理)。

    NF_REPEAT: 再此调用这个钩子函数。

     

    另外,要注意一个是 抛开mangle的部分不说,其实每条包的经过路径都是这样一个过程:

     

    CONNTRACK -> DNAT -> FILTER -> SNAT -> CONNTRACK

     

    上面是 netfilter总体结构的一个介绍,下次会重点介绍一下 conntrack部分

     

    因为时间仓促可能有的东西理解的不正确,如果高手看到请指正,谢谢。

     

     

     

     

     

     

     

  • 最近,搞一个项目,想加入类似 linux kernel中的menuconfig的功能,进行项目配置管理。

    于是,看了看相关代码。

    不过,一直没有找到 独立的项目,只得从 busybox 项目中, 把menuconfig的部分分离出来。

    形成一个独立的小项目,以便移植到其他需要类似功能的项目中去。

    有兴趣的朋友可以从 google code 的svn上 下载。

    这是一个非常简单的menuconfig示例。

    只需要执行 make menuconfig

    进入菜单,选择相关项目,保存,就会输出两个项目:

    .config 这个文件通常可以用于MAKEFILE处理用。

    include/autoconf.h 这个可以直接在 c 代码里面使用。

     

    理论上,这个配置还支持

    make xconfig

    make config

    make gconfig

    make oldconfig

    make defconfig

    等等,不过,我没有测试过。

    回头我会简单介绍一下,代码的处理过程,和脚本的语法。

    谢谢关注

     

     

  • 最近在porting uIP,就顺便读了下uIP的代码。

    意外发现一个比较好玩的东西 Protothreads

    这是个什么东西呢?

    文档上说,这是一个极轻量级的无栈线程库。

    简单的说,就是在一个死循环 while(1){} 里面跑多个线程,但是,又不必为每个线程都分配一个栈。

    因为为每个线程都分配一个栈空间还要分配一个数据结构,太耗费内存了。

    所以,它特别适合在内存受限的嵌入式系统里面用。

    比如,一个小嵌入式系统,要支持网络协议,还要支持客户的命令,而根本就没有一个操作系统。

    你就可以用 Protothreads 来做一个简单的线程调度。

    就好像有了个小操作系统。而其代价又是如此之小。不单没有什么调度算法的cpu开销。

    内存方面为每个线程也只需要多花两个字节。

    还支持信号量操作,而且不用汇编,只用c语言和一些宏定义。

    哇,简直太幸福啦!

    这么酷的玩意是怎么搞的呢?

    其实,说起来也不麻烦,他提供了一些宏,我读了一些,展开来大致就相当于下面这种形式:

    while(1){

    switch(s) {

    case 0:...

    case x:...

    case y: ...

    }

    }

    从下面几个宏的定义就能看出来了:

    #define     LC_INIT(s)   s = 0;
    #define     LC_RESUME(s)   switch(s) { case 0:
    #define     LC_SET(s)   s = __LINE__; case __LINE__:
    #define     LC_END(s)   }   

     

    不过,这样做显然也是有一些代价的,

    第一,使用局部变量要极为小心,(文档建议不要用)

    很简单,没有栈啊,局部变量放哪里(这个解释不严密,具体看看实现就清楚了)?

    第二,当调用函数时,没多调用一层就要多加一个变量,记录执行到这个函数的位置。

    以便下次进入时,能找到合适的位置。

     

    不过,不管怎么讲,这还是很好玩的一个“线程”库,如果手上碰巧碰到一个内存极少的硬件可以用下。

     

  • 接上篇
    最后大致讲一下处理流程,主要讲几个函数 malloc, free 和 malloc_extend_top。

    - malloc
    当申请内存的时候就调用 Void_t* malloc(size_t bytes)

    malloc函数首先根据申请内存的大小计算实际需要的内存,因为malloc_chunk本身需要一些内存,还有8字节对齐的pad部分。如果实际需要的内存小于512byte就直接在相应的bin中寻找是否有空余的内存,如果没有可以再到下一个bin中寻找(这个个bin分配到的内存要多8byte。)

    如果需要内存不小于512就要根据相应算法找到所需的bin。查找空闲内存块。因为这里的内存块大小并不相同,所以要比较大小。如果大于所需内存太多(多过一个最小块的大小)就不能用了。


    如果上面步骤没有找到内存,就到last_remainder里面去找,如果last_remainder里面的够大。就在上面分配。
    如果还没有找到就全局搜索,寻找 best fitting chunk。这时还没有找到的话,就在top上面分配,如果top上面的大小不够,则调用malloc_extend_top函数向系统申请更多的内存扩充top。如果还申请不到足够的内存,就只能返回空指针了。

    - free
    free的算法比较简单,当free一块内存时,就把这个malloc_chunk块挂到相应的bin中,不过在挂之前,要先看看邻接的内存块是否空闲,如果空闲则正好合并成大块,这样可以减少内存碎片的问题。
    另外,如果当前释放的内存被合并到top上面,会检查一下top有多大,如果非常大了(uboot定义为128k),就通过函数malloc_trim还给系统。

    - malloc_extend_top

    malloc本身并没有内存,所有的内存都是通过malloc_extend_top函数分配给top, 然后再给malloc函数去分配的。所以,本身malloc_extend_top函数很简单,不过要注意几个事情,一个是向内存申请通常是以一页(4k)为单位申请;另外,每次向系统申请的内存通常来说应当连续,如果不连续说明还有别的程序再想系统申请内存,这时不能把分到的内存直接扩充到top上。

     

    好了,最近看的dlmalloc的部分基本上写完了。

    其实,代码本身比较细,很多东西一下子看不清楚,所以难免有理解错的地方。

    写下来有个好处,回头如果需要不必重新看起。

    读了dlmalloc后,自己在其基础上面写了个非常简单的malloc。

    实现一个简单的malloc功能:

    只有一个链表,所有free的内存块都挂在上面,

    分配的时候,先从这个链表里面找 first-fit,就是只要够大就用,如果非常大就分割一下。

    如果链表不够,就调用malloc_extend_top申请新内存。

    跑了一下感觉还不错,其实在uboot这种简单应用场景下面这样简单的东西也够用啦。

     

  • 接上篇

    dlmalloc有两个重要的数据结构,一个是 chunk, 另一个是bin。

    chunk就是分配内存的数据块,定义如下:
    struct malloc_chunk
    {
      INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
      INTERNAL_SIZE_T size;      /* Size in bytes, including overhead. */
      struct malloc_chunk* fd;   /* double links -- used only if free. */
      struct malloc_chunk* bk;
    };
    这是malloc_chunk的数据结构,不过要注意,一块malloc_chunk可不是只有这么大。
    一块malloc_chunk的大小是8byte的倍数。
    举个例子:
    如果申请一块4byte的内存,只是实际会分配出16byte。
    其中,头8byte存放 prev_size和size,然后是4个byte的内存,最后还有4个byte是为了8byte对其的pad。
    如下图:

    =================
       prev_size [4byte]
       size         [4byte]
      -------------------
       data        [4byte]
      -------------------
       pad         [4byte]
    =================

    这样我们可以看到,fd和bk在分配出去的时候并不存在,只是在空闲时可用,构成一个双向链表。
    当malloc_chunk被free时,就会插入一个双向链表中,留作以后分配,用得就是fd和bk。

    而prev_size和size还有一个用处,就是通过这两个元素,可以找到与当前malloc_chunk块相邻的
    的malloc_chunk块。
    比如:有指针p指向一个malloc_chunk块: struct malloc_chunk * p = ...
    这是通过 (struct malloc_chunk *)((char*)(p) + p->size) 和
    (struct malloc_chunk *)((char*)(p) - p->prev_size)
    就可以找到相邻内存。
    这在合并内存的时候非常有用。

    另外,要注意的是 size的最低一位是标记是否被用的状态。
    因为malloc_chunk的大小是8byte的倍数。所以,低3位都为零,于是最低一位就当作一个标记位。
    标记是否被分配。不过,奇怪的是,这个标记是将相邻前一块是否被用 (PREV_INUSE)而不是当前块。
    这点一直让我蛮费解的。

    关于chunk,这里一篇文章介绍的蛮详细的,还有些配图,很不错。

    www.vtzone.org里面还有挺多介绍dlmalloc和内存管理方面的文章,值得读读。

    chunk介绍完,就该介绍bin了。
    所谓的bin其实就是一些双向链表,在初始化的时候,申请了128个双向链表。用以存放空闲的malloc_chunk。
    这样,就可以快速的寻找的所需要的malloc_chunk。其中,前64个bin存放的是固定尺寸
    (16byte-512byte以8byte递增)
    的malloc_chunk。之后,有相应的映射算法。这是因为小于512byte的分配更频繁,这样做会有更好的效率。
    在分配时,以最小块优先,最佳适合的顺序搜索可用的块。

    其中注意两个特殊的bin是top和last_remainder。
    top存放的是最新从系统获得到的内存,分配时先在其他链表中寻找,如果都没有才在top上面分配,
    如果top的内存也不够分了,就从系统申请新的内存,还是放在top上面供分配。

    last_remainder是指上次被切分的malloc_chunk,这大概是根据局部化特性,来提高分配的效率。

    这样数据结构就大致介绍这么多,下一篇讲讲处理流程

  • 最近,又有关于uboot的项目,于是读了malloc部分的代码。uboot上的malloc是用的dl malloc,这是由Doug Lea写的一个malloc版本。据说,linux上用的就是它。看网上有人测试,效率蛮不错的。

    这是作者写的说明 (中文)

    u-boot用dlmalloc的版本是 2.6.6,应该算是比较老的吧,最新的是2.8.3。不过,对于u-boot来讲已经足够了。本身没有多少内存好分配的。也用不到mmap。

    对于u-boot来说,就是先设定一块内存空间留给malloc分配。malloc分配时,先回寻找当前空闲的块。如果找不到就通过函数malloc_extend_top调用一个宏:MORECORE(直接调用sbrk函数,sbrk函数是unix的标准函数,在uboot里面自己写了一个非常简单的sbrk),从系统(就是uboot了)申请一块内存。这时,uboot就从前面预留的内存空间中分配一块给malloc。

    free的部分比较简单,当一块内存free的时候,先看看能不能跟前后空闲的内存块一起合并,这样就能避免内存的碎片问题。然后,把释放的内存按照大小插入的相应的链表中,以供下次分配用。

    代码虽然不算很多,但是却挺不容易读懂的。
    努力读了读,大致明白了基本的流程。

    打算分几次记录一下。

    下一篇

  • 2009-02-12

    u-boot移植 - [uboot]

    当我们有一个新的硬件 希望porting uboot要做哪些工作呢?

    首先,代码部分要做以下工作:
    lowlevel_init.S 主要是配置频率,硬件间差别较大(频率值和寄存器配置方式都会不同)通常都要根据实际情况进行设置。

    start.S, cache.S 两个文件基本可以参照现有文件,进行相应修改。

    另外,配置ddr controller, flash初始化,串口都是必要工作。

    还有就是pci和网口的驱动代码,还有环境变量的访问。

    配置方面:
      要添加相应硬件的头文件xxx.h在 include/configs目录下, 该文件内容比较多,尽量参照意义已有类似硬件的模板。
     
    Makefile中加入编译目标:
      xxx_config    :    unconfig
        @$(MKCONFIG) $(@:_config=) ARCH CPU BOARD VENDOR SOC

    目录组织与硬件配置的关系可以参考如下:
       
    header_file        ARCH    CPU            BOARD        VENDOR    SOC
    smdk2410         arm     arm920t     smdk2410     NULL     s3c24x0

    LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a
    board/smdk2410/libsmdk2410.a

    LIBS += cpu/$(CPU)/lib$(CPU).a
    cpu/arm920t/libarm920t.a

    LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
    cpu/arm920t/s3c24x0/libs3c24x0.a

    LIBS += lib_$(ARCH)/lib$(ARCH).a
    lib_arm/libarm.a

  • 2009-02-12

    u-boot命令配置 - [uboot]


    u-boot启动后,进入命令模式,通过输入uboot提供的命令来进行交互操作。一些相关命令的用法已经在前面文章介绍,这里介绍一下uboot命令内部结构,并了解如何配置uboot的命令,以及可以自己添加一些命令。

    如何实现一个命令,看下面一个简单的例子,就明白了。

    uboot命令的数据结构定义:

    struct cmd_tbl_t {
        char        *name;        /* Command Name            */
        int        maxargs;    /* maximum number of arguments    */
        int        repeatable;    /* autorepeat allowed?        */
                        /* Implementation function    */
        int        (*cmd)(struct cmd_tbl_s *, int, int, char *[]);
        char        *usage;        /* Usage message    (short)    */
        char        *help;        /* Help  message    (long)    */

    };

    声明命令的宏定义:

    #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
    cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}


    声明一个命令:

    U_BOOT_CMD(
        exit,    2,    1,    do_exit,
         "exit    - exit script\n",
        "    - exit functionality\n"
    );

    命令的具体实现:

    int
    do_exit (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
    {
        int r;

        r = 0;
        if (argc > 1)
            r = simple_strtoul(argv[1], NULL, 10);

        return -r - 2;
    }

    这样,就实现了一个命令 “exit” 。

    如果,我们需要自己添加一个新命令,只要有实现函数 do_xxx,和命令声明U_BOOT_CMD(xxx, ...) 就可以了。

    下面,我们介绍一下命令的配置方式。注意:最新的uboot命令配置方法发生变化,与以前不同。

    首先,在头文件 smdk2410.h (该文件前面文章有介绍) 包含了所需的命令头文件
    #include <config_cmd_default.h>


    在 config_cmd_default.h 中定义了,所需要用到的命令,如:
    #define CONFIG_CMD_MEMORY    /* md mm nm mw cp cmp crc base loop mtest */
    (所以,autoconf.mk里,就会有 CONFIG_CMD_MEMORY=y )

    而在common/Makefile 下,有这样的定义:
    COBJS-$(CONFIG_CMD_MEMORY) += cmd_mem.o

    这样,当config_cmd_default.h里有了相应定义,就会把相应实现进行编译。
    就可以获得相关命令的支持了。

     

     

  • 2009-02-11

    u-boot编译过程二 - [uboot]

    接着上次,这次介绍make all的过程。
    首先,介绍一下生成的config.mk 和 config.h如何使用,得到正确配置的。

    config.mk直接被include到Makefile来,并使用其定义如下:
        include $(obj)include/config.mk
        export    ARCH CPU BOARD VENDOR SOC
    这样可以直接选择需要编译的模块,例如:
        LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
        LIBS += lib_$(ARCH)/lib$(ARCH).a
        LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a
    config.h被include/common.h所包含,而它有包含了相应硬件的头文件。
        common.h <--- config.h <--- smdk2410.h
    除了在源程序中,使用这些头文件的定义之外,uboot还有一个脚本通过解析common.h以及其包含的所有头文件信息,来生成配置信息如下形式:
        CONFIG_BAUDRATE=115200
        CONFIG_NETMASK="255.255.255.0"
        CONFIG_DRIVER_CS8900=y
        CONFIG_ARM920T=y
        CONFIG_RTC_S3C24X0=y
        CONFIG_CMD_ELF=y

    而头文件的定义的形式如下,对比可以看出脚本的工作原理。   
        #define CONFIG_BAUDRATE        115200
        #define CONFIG_NETMASK          255.255.255.0
        #define CONFIG_DRIVER_CS8900    1    /* we have a CS8900 on-board */
        #define CONFIG_ARM920T        1    /* This is an ARM920T Core    */
        #define    CONFIG_RTC_S3C24X0    1
        #define CONFIG_CMD_ELF

    通过这样,uboot可以自动得到一个模块选择的配置功能。如果我们需要添加什么定义或者功能,也可以在相应的头文件中加入定义实现。

    现在,配置已经得到,就看最后的编译流程。
    编译分为五大部分,分别如下:
        1. $(SUBDIRS) 工具,例子等,包括目录:tools examples api_examples
        2. $(OBJS)  启动模块 cpu/arm920t/start.o
        3. $(LIBBOARD) 板子支持模块 board/smdk2410/libsmdk2410.a
        4. $(LIBS)     其他模块,有诸如以下模块:
                cpu/arm920t/libarm920t.a
                cpu/arm920t/s3c24x0/libs3c24x0.a
                lib_arm/libarm.a
                fs/jffs2/libjffs2.a
                fs/yaffs2/libyaffs2.a
                net/libnet.a
                disk/libdisk.a
                drivers/bios_emulator/libatibiosemu.a
                drivers/mtd/libmtd.a
                drivers/net/libnet.a
                drivers/net/phy/libphy.a
                drivers/net/sk98lin/libsk98lin.a
                drivers/pci/libpci.a
                common/libcommon.a /

        5. $(LDSCRIPT) 链接脚本

    编译完成这五部分,链接成elf格式的u-boot文件,最后通过objcopy -O binary命令将elf格式转换成为raw binary格式的文件u-boot.bin就可以烧到板子上使用了。

  • 2009-02-11

    u-boot编译过程一 - [uboot]

    现在介绍一下u-boot的编译过程,这里用的uboot版本是U-Boot 2008.10,硬件用smdk2410,这个板子用得比较普遍,uboot已经有对其的支持。通过我们对编译过程和代码的了解,我们也容易用uboot支持我们自己需要的硬件。

    编译命令非常简单:
        make smdk2410_config (生成配置)
        make all  (生成最终文件)
    当然,更好的做法是把编译出的文件生成到另外一个目录,并make clean如:
        export BUILD_DIR=../tmp
        make distclean
        make smdk2410_config
        make all

    现在,我们可以来看看Makefile,u-boot的Makefile文件非常大。但是,其结构却并不复杂。
    u-boot已经支持了很多硬件,前半部分是共用部分,编译出最终的uboot可执行文件。
    而后半部分,是为各种不同的硬件进行配置,每种硬件有一个目标,每个的做法都非常类似,我们用到的是:
    smdk2410_config    :    unconfig
        @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0

    这里的 MKCONFIG    := $(SRCTREE)/mkconfig
    实际上是调用脚本mkconfig,而这个脚本做的工作简单如下:

    建立config.mk文件
        echo "ARCH   = $2" >  config.mk
        echo "CPU    = $3" >> config.mk
        echo "BOARD  = $4" >> config.mk
        echo "VENDOR = $5" >> config.mk
        echo "SOC    = $6" >> config.mk
    建立config.h
        echo "#include <configs/$1.h>" >>config.h
       
    在这里$1-$6的值分别是:smdk2410 arm arm920t smdk2410 NULL s3c24x0

    而执行了 make smdk2410_config 之后,就生成了相应的config.mk,config.h两个文件。
    在config.mk文件中,定义了相应硬件信息 : ARCH CPU BOARD VENDOR SOC
    在config.h文件中,包含了相应硬件的头文件smdk2410.h ,位于include\configs目录下。
    如果新建自己的硬件项目,那么也需要建立相应的头文件在这个地方。
    这样,uboot的配置已经生成,下一次介绍make all的过程。

  • 2009-02-10

    u-boot命令总结(zt) - [uboot]

    Printenv 打印环境变量。

    Uboot> printenv
    baudrate=115200
    ipaddr=192.168.1.1
    ethaddr=12:34:56:78:9A:BC
    serverip=192.168.1.5
    Environment size: 80/8188 bytes

    Setenv 设置新的变量

    Uboot> setenv myboard AT91RM9200DK
    Uboot> printenv
    baudrate=115200
    ipaddr=192.168.1.1
    ethaddr=12:34:56:78:9A:BC
    serverip=192.168.1.5
    myboard=AT91RM9200DK
    Environment size: 102/8188 bytes

    Saveenv 保存变量

    命令将当前定义的所有的变量及其值存入flash中。用来存储变量及其值的空间只有8k字节,应不要超过。

    Loadb 通过串口Kermit协议下载二进制数据。

    Tftp 通过网络下载程序,需要先设置好网络配置

    Uboot> setenv ethaddr 12:34:56:78:9A:BC
    Uboot> setenv ipaddr 192.168.1.1
    Uboot> setenv serverip 192.168.1.254    
    tftp服务器的地址)
    下载
    bin文件到地址0x20000000处。
    Uboot> tftp 20000000 application.bin application.bin应位于tftp服务程序的目录)

    Uboot> tftp 32000000 vmlinux
    把server(IP=环境变量中设置的serverip)中/tftpdroot/ 下的vmlinux通过TFTP读入到物理内存32000000处。

    Md 显示内存区的内容。

    Mm 修改内存,地址自动递增。

    Nm 修改内存,地址不自动递增。

    Mw 用模型填充内存

    mw 32000000 ff 10000(把内存0x32000000开始的0x10000字节设为0xFF)

    Cp 拷贝一块内存到另一块

    Cmp 比较两块内存区

    这些内存操作命令后都可加一个后缀表示操作数据的大小,比如cp.b表示按字节拷贝。

    Protect 写保护操作

    protect on 1:0-3(就是对第一块FLASH的0-3扇区进行保护)
    protect off 1:0-3取消写保护

    Erase 擦除扇区。

    erase: 删除FLASH的扇区
    erase 1:0-2(就是对每一块FLASH的0-2扇区进行删除)

    DataFlash的操作

    U-Boot在引导时如果发现NPCS0NPCS3上连有DataFlash,就会分配虚拟的地址给它,具体为 :
    0xC0000000---NPCS0
    0xD0000000---NPCS3

    run 执行设置好的脚本

    Uboot> setenv flashit tftp 20000000 mycode.bin\; erase 10020000 1002FFFF\;
    cp.b 20000000 10020000 8000
    Uboot> saveenv
    Uboot> run flashit

    bootcmd 保留的环境变量,也是一种脚本

    如果定义了该变量,在autoboot模式下,将会执行该脚本的内容。

    Go 执行内存中的二进制代码,一个简单的跳转到指定地址

    Bootm 执行内存中的二进制代码

    要求二进制代码为制定格式的。通常为mkimage处理过的二进制文件。
    起动UBOOT TOOLS制作的压缩LINUX内核, bootm 3200000

    Bootp 通过网络启动,需要提前设置好硬件地址。

    得到所有命令列表

    help  help usb, 列出USB功能的使用说明

    ping  注:只能开发板PING别的机器

    usb

    usb start:  起动usb 功能
    usb info:  列出设备
    usb scan:  扫描usb storage(u 盘)设备

    kgo  起动没有压缩的linux内核

    kgo 32000000

    fatls 列出DOS FAT文件系统

    fatls usb 0列出第一块U盘中的文件

    fatload 读入FAT中的一个文件

    fatload usb 0:0 32000000 aa.txt 把USB中的aa.txt 读到物理内存0x32000000处!

    flinfo 列出flash的信息

    nfs

    nfs 32000000 192.168.0.2:aa.txt
    把192.168.0.2(LINUX 的NFS文件系统)中的NFS文件系统中的aa.txt 读入内存0x32000000处。

     

    来源:电子工程师笔记

     

     

     



  • 上次讲到,内存划分好,准备进行relocate code。
    relocate code的意思是这样的。通常u-boot的执行代码肯定是在flash上(当调试的时候也可以放在ram上)。当启动起来以后,要把它从flash上搬移到ram里运行。这个工作就叫做relocate code。

    但是,问题在于,flash上的地址和ram上的地址是不同的。当我们把代码从flash上搬移到ram上以后,当执行函数跳转时,代码里的函数地址还是flash上的地址,所以一跳就跳回去了。
    这怎么办呢?
    在u-boot里面用的是PIC(position-independent code)的方式解决这个问题。
    简单介绍一下其原理。当你用PIC方式时,在用gcc编译时需加上 -fpic的选项。编译器会为你的可执行代码建立一个GOT(global offset table)的段。一个地址在GOT表中有一项,里面存放地址的信息,而在使用这个地址时,只要根据这个地址的编号(也可以叫做偏移量offset)找到表中相应的项目,就可以取得那个地址了。
    而如果位置发生变化,只要对GOT表中的地址进行修改就可以了。
    我们可以通过反汇编,看一个简单的函数调用例子:
    lw    t9,1088(gp)
    jalr    t9

    这里,gp存放的就是GOT表的起始地址,而1088就是要调用函数的offset,也就是说GOT表的那个位置存放着它的地址。lw    t9,1088(gp) 把函数地址放入t9, 然后调用就可以了。

    知道了PIC的原理,解释u-boot relocate code的方法就简单了。
    简单的说就把u-boot的执行代码直接从flash里copy到ram的相应区域。
    然后,把GOT表中的地址都加上一个偏移量,这个偏移量就是flash里的地址与ram里的地址的差。
    还有其他一些工作比如:设置新的栈指针,从flash代码里跳转到ram代码里 等等。

    之后,就进入board.c的board_init_r函数,在这个函数里初始化 malloc,flash,pci 以及外设(比如,网口),最后进入命令行或者直接启动Linux kernel。
    这样,u-boot的启动工作就完成了。

  • 本来老早就应该写这篇文章。不过,家里有事,刚来了个小宝宝。呵呵,真是忙的不行。不过,小家伙也真是可爱。现在终于有点空,把它整理一下,写出来。

    u-boot的启动过程比较简单,大致做下面的工作:
        1 cpu初始化
        2 时钟,串口,内存(ddr ram)初始化
        3 内存划分,分配栈,数据,配置参数,以及u-boot代码在内存中的位置。
        4 对u-boot代码做relocate
        5 初始化 malloc,flash,pci 以及外设(比如,网口)
        6 进入命令行或者直接启动Linux kernel

    基本上,这就是u-boot的启动要做的事情,我也曾经大致看过arm的启动代码,也是类似。
    不过,这里以mips作为例子进行介绍。

    启动涉及到几个文件: start.S, cache.S, lowlevel_init.S 和 board.c  前三个都是汇编代码。

    程序从start.S的_start开始执行。首先,初始化中断向量,寄存器清零,大致包括32个通用寄存器reg0-reg31和协处理器的一些寄存器:CP0_WATCHLO,     CP0_WATCHHI,     CP0_CAUSE, CP0_COUNT, CP0_COMPARE等等。

    之后,配置寄存器CP0_STATUS,设置所使用的协处理器,中断以及cpu运行级别(核心级)。
    配置gp寄存器,把GOT段的地址赋给gp寄存器。(gp寄存器的用处会在后面relocate code的部分详细解释)
    这时,开始执行lowlevel_init.S的lowlevel_init,主要目的是工作频率配置,比如cpu的主频,总线(AHB),DDR工作频率等。
    然后,调用cache.S的mips_cache_reset对cache进行初始化。接着调用cache.S的mips_cache_lock。这个调用的目的,起初让我不解,后来才知道。这时ddr ram并没有配置好,而如果直接调用c语言的函数必须完成栈的设置,而栈必定要在ram中。所以,只有先把一部分cache拿来当ram用。做法就是把一部分cache配置为栈的地址,锁定。这样,当读写栈的内存空间时,只会访问cache,而不会访问真的ram地址了。

    这时,配置栈的地址,进行调用函数board_init_f(board.c)
    进入函数board_init_f后,首先做一系列初始化:
                    timer_init    时钟初始化
                    env_init    环境变量初始化(取得环境变量存放的地址)
                    init_baudrate    串口速率
                    serial_init        串口初始化
                    console_init_f    配置控制台
                    display_banner    显示u-boot启动信息,版本号等
                    checkboard        执行board相关的操作。
                    init_func_ram    初始化内存,配置ddr controller
    这一系列工作完成后,串口和内存都已经可以用了。然后,就要把内存进行划分,
    在内存的最后一部分,留出u-boot代码大小的空间,准备把u-boot代码从flash搬移到这里
    然后,是堆的空间,malloc的内存就来自于这里。紧接着放两个全局数据结构bd_info global_data和环境变量boot_params。最后,是栈的空间。

    内存划分好,就准备进行relocate code了。关于relocate code和其他启动过程下次再介绍。

     

  • 2008-11-26

    U-boot简介 - [uboot]

    最近一段时间在读uboot的代码,看了redboot之后,感觉uboot比较简单些。
    不像redboot做了很多抽象的工作,而且背后还有ecos那么个大家伙。
    uboot目的很明确,就是做bootloader。所以,更简洁,代码量少,也比较容易读。

    我觉得做嵌入式软件,从uboot这样的软件开刀,进行学习,比较好。
    一方面,代码量少,这样不会感觉,任务量大,无处下手。
    另一方面,bootloader相关的东西很多,从一般开发都需要用到的编译知识,如makefile,link script
    到对cpu, 设备驱动,几乎所有相关的东西都用到了。

    更重要的是,你面对的是一个裸机,你可以从它最初上电开始,看着它一点一点运行起来。
    那感觉真好。

    uboot现在基本上是应用最广的bootloader了,似乎比redboot用得更多些。
    我主要看了两个不同的平台,一个是mips的cpu,用的是24k的core, 另一个是arm的芯片,三星的s3c2410的cpu
    用的是arm 920t的core。 mips的代码基本上读完了,arm的才刚刚开始。

    arm和mips也算是当下最流行的嵌入式cpu喽。应该还是有代表性的。
    后面想花些时间,边读边总结一下。

    uboot的文档可以访问这里
    已发布源码可以在这里下载
    也可以用git下载最新的代码

    在源码中,还有一份README,建议porting的时候可以读读。
    最后,我把这份README里面的porting guid贴出来,给大家读读。
    嘿嘿,蛮有意思的。

    U-Boot Porting Guide:
    ----------------------
    int main (int argc, char *argv[])
    {
        sighandler_t no_more_time;

        signal (SIGALRM, no_more_time);
        alarm (PROJECT_DEADLINE - toSec (3 * WEEK));

        if (available_money > available_manpower) {
            pay consultant to port U-Boot;
            return 0;
        }

        Download latest U-Boot source;

        Subscribe to u-boot-users mailing list;

        if (clueless) {
            email ("Hi, I am new to U-Boot, how do I get started?");
        }

        while (learning) {
            Read the README file in the top level directory;
            Read http://www.denx.de/twiki/bin/view/DULG/Manual ;
            Read the source, Luke;
        }

        if (available_money > toLocalCurrency ($2500)) {
            Buy a BDI2000;
        } else {
            Add a lot of aggravation and time;
        }

        Create your own board support subdirectory;

        Create your own board config file;

        while (!running) {
            do {
                Add / modify source code;
            } until (compiles);
            Debug;
            if (clueless)
                email ("Hi, I am having problems...");
        }
        Send patch file to Wolfgang;

        return 0;
    }

    void no_more_time (int sig)
    {
          hire_a_guru();
    }

    ------------------
    好了,今天聊到这,下次准备介绍一下uboot的编译过程。

  • 随着计算机的飞速发展,虽然内存变得越来越大,但是在速度上面却跟不上cpu的发展。为了保证系统整体性能,cache的地位变得越来越重要。

    Cache的原理很简单,就是根据程序访问的局部性,把最近访问过的数据或者程序缓存在一块速度更快的内存中。
    现在的cache命中率一般都会超过90%,这样就解决了 越来越快的cpu和 越来越大却相对慢的内存之间的配合问题。

    为了完成这个任务,简单的方法就是把cache分成许多行(line),每一行分为两部分,一部分是Data,保存缓存的数据,另一部分是Tag,保存这这些数据在内存中的地址信息。每行之间没有关系,这样每次cpu访问的时候,把地址给每一行的tag进行比较,如果有相同即为命中。这就是“全相联Cache ”。
    这种方式的缺点是,每次内存访问都要跟每一行的tag进行比较,造成结构上的复杂。
    假设cache大小为64KB,每一行的data是32B,那么就有2048 line, 一次内存访问要跟每一个line的tag进行比较,才能确定是否命中,显然太复杂了。

    为了解决这个问题,就对其进行改进,改进的结果就是直接映像Cache。 这种方式把访问内存的地址分为3个部分。高位地址,低位地址,和行内部地址。比如,地址为32位,cache大小为64KB, 每行的data大小为32B。
    那么地址位如下分配:
    高位地址范围为:[31-16], 低位地址范围为[15-5], 行内部地址范围为[4-0]
    比如:对于地址 0x12345678, 0x1234为高位地址, 0x567 >> 1 为低位地址, 0x18为行内地址
    在访问该内存的时候,首先用低位地址作为行号来检索 0x567 >> 1 = 0x293 就是第659行cache,找到这行cache用0x1234与其tag内的地址比较,相等则命中。

    很显然这种结构,非常简单,只需要跟一行进行比较就可以了。不过,它的缺点是每一行对应内存中很多不同的地址,而且这些地址只能缓存在这一行中。
    比如,当上面那个地址缓存了之后,我们又要访问地址0x43215678, 这时候还是找到第659行cache,但是只能将0x12345678的数据作废,重新读入新地址的数据。如果,这种情况频繁发生,会大大降低系统效率。

    为了解决这个问题就出现了改进的“组相联Cache“,组相联Cache 把cache分成几个独立的组。这样,虽然一个line可以对应内存中的很多地址,但是内存的这些地址可以存在不同的组中,这样碰到上述那种情形的概率就非常低了。

    举个例子:64K cache分为4组,每组16K,每行32B,每组就有512行。
    那么地址分配方式就变为:
    高位地址范围为:[31-14], 低位地址范围为[13-5], 行内部地址范围为[4-0]
    0x12345678 地址就对应到一组中的第147行,虽然地址0x43215678也对应在147行,但是他们可以分别存放在4组中的任意两组而互不影响。从效果上,和全相联Cache基本相当,却在结构上简单很多。所以,是当前常用的方式。

    最近,需要用到cache看了一些介绍,做个简单的笔记。
    这是一个基本的工作原理,实际上的cache要复杂的多,比如 write-through和write-back问题,cache aliases问题等等,而且不同的cpu cache的配置方式也各不相同,以后有机会再详细记录。
  • 如何读源码2

    上次聊过这个话题,不过最近又在读代码,这次不是读redboot,而是读一个网络协议栈的代码。协议还是蛮复杂的,代码很多,更郁闷的是没有运行环境。所以,只能摸黑读代码,很多不懂的地方只能靠猜了。

    还是,有些体会,记录一下:

    1 Programs = Algorithms + Data Structures ,通常读代码的时候,可能更关注执行的流程。其实,数据结构应该给予更多的关注,数据结构相比执行流程,代码量少的多,却更容易体现设计者的思路。

    2 协议,文档和注释。有的算法比较复杂,看代码会很累,所以要注意文字性的描述,比如协议规范,程序注释,已经附带的文档。搞清楚代码想做什么,然后看起来可能会更清楚了。

    3 把注意力集中在主干流程上。 程序的流程错综复杂,有很多代码是针对错误处理,或者一些其他情况。也会有一些宏来区分不同平台,硬件等等。所以,一开始不要太执着于细节,首先理清楚自己所关注的流程上。把整个流程走通,然后把流程中的细节问题读懂,之后再去读与流程相关的其他情况。

    4 推荐一个工具,freemind,  用树形的结构来整理函数的调用过程还是不错的。这次帮我不少忙。
     
    5 画图推演,对一些算法,不要光读,要用笔画图演示一下执行过程,更直观的领会程序的功能。
  • 今天看到这篇文章 ,这是程序设计常用到的几个概念,不过,对这几个概念每个人都会有自己的理解。我自己也经常想关于其定义和不同的含义,所以这里也把自己的想法表述一下。

    架构(Architecture),通常架构是个比较宏观的概念,它关注的是对一个完整系统组成的划分和他们之间的关系。这种关系的定义通常是定义了一种协议,即各组织部分之间的交互协议。
    比如:分层(Layer) 是一种常用的架构模式。每一层关注自己的功能角色,以及与其他层的交互协议。

    框架(Framework),也是一个宏观的概念,也会把一个系统划分成不同的部分,并定义他们的关系。但是其与架构不同的地方在于架构之上一种设计,而框架已经包含了某种程度的实现。所以,架构通常是更纯粹的设计思想,而框架已经是一个产品的部分实现了。
    与框架相对应的概念是 库(Library)。库也是一种实现,但是正如文章说的:库是你去调用它,而框架是它来调用你。我们也可以认为,框架是用的自顶向下的设计思路,而库是用自底向上的设计思路。

    最常用的框架模式,应该属 MVC 了吧,每一个MVC的框架都会定义 model view controller 的三个组成部分,框架使用者可以根据框架的要求来实现自己的 model view controller ,而三部分的交互方式已经由框架定义实现。框架通过自身把几个组成部分粘合在一起,形成一个整体。
    当然,同样MVC也可以作为一种架构模式,所以,我们可以说 某某框架采用了MVC的架构。

    设计模式(Design Pattern),简单的说就是由经验形成的对某种问题的经典解决方案。类似于数学里的公式,或者是武术里的套路。设计模式本身是没有尺度限制的,设计模式可以是解决小问题的模式,也可以是解决宏观问题的模式。就像上面的 Layer和MVC 都应当属于设计模式。

  • 转载自: ning的个人空间

    -------------------------------------------

    eCos 3.0 大约会在08年9月底发布。支持下面的新特性:
    * 最新的toolchain
    * 支持Flash v2架构和驱动样例
    * 支持Microsoft Vista的主机工具

    详细请看下面的讨论组邮件:

    从: John Dallaway <jld@ecoscentric.com>
    日期: 星期五, 七月 18日, 2008 下午6:52
    主题: [ECOS] eCos 3.0 planning

    eCos community

    The assignment of copyright in the eCos public sources to the FSF is now
    complete and it is time to push forward with our plans for a new release
    of eCos. A new release at this time is important for a number of reasons:

    a) It allows us to draw a line which marks eCos under FSF copyright
    ownership. For this reason alone, we will be bumping the major version
    number and make this an "eCos 3.0" release.

    b) Although most members of the eCos community work from our CVS
    repository, a new release will generate publicity for the project and
    send a clear message to technology decision makers.

    c) A new release will provide a better experience for users downloading
    and installing eCos for the first time.

    We plan to incorporate the following new features into the eCos 3.0 release:

    * Support for more recent GNU toolchain components
    * Flash v2 infrastructure and example drivers (supporting multiple flash
       parts on a single hardware platform)
    * Support for Microsoft Vista within the eCos host tools

    Of course, the new release will also incorporate the numerous
    enhancements, bug fixes and platform support packages that have been
    contributed since the last release.

    As with previous releases, we will aim for verification of eCos
    architectural support by testing on a representative set of hardware
    platforms to which the maintainers have access. Offers of assistance in
    testing on some of the less common architectures would be gratefully
    received. Please contact the eCos maintainers at:

       ecos-maintainers@ecos.sourceware.org

    We will be aiming to release eCos 3.0 towards the end of September 2008
    and will therefore branching in August. If you have any local patches
    that you would like to see incorporated into the new release, please
    submit them for review as soon as possible, allowing time for the
    copyright assignment process.

    John Dallaway
    eCos 3.0 release manager

    --
    Before posting, please read the FAQ: http://ecos.sourceware.org/fom/ecos
    and search the list archive: http://ecos.sourceware.org/ml/ecos-discuss

    --------

    ps: 最近有一些事情,所以没有更新,忙完之后将继续更新本博客。

     

  •  

    关于链接和加载(link and load)我们一定不会陌生,不过由于它被编译过程隐含的执行,所以通常被大家忽略。但是对于嵌入式系统来说,可能需要更多关注它,它涉及到你的程序的运行空间的分配。当你拿到一块新板子,一定会先了解它有多少内存,多少flash,编址情况,执行的起始地址等等。

    特别是需要把一个boot loader运行在上面的时候,就需要仔细地根据硬件情况进行处理。当然,这只是链接加载器的一部分功能而已。

    对于,Redboot 使用GNU ld进行连接部分的工作。
    ld 进行连接时,会根据一个script,将输入文件进行连接生成输出文件(比如 elf格式的二进制可执行文件,或者其他的bin文件)在linux下,只需要打

            ld -verbose

    就可以看到缺省的链接脚本。

    ld的script等于是另一种语言,就像makefile对于make一样。


    如果希望仔细了解链接和加载 可以参考
    gnu ld document中文)和 这本书

     

    话说回来,我们看看redboot的链接script如何生成的。


    在目录下 packages/hal/i386/pc/v2_0/include/pkgconf 有三类文件 .mlt  .ldi  .h

    mlt 是 memory layout tool 的缩写
    ldi 是链接脚本文件 (linker script)
    .h文件中包括一些内存宏定义

    如果需要手动修改内存,只需要修改 .ldi 和 .h文件。

    在ldi文件内容如下:

    MEMORY
    {
        ram : ORIGIN = 0, LENGTH = 0xa0000
    }

    SECTIONS
    {
        SECTIONS_BEGIN
        SECTION_vectors (ram, 0x3000, LMA_EQ_VMA)
        SECTION_text (ram, ALIGN (0x4), LMA_EQ_VMA)
        SECTION_fini (ram, ALIGN (0x4), LMA_EQ_VMA)
        SECTION_rodata1 (ram, ALIGN (0x8), LMA_EQ_VMA)
        SECTION_rodata (ram, ALIGN (0x8), LMA_EQ_VMA)
        SECTION_fixup (ram, ALIGN (0x4), LMA_EQ_VMA)
        SECTION_gcc_except_table (ram, ALIGN (0x1), LMA_EQ_VMA)
        SECTION_rel__got (ram, ALIGN (0x1), LMA_EQ_VMA)
        SECTION_data (ram, ALIGN (0x8), LMA_EQ_VMA)
        SECTION_sbss (ram, ALIGN (0x4), LMA_EQ_VMA)
        SECTION_bss (ram, ALIGN (0x10), LMA_EQ_VMA)
        CYG_LABEL_DEFN(__pci_window) = ALIGN(0x10); . = CYG_LABEL_DEFN(__pci_window) + 0x64000;
        CYG_LABEL_DEFN(__heap1) = ALIGN (0x8);
        SECTIONS_END
    }

    在文件中有很多宏 如: SECTIONS_BEGIN, SECTION_vectors 等等
    这些宏在文件 packages/hal/i386/arch/v2_0/src/i386.ld中定义。

    最后生成 链接脚本文件在 install/lib/target.ld

  • 当redboot启动完毕后,进入cyg_start 函数,在这个函数里进行了初始化设备的工作:

    for (init_entry = __RedBoot_INIT_TAB__; init_entry != &__RedBoot_INIT_TAB_END__;  init_entry++)
    {
     (*init_entry->fun)();
    }


    __RedBoot_INIT_TAB__ 为初始化表,初始化表是一个 struct init_tab_entry 的数组。
    而 struct init_tab_entry 只有一个函数 fun。

    而这一段初始化过程就是执行初始化表中的每一个fun函数。
    相关定义如下:

    extern struct init_tab_entry __RedBoot_INIT_TAB__[], __RedBoot_INIT_TAB_END__;

    struct init_tab_entry {
        void_fun_ptr fun;
    } CYG_HAL_TABLE_TYPE;


    如果希望在这个初始化过程中,执行某函数需要加入如下声明:

    RedBoot_init(net_init, RedBoot_INIT_LAST);

    net_init 是网络设备的初始化函数, RedBoot_INIT_LAST是表示放在 __RedBoot_INIT_TAB__ 表的尾部。

    -------------------------------------------------------------------

    大致的实现原理在下面简单解释 一下

    首先,_RedBoot_INIT_TAB__ 和 __RedBoot_INIT_TAB_END__ 有如下宏定义

     

    CYG_HAL_TABLE_BEGIN( __RedBoot_INIT_TAB__, RedBoot_inits );
    CYG_HAL_TABLE_END( __RedBoot_INIT_TAB_END__, RedBoot_inits );

     

    #define CYG_HAL_TABLE_BEGIN( _label, _name )                                 \
    __asm__(".section \".ecos.table." __xstring(_name) ".begin\",\"aw\"\n"       \
        ".globl " __xstring(CYG_LABEL_DEFN(_label)) "\n"                         \
        ".type    " __xstring(CYG_LABEL_DEFN(_label)) ",object\n"                \
        ".p2align " __xstring(CYGARC_P2ALIGNMENT) "\n"                           \
    __xstring(CYG_LABEL_DEFN(_label)) ":\n"                                      \
        ".previous\n"                                                            \
           )

    #define CYG_HAL_TABLE_END( _label, _name )                                   \
    __asm__(".section \".ecos.table." __xstring(_name) ".finish\",\"aw\"\n"      \
        ".globl " __xstring(CYG_LABEL_DEFN(_label)) "\n"                         \
        ".type    " __xstring(CYG_LABEL_DEFN(_label)) ",object\n"                \
        ".p2align " __xstring(CYGARC_P2ALIGNMENT) "\n"                           \
    __xstring(CYG_LABEL_DEFN(_label)) ":\n"                                      \
        ".previous\n" 

     

    解析后大致为:

    .section ".ecos.table.RedBoot_inits.begin","aw"
        .globl  __RedBoot_INIT_TAB__
        .type     __RedBoot_INIT_TAB__ ,object
        .p2align  5
    __RedBoot_INIT_TAB__ :
        .previous
      
    .section ".ecos.table.RedBoot_inits.finish","aw"
        .globl  __RedBoot_INIT_TAB_END__
        .type     __RedBoot_INIT_TAB_END__ ,object
        .p2align  5
    __RedBoot_INIT_TAB__ :
        .previous

     

    另外,对于 RedBoot_init(net_init, RedBoot_INIT_LAST); 宏定义分别如下:

     

     #define RedBoot_init(_f_,_p_) _RedBoot_init(_f_,_p_)

    #define _RedBoot_init(_f_,_p_)                                          \
    struct init_tab_entry _init_tab_##_p_##_f_                              \
      CYG_HAL_TABLE_QUALIFIED_ENTRY(RedBoot_inits,_p_##_f_) = { _f_ };


    #define CYG_HAL_TABLE_QUALIFIED_ENTRY( _name, _qual ) \
            CYGBLD_ATTRIB_SECTION(".ecos.table." __xstring(_name) ".data." \
                                  __xstring(_qual))

    #define RedBoot_INIT_FIRST 0000
    #define RedBoot_INIT_LAST  9999

     #define CYGBLD_ATTRIB_SECTION(__sect__) __attribute__((section (__sect__)))

     

    解析后大致为:

    struct init_tab_entry _init_tab_RedBoot_9999net_init
    __attribute__((section (.ecos.table.RedBoot_inits.data.9999net_init)))  =    { net_init };
     

    -------------------------------

     

    这种表结构在eCos的其他部分也经常使用,所以了解一下,很有必要的。

     

     

     

     

     

     

     

     

     

     

  • 如何读源码 

    有个朋友看了我的博客,发信问我如何读源码。说实话,我在读源码的过程中也并不顺利。
    最初,我希望能好好读读linux的源码,可惜的是linux太庞大了,虽然学了不少时间,但是觉得还是前路遥遥。有时也感觉庞大的代码库有些无处下手,才选择了eCos。eCos体积非常小,感觉读起来轻松很多,有了linux的一些学习基础,对理解ecos也很有用处。

    现在已经进入开源时代,有很多开源的项目,大量的代码需要读。如何读源代码可能是很多人面临的问题吧。我想,根据自己的经验教训,总结一些想法,希望对别人,对自己都能有所启发。

     

    1 耐心和坚持
      读代码很枯燥,所以需要耐心和坚持。其实,学什么都一样吧。

    2 实践
      很多时候多跑一跑能够更清楚的得到结果,而且有的时候光读,理解不一定正确,必须跑起来才心里有底。  说这个也是因为我自己比较懒得动手,所以吃了不少亏。

    3 分治
      即分而治之。遇到一个大问题总是会有望洋兴叹之感。可是,如果能把一个大问题划分诸多小问题,马上会感觉目标清晰很多。 当每解决一个小问题,也能知道自己离最后的目标有多远。

    4 多方面入手
      前面提到分治,但是在读源码时,如何将诸多代码进行划分呢?
      依据我的经验,提供以下3个思路:
      一、根据运行流程,程序就是一个执行流程,根据执行流程将程序划分出若干运行阶段,然后再逐步求精。
      比如,我前面的系列文章:《Redboot(i386)启动流程》就是完全按照执行流程来分析的。
      根据运行流程读代码的时候,就要尽量打开项目本身的DEBUG信息,或者自己添加一些代码测试,确认运行状况。

      二、根据项目组织结构和编译过程。项目本身会划分诸多模块,通常根据makefile可以大致了解项目的组织情况。
      另外,了解项目编译过程对理解项目也是非常有用的。

      三、从一些主题入手,比如对于ecos,可以提出很多主题,比如:跟踪收发包过程去了解其网络处理流程,跟踪命令执行过程,跟踪中断处理过程等等。
      从不同侧面了解你所读的代码,会让你对整个项目有更清晰的认识。

      读代码的时候,经常会碰到读到没有路的感觉,或者不想读下去了。这时候,换个思路,换个目标,也许就能再继续下去。有的时候,分兵出击,会起到意想不到的效果。


    5 记录
      当一段代码读完,尽量记下来自己的理解,否则回头再看,又没有印象了。人们有时候喜欢太高估自己的记忆力,我以前特别不喜欢记,后来不得不记了。可是,有时候,时间一长,甚至自己记得东西都不太看得懂了,呵呵。

    6 学习相关知识
      对编译工具 make(makefile), gcc要了解,对嵌入式软件还需要对cpu有了解。另外了解链接和程序加载过程(ld,loader and linker)也是很有帮助的。
      开源项目的编译和环境搭建大多在linux下,所以对linux,shell等也都要了解。
      这些东西内容不少,要慢慢来,还是要坚持和耐心,多动手。

    7 借鉴别人的思路,多参考书
      学linux有不少书,比如:《linux 0.11源代码完全中文注释》《LINUX设备驱动程序》都很不错,
      学ecos,现在看到最好的就是:《EMBEDDED SOFTWARE DEVELOPMENT WITH ECOS》
      虽然,有几本中文书感觉并不是很好,不过还是可以参考一下。
      网上也有不少关于ecos/redboot的小文章,也挺不错的。

    8 目标明确,急用先学
      有明确目标的学习通常效果最好,很多时候不一定有时间完完全全把一个项目了解清楚才能用。
      项目来了,找到明确目标,就去学,分清长线和短线。学以致用。

    大概想了这么些。很多事情刚开始起步都很难,不过慢慢做就会越来越熟练,越来越有经验技巧。
    所以,我才认为耐心和坚持最重要 ^_^

  • Redboot启动完成后,就进入命令行状态(CLI),接受命令并执行,如果你用Redboot作为boot-loader就一定需要熟悉相关的命令,命令的相关解释在帮助文档上面都解释的很详细,今天突然看到这里有一个中文的版本,作为一个速查手册感觉不错,就转载过来。

                Redboot 常用命令说明
              

    1 cache
    使用格式:cache [on | off]
    功能描述:cache命令用于管理微处理器的cache。在传输大容量的文件时,最好是把cache打开。
    Redboot>cache      //显示系统当前cache状态
    Redboot>cache on   //打开cache
    Redboot>cache off  //关闭cache
     
    2 channel
    使用格式:channel [-l | channel number]
    功能描述:如果不带任何参数,channel命令会显示当前的控制台通道号;如果参数为-1,则将控制台通道切换到默认的控制台通道;若参数为硬件平台所支持的其他控制台号,则channel命令就对控制台作相应的切换。
     
    3 dump
    使用格式:dump [-b location] [-l length] [-s] [-1 | -2 | -4]
    功能描述: 显示参数指定区域的数据,显示方式由参数指定。
    -b  存储器的起始位置
    -l  显示的长度
    -s  使用Motorala S-reconds格式显示数据
    -1  按单字节显示数据
    -2  按双字节显示数据
    -4  按四字节显示数据
     
    4 exec
    使用格式:exec [-w timeout] [-r ramdisk_address] [-s ramdisk_length] [-b load_address] [-l load_length] [-c kernel_command_line] [entry_point]
    功能描述:执行一个映象文件,如引导Linux内核
    -w  执行映象文件之前的等待时间
    -r  传递给内核的ramdisk_address起始地址
    -s  传递给内核的ramdisk_address长度
    -b  内核映象文件地址
    -l  内核映象文件长度
    -c  传递给内核的命令行
     
    5 fis creat
    使用格式:fis creat [-b data_address] [-l length] [-f flash_address] [-e entry] [-r relocation_address] [-s data_length] [-n] [name]
    功能描述:在FIS(Flash Image System)目录中创建一个映象,将当前RAM中的数据写入FLASH存储器中。因此,在使用该命令之前,映象文件数据必须已经保存在RAM中。
    -b  待写入flash数据的存放地址
    -f  flash地址
    -e  可执行映象地址
    -r  执行fis load命令时,可执行映象的重定位地址
    -s  写入flash中的可执行映象的实际长度
    -n  用于更新FIS目录
    name 创建映象的名称
     
    6 fis init
    使用格式:fis init [-f]
    功能描述:初始化FIS目录,-f表示将所有的flash空间初始化
     
    7 fis list
    使用格式:fis list [-c] [-d]
    功能描述:显示FIS中当前的所有映象文件
    -c  显示映象的校验和
    -d  显示映象的长度
     
    8 fis free
    使用格式:fis free
    功能描述:显示flash当前的空闲空间
     
    9 fis delete
    使用格式:fis delete [name]
    功能描述:删除FIS目录中的映象。name为需要删除映象的名称。
    举例:
    Redboot>fis delete ramdisk.gz
     
    10 fis lock
    使用格式:fis lock [-f flash_address] [-l length]
    功能描述:锁定flash空间
    -f  锁定flash空间的起始地址
     
    11 fis unlock
    使用格式:fis unlock [-f flash_address] [-l length]
    功能描述:解除flash空间的锁定
     
    12 fis erase
    使用格式:fis erase [-f flash_address] [-l length]
    功能描述:擦除指定的flash空间
     
    13 fis write
    使用格式:fis write [-b mem_address] [-l length] [-f flash_address]
    功能描述:将数据由RAM写入FLASH中
    -b 待写数据在RAM中的起始地址
    -f 写入Flash的起始地址
     
    14 fconfig
    使用格式:fconfig
    功能描述:对已保存在flash中的配置选项进行管理和重配置。
     
    15 go
    使用格式:go [-w timeout] [start_address]
    功能描述:执行放在某一位置的可执行代码
    -w  执行代码前的等待时间
    start_address  可执行代码的起始地址
     
    16 ip_address
    使用格式:ip_address [-l local_ip_address] [-h server_ip_address] [-d DNS_server_ip_address]
    功能描述:设置或改变系统使用的IP地址
     
    17 load
    使用格式:load [-r] [-v] [-h host] [-m varies] [-c channel_number] [-b base_address] [file_name]
    功能描述:下载数据到目标系统RAM中
    -r  下载未处理的数据到RAM
    -v  下载过程显示进度
    -b  数据下载到RAM的地址
    file_name  下载的文件名
     
    18 mcmp
    使用格式:mcmp [-s location] [-d location] [-l length] [-1 | -2 | -4]
    功能描述:比较两个存储区域的内容
    -s  源区域起始地址
    -d  目的区域起始地址
    -l  需要比较数据的长度
    -1  单字节读取
    -2  双字节读取
    -4  四字节读取
     
    19 mcopy
    使用格式同mcmp,功能就是将数据从一个存储区域复制到另一个存储区域
     
    20 mfill
    使用格式:mfill [-b location] [-l length] [-p value] [-1 | -2 | -4]
    功能描述:将给定的数值填充到指定的存储区域
     
    21 reset
    功能描述:复位系统
     
    22 ping
    使用格式:ping [-v] [-n count] [-l length] [-t timeout] [-r rate] [-i IP_addr] [-h IP_addr]
    功能描述:向指定主机发送ICMP报文,用于检查网络是否正常。
    -v  显示数据包信息
    -n  发送数据包的数目
    -l  发送报文的长度
    -t  设置超时时间
    -r  发送数据包的间隔时间
    -i  本机IP地址
    -h  远端主机IP地址
     
    23 help
     
    24 version

  • 上篇继续

    进入保护模式之后做以下工作:

    •  设置中断栈空间,在之前栈定义在小于0x3000的内存空间中。通过
       movl $__interrupt_stack, %esp
       将栈重新设置在__interrupt_stack处,并分配了4096kB的栈空间。
       栈大小也可以通过改变cdl文件进行调整。

     

    • hal_idt_init(packages/hal/i386/pc/v2_0/include/platform.inc) 
        设置idt向量, 回头会在介绍中断的文章中介绍这部分。
    • BSS段初始化(清零)
      解释: BSS(Block Started by Symbol)用来存放程序中未初始化的全局变量的一块內存区域,属于静态內存分配。

     

    • hal_platform_init(packages/hal/i386/pc/v2_0/src/plf_misc.c)
        这部分是内容比较多,也比较重要。主要为 初始化 中断,异常 以及虚拟向量表。
        初始化虚拟向量表的工作通过调用函数 hal_if_init 来完成
      在eCos中设置了3张向量表,idt, hal_vsr_table, hal_virtual_vector_table
      内存地址如下:
      idtStart = 0x00001000;
      hal_vsr_table = 0x00001800;
      hal_virtual_vector_table = 0x00001c00;

    idt 是硬件实际设置的中断区域,当中断发生后进入调用idt的中断向量。而eCos将idt的中断向量有指向 hal_vsr_table
    (猜测是为了隔离不同的cpu中断机制不同,专门设置了hal_vsr_table,使其中断处理方式一致)

    在 hal_platform_init 函数中,初始化所有中断(和异常),设定为缺省的中断处理例程,以防止有中断意外发生,造成程序进入未知状态。

    有关中断,异常,虚拟向量等相关处理机制内容比较多,会在以后的文章具体介绍。

    • cyg_hal_invoke_constructors

    这个函数主要执行相关的构造函数,因为ecos一部分代码是用c++写的,所以有些类具有构造函数,需要在首先执行,这里面做这部分的处理。

     

    • cyg_start
      最后则进入主函数 cyg_start, 这个函数中,首先执行外围设备的初始化,之后就开始轮询处理用户输入的命令。

    以上就是Redboot启动的全过程。由于篇幅有限,对很多部分没有深入讨论。在之后计划对以下部分进行讨论:

    1 eCos设备驱动结构和编写

    2 PCI设备的初始化过程

    3 中断和异常处理过程

    4 虚拟向量(hal_virtual_vector_table)介绍

    5 Redboot 命令介绍

    等等……

  • eCos, uC/OS, uClinux 同为嵌入式操作系统,也是当前比较流行的选择,他们之间究竟有何不同,如何选择?

    最近刚好连续看到两篇文章,转贴在下面供参考。

    我当然是倾向eCos喽

    eCos vs. uClinux: Which is best for your embedded target?
    Rob Wehrli (March 11, 2002)

    Do you need or want a freely available, royalty free embeddable operating system for your next project? Is support important to you? Is wide-spread developer experience important to you? Are you looking for something that you can productize or just use to become familiar with the embedded world? These and many more questions are typically asked by designers and business people interested in free software.

    Both eCos and uClinux offer freely available, royalty free downloads from public cvs sites. Neither offers an exhaustive range of existing board support packages, though, curiously enough, both seem to offer about the same type and number of BSPs. Adding support for a new board is relatively easily accomplished with uClinux--given prior Linux kernel development experience and familiarity with the tools and assuming that the GNU Compiler Collection already supports your target microprocessor. Adding support for a new board under eCos, from the perspective of this Linux person, is a bit more of a learning curve, but not difficult once the process is underway. Again, GCC support for your micro is required.

    Do you need professional support or are you willing to staff knowledgeable personnel capable of solving the problems you're likely to encounter? Red Hat offers cost-based support services for eCos. Lineo also offers similar support for their supported uClinux code. Other consultants and service providers are available, though perhaps are not as easily found. Neither of these services tend to be overtly inexpensive and there is a tendency to feel as if you're helping to pay for the first time something is done so that everyone else can benefit. Of course, that's how free software got to be free for others, someone (or many someones) made an initial investment that helped serve the needs of the larger community...and we all tend to help out a bit from time-to-time by contributing what we can back into the community. Mailing lists are probably the best support available, especially for smaller project budgets. They tend to offer the quickest and most reliable responses, too!

    Many, many more developers have experience using and developing for Linux (the uClinux kernel is simply Linux for MMU-less computers) than eCos, probably as a result of the tremendous popularity of Linux. It is my opinion that nearly any developer familiar with Linux kernel hacking will be able to "get along" under an eCos world with a required minimum learning curve.

    If you're looking for something that you can ship in your next embedded product, you may want to more strongly consider eCos as your first, open source OS choice. eCos was designed from the beginning to be an embedded OS. It has distinctly embedded features are better suited to putting in an embedded product--ready to fly off the shelves--than uClinux is currently considered today. It helps to think that uClinux is Linux "lite." You can build a minimalist uClinux product in about 600KBytes, which is certainly not "lite" by truly embedded standards. eCos allows for a shipping system in perhaps as little as 60KBytes, obviously depending on your system requirements. uClinux has no notably "real time" features, though extensions are likely "in the works." eCos already has real time extensions.

    Along those lines, the development tools packaged with eCos are more finely "tuned" for the needs of system designers and developers than currently available uClinux tools that I've seen. While that doesn't prohibit development, in fact, it enables you to select any tool that does the job, it doesn't offer a composite package ready-to-ship that many companies seek when considering ramping up on a new project. Naturally, pre-packaged tools come at an additional project budget impact, however, such might be seen as a risk-mitigating factor in the eyes of many line and configuration managers. Lineo, probably the forerunner of uClinux support and services, is undoubtedly producing similar tools for uClinux. As a note, the wave of people rushing to embrace a platform seems to be heading in the direction of uClinux, which may be an important consideration as we all keep an eye on what the future offers.

    If you're looking for something to familiarize yourself with the (seemingly) recently-blooming embedded systems world, you may be happy to consider uClinux as an excellent starting point. While you're welcomed by Red Hat to download eCos, you're not as likely to find as much free and active developer support as you would from the Linux/uClinux community. Most Linux mailing lists I've seen are relatively eager to help out newbies and many developers offer free help in jumpstarting projects once hardware is available for businesses considering exploring for an existing or new board. Either eCos or uClinux offer plenty of insight into the complete worlds of embedded systems in each board support package made freely available.

    I encourage everyone to download either or both and start hacking today!

    Take Care.

    Rob!  

     

    UCOS和uClinux的比较

    (出处不详)

    摘要:嵌入式操作系统是嵌入式系统应用的核心软件。本文通过对两种典型的开源嵌入式操作系统的对比,分析和总结嵌入式操作系统应用中的若干问题,归纳嵌入式操作系统的选型依据。

        关键词:嵌入式系统 操作系统 uC/OS uClinux

    引言

    随着现代计算机技术的飞速发展和互联网技术的广泛应用,从PC时代过渡到了以个人数字助理、手持个人电脑和信息家电为代表的3C(计算机、通信、消费电子)一体的后PC时代。后PC时代里,嵌入式系统扮演了越来越重要的角色,被广泛应用于信息电器、移动设备、网络设备和工控仿真等领域。

    嵌入式系统是以嵌入式计算机为核心,面向用户、面向产品、面向应用,软硬件可裁减的,适用于对功能、可靠性、体积、成本、功耗等综合性能有严格要求的计算机系统。随着嵌入式系统的广泛应用,传统的前/后台程序开发机制已经不能满足日益复杂和荷记得的实现要求,因而现场常常采用嵌入式产时操作系统PROS(Real Time Operation System)开发实时多任务系统。嵌入式实时操作系统一般可以提供多任务的任务调度、时间管理、任务间通信和同步以及内存管理MMU(Memory Manager Unit)等重要服务,使得嵌入式应用程序易于设计和扩展。采用RTOS可以使嵌入式产品更可靠、开发周期更短。在嵌入式应用中使用RTOS已经成为当前嵌入式应用的一个热点。

    完成简单功能的嵌入式系统一般不需要操作系统。如,以前许多MCS51系列单片机组成的小系统就只是利用软件实现简单的控制环路;但是随着所谓后PC时代的来临,嵌入式系统设计日趋复杂,嵌入式操作系统就必不可少了。

    嵌入式RTOS在系统实时高效性、硬件的相关依赖性、软件固化以及应用的专业性等方面具有较为突出的优势。一般而言,嵌入式操作系统不同于一般意义的计算机操作系统,它有占用空间小、执行效率高、方便进行个性化定制和软件要求固化存储等特点。

    从20世纪80年代起,国际上就有一些IT组织、公司,开始进行商用嵌入式操作系统和专用操作系统的研发。这其中涌现了一些著名的嵌入式操作系统,如Microsoft公司的WinCE和WindRiver System公司的VxWorks就分别是非实时和实时嵌入式操作系统的代表。但是商用产品的造价都十分昂贵,用于一般用途会提高产品成本从而失去竞争力。


    UC/OS和uClinux操作系统是用两种性能优良、源码公开且被广泛应用的免费嵌入式操作系统,可以作为研究实时操作系统和非实时操作系统的典范。本文通过uC/OS和uClinux的对比,分析和总结嵌入式操作系统应用中的若干重要问题,归纳嵌入式系统开发中操作系统的选型依据。

    1 两种开源嵌入式操作系统介绍

    uC/OS和uClinux操作系统,是当前得到广泛应用的两种免费且公开源码的嵌入式操作系统。UC/OS适合小型控制系统,具有执行效率高、占用空间小、实时性能优良和可扩展性强等特点,最小内核可编译至2KB。UClinux则是继承标准Linux的优良特性,针对嵌入式处理器的特点设计的一种操作系统,具有内嵌网络协议、支持多种文件系统,开发者可利用标准Linux先验知识等优势。其编译后目标文件可控制在几百KB量级。

    UC/OS是一种免费公开源代码、结构小巧、具有可剥夺实时内核的实时操作系统。其内核提供任务调度与管理、时间管理、任务间同步与通信、内存管理和中断服务等功能。

    UClinux是一种优秀的嵌入式Linux版本。uClinux是Micro-Conrol-Linux的缩写。同标准Linux相比,它集成了标准Linux操作系统的稳定性、强大网络功能和出色的文件系统等主要优点。但是由于没有MMU(内存管理单元),其多任务的实现需要一定技巧。

    2 两种嵌入式操作系统主要性能比较

    嵌入式操作系统是嵌入式系统软硬件资源的控制中心,它以尽量合理的有效方法组织多个用户共享嵌入式系统的各种资源。其中用户指的是系统程序之上的所有软件。所谓合理有效的方法,指的就是操作系统如何协调并充分利用硬件资源来实现多任务。复杂的操作系统都支持文件系统,方便组织文件并易于对其规范化操作。

    嵌入式操作系统还有一个特点是,针对不同的平台,系统不是直接可用的,一般需要经过针对专门平台的移植操作系统才能正常工作。

    进程调度、文件系统支持和系统移植是在嵌入式操作系统实际应用中最常见的问题。下文就从这几个角度入手对uC/OS和uClinux进行分析比较。

    2.1 进程调度

    任务调度主要是协调任务对计算机系统资源(如内存、I/O设备、CPU)的争夺使用。进程调度又称为CPU调度,其根本任务是按照某种原理为处于就绪状态的进程分析CPU。由于嵌入式系统中内存和I/O设备一般都和CPU同时归属于某进程,所以任务调度和进程调度概念相近,很多场合不加区分。


    进程调度可分为“剥夺型调度”和“非剥夺型调度”两种基本方式。所谓“非剥夺型调度”是指:一旦某个进程被调度执行,则该进程一直执行下去直至该进程结束,或由于某种原理自行放弃CPU进入等待状态,才将CPU重新分配给其它进程。所谓“剥夺型调度”是指:一旦就绪状态中出现优先权更高的进程,或者运行的进程已用满了规定的时间片时,便立即剥夺当前进程的运行(将其放回就绪状态),把CPU分配给其它进程。

    作为实时操作系统,uC/OS采用的是可剥夺型实时多任务内核。可剥夺型的实时内核在任何时候都运行就绪了的最高优先级的任务。uC/OS中最多可以支持64个任务,分别对应优先级0~63,其中0为最高优先级。调度工作的内容可以分为两部分:最高优先级任务的寻找和任务切换。

    其最高优先级任务表来实现的。UC/OS中的每一个任务都有独立的堆栈空间,并有一个称为任务控制块TCB(Task Control Block)的数据结构,其中第一个成员变量就是保存的任务堆栈指针。任务调度模块首先用变量OSTCBHighRdy记录当前最高级就绪任务的TCB地址,然后调用OS_TASK_SW()函数来进行任务切换。

    UClinux的进程调度沿用了Linux的传统。系统每隔一定时间挂起进程,同时产生快速和周期性的时钟性时中断,并通过调度函数(定时器处理函数)决定进程什么时候拥有它的时间片,然后进行相关进程切换。这是通过父进程调用fork函数生成子进程来实现的。

    UClinux系统fork调用完成后,要么子进程代替父进程执行(此时父进程已经休眠),直到子进程调用exit退出;要么调用exec执行一个新的进程,这时产生可执行文件的加载,即使这个进程只是父进程的拷贝,这个过程也不可避免。当子进程执行exit或exec后,子进程使用wakeup把父进程唤醒,使父进程继续往下执行。

    uClinux由于没有MMU管理存储器,其对内存的访问是直接的,所有程序中访问的地址都是实际的物理地址。操作系统对内存空间没有保护,各个进程实际上共享一个运行空间。这就需要实现多进程时进行数据保护,也导致了用户程序使用的空间可能占用到系统内核空间。这些问题在编程时都需要多加注意,否则容易导致系统崩溃。

    由上述分析可以得知,uC/OS内核是针对实时系统的要求设计实现的,相对简单,可以满足较高的实时性要求;而uClinux则在结构上继承了标准Linux的多任务实现方式,仅针对嵌入式处理器特点进行改良。其要实现实时性效果则需要使系统在实时内核的控制下运行。RT-Linux就是可以实现这一功能的一种实时内核。

    2.2 文件系统

    所谓文件系统是反映负责存取和管理文件信息的机构,也可以说是负责文件的建立、撤销、组织、读写、修改、复制及对文件管理所需要的资源(如目录表、存储介质等)实施管理的软件部分。

    uC/OS是面向中小型嵌入式系统的。如果包含全部功能(信号量、消息邮箱、消息队列及相关函数),编译后的uC/OS内核仅有6~10KB,所以系统本身并没有对文件系统的支持。但是uC/OS具有良好的扩展性能,如果需要的话也可自行加入文件系统的内容。

    uClinux则是继承了Linux完善的文件系统性能。其采用的是romfs文件系统。这种文件系统相对于一般的ext2文件系统要求更少的空间。空间的节约来自于两个方面:首先,内核支持romfs文件系统比支持ext2文件系统需要更少的代码;其次,romfs文件系统相对简单,在建立文件系统超级块(superblock)需要更少的存储空间。Romfs文件系统不支持动态擦写保存,对于系统需要动态保存的数据采用虚拟ram盘的方法进行处理(ram盘将采用ext2文件系统)。

    uClinux还继承了Linux网络操作系统的优势,可以很方便地支持网络文件系统且内嵌TCP/IP协议。这为uClinux开发网络接入设备提供了便利。

    由两种操作系统对文件系统的支持可知:在复杂的需要较多文件处理的嵌入式系统中,uClinux是一个不错的选择;而uC/OS则主要适合一些控制系统。

    2.3 操作系统的移植

    嵌入式操作系统移植的目的是指使操作系统能在某个微处理器或微控制器上运行。UC/OS和uClinux都是源码公开的操作系统,且其结构化设计便于把与处理器相关的部分分离出来,所以被移植到新的处理器上是可能的。以下对两种系统的移植分别予以说明。

    (1)uC/OS的移植

    要移植uC/OS,目标处理器必须满足以下要求:

    *处理器的C编译器能产生可重入代码,且用C语言就可以打开和关闭中断;

    *处理器支持中断,并能产生定时中断;

    *处理器支持足够的RAM(几KB),作为多任务环境下的任务堆栈;

    *处理器有将堆栈指针和其它CPU寄存器读出和存储到堆栈或内存中的指令。


    在理解了处理器和C编译器的技术细节后,uC/OS的移植只需要修改与处理器相关的代码就可以了。具体有如下内容:

    *OS_CPU.H中需要设置一个常量来标识堆栈增长方向;

    *OS_CPU.H中需要声明几个用于开关中断和任务切换的宏;

    *OS_CPU.H中需要针对具体处理器的字长重新定义一系列数据类型;

    *OS_CPU_A.ASM需要改写4个汇编语言的函数;

    *OS_CPU_C.C需要用C语言编写6个简单函数;

    *修改主头文件INCLUDE.H,将上面的三个文件和其它的头文件加入。

    (2)uClinux的移植

    其实,uClinux是Linux针对嵌入式系统的一种改良,其结构比较复杂;相对uC/OS,uClinux的移植也复杂得多。一般而言,要移植uClinux,目标处理器除了应满足上述uC/OS应满足的条件外,还需要具有足够容量(几百KB以上)外部ROM和RAM。

    uClinux的移植大致可以分为3个层次。

    *结构层次的移植。如果待移植处理器的结构不同于任何已经支持的处理器结构,则需要修改linux/arch目录下相关处理器结构的文件。虽然uClinux内核代码的大部分是独立于处理器和其体系结构的,但是其最低级的代码也是特定于各个系统的。这主要表现在它们的中断处理上下文、内核映射的维护、任务上下文和初始化过程都是独特的。这些例行程序位于lunux/arch/目录下。由于Linux所支持体系结构的种类繁多,所以对一个新型的体系,其低级例程可以模仿与其相似的体系例程编写。

    *平台层次的移植。如果待移植处理器是某种uClinux已支持体系的处理器,则需要在相关体系结构目录下建立相应目录并编写相应代码。如MC68EZ328就是基于无MMU的m68k内核的。此时的移植需要创建的linux/arch/m68knommu/platform/MC68EZ328目录下,并在其下编写跟踪程序(实现用户程序到内核函数的接口等功能)、中断控制调度程序和向量初始化程序等。

    *极级移植。如果所用处理器已被uClinux支持,就只需要板级移植了。板级移植需要在linux/arch/?platform/中建立一个相应板的目录,再在其中建立相应的启动代码crt0_rom.s或crt0_ram.s和键接描述文档rom.ld或ram.ld就可以了。板级移植还包括驱动程序的编写和环境变量设置等内容。


    结语

    通过对uC/OS和uClinux的比较可以看出,这两种操作系统在应用方面各有优劣。uC/OS占用空间少、执行效率高、实时性能优良,且针对新处理器的移植相对简单。UClinux则占用空间相对较大,实时性能一般,针对新处理器的移植相对复杂。但是,uClinux具有对多种文件系统的支持能力、内嵌了TCP/IP协议,可以借鉴Linux丰富的资源,对一些复杂的应用,uClinux具有相当优势。例如,CISCO公司的2500/3000/4000路由器就是基于uClinux操作系统开发的。总之,操作系统的选择是由嵌入式系统的需求决定的。简单地说就是,小型控制系统可充分利用uC/OS小巧且实时性强的优势;如果开发PDA和互联网连接终端等较和为复杂的系统,则uClinux是不错的选择。

     

     

     

     

  • 上篇继续


    _start之后的第一行是一个宏 hal_cpu_init, 该宏定义在package/hal/i386/pcmb/v2_0/include/pcmb.inc 文件中。   由于从软盘引导,所以 hal_cpu_init分为两部分: 一部分是引导分区部分(以0xAA55结束),另一部分的功能是使CPU进入保护模式。

    解释:PC上电后会进入BIOS, BIOS会检测磁盘(软盘和硬盘),如果软盘(或硬盘)的第一个分区是引导分区(以 0xAA55结束为标志)则将软盘的第一个分区内容加载到内存 0x7c00处,并运行它。

    引导分区被运行之后,将整个redboot逐个分区地加载到0x3000为起始地址的内存空间。

    注意:这个过程,引导分区内容会再次被加载到0x3000处,且马上会跳转到0x3000处执行引导分区的代码。而0x7c00处的内容会在后面被覆盖掉。

    在执行加载工作前,会先执行两个小任务:
       1. 设置栈,栈顶为0x3000
       2. 通过BIOS提供的中断,得到扩展内存和标准内存大小,压入栈中
      

    引导分区代码结束后,程序将进入保护模式。大概次序如下:
      1. 关中断
      2. 初始化GDT和IDT
      3. 进入保护模式
      4. 设置数据段
      5. 重新设置栈段(因为保护模式和实模式,内存编址方式不同)

    GDT主要用两个Selector分别是 0x08 (code) 和 0x10 (data)。

    寻址空间都是从0x00000000 - 0xFFFFFFFF

    特权级(DPL)为 0

    IDT的地址为:0x1000

    这部分比较复杂,这里不在详细说明,如果有时间,会在讨论i386架构的文章里,详细说明。而有关这部分的资料也相当丰富,可以在网上搜寻。

    到这里hal_cpu_init已经执行结束了 ,不过进入保护模式还需要 激活A20地址线 。不激活A20地址线只能访问1M空间内的内存,激活A20才能访问所有内存。这个任务在另外一个宏 hal_memc_init 中实现,它位于文件 packages/hal/i386/arch/v2_0/include/arch.inc

    这样,就完成了加载和进入保护模式的任务。

    待续

     

  • 上篇

    虽然 Redboot的启动过程已经相对简单,它没有使用分页机制,也没有区分内核态和用户态。
    所有的代码都被放在一个段中统一编址。只使用了两个段(selector)一个为code,另一个为data。这个对于redboot的size(大概在100k左右吧)已经足够了。

    如果对于一个操作系统来讲,这是一个相当程度简化的操作系统。即便如此,启动过程也不是一目了然的清晰。所以,这里仍然会忽略掉一些不太必要的内容,比如对SMP,FPU,GDB的支持等等。

    整个启动过程中大概分为以下几个部分:

    1. boot-loader: 这部分的工作就是将代码加载(load)到内存中,对于不同的设备,代码会装在不同的地方,通常PC会放在硬盘里面,某些嵌入式设备会把代码放在ROM(一般是flash)里面,而这里是放在软盘里面。boot-loader就是把软盘的代码load至内存,然后运行它们。

    2. 进入保护模式,对于i386体系架构的cpu上电后缺省时实模式,内存访问空间也只有1M,这只是为了跟最初的8086兼容,所以要进入保护模式。

    3. 初始化中断向量和异常。

    4. 初始化基本的输入输出设备,如显示器,键盘,串行口等等。

    5. 其他还有一些零散的初始化工作。

    从下次开始将按运行次序对启动过程进行介绍。

    待续


  • 现在将通过阅读代码,看看redboot是如何启动的,这是每个系统执行的第一步,也是不可缺少的一步。这部分会分几篇完成,这是第一部分,主要是一个概要介绍。

    由于系统启动跟硬件的紧密关系,所以在不同的硬件平台下,这部分都会有相应不同的处理。

    下面这幅图来自《EMBEDDED SOFTWARE DEVELOPMENT WITH ECOS》书中,介绍了一款PowerPC的设备启动流程。

    而这里,仍将介绍PC下的启动流程,通过阅读代码可以了解其具体的执行步骤,也可以通过修改代码重新编译、运行前面介绍的实验,查看结果。在阅读代码的过程中,最好结合实验,这样能够证实系统是否象我们理解的那样运行。

    在今后,会进一步分析eCos操作系统的其他部分代码结构,比如:调度,驱动等等。
    也会对其他不同体系架构(如 mips arm)下的代码进行分析阅读。

    在Redboot最先执行的文件通常命名为 vectors.S

    因为我们是i386架构下,所以可以在下面目录下找到相应文件:
    package/hal/i386/arch/v2_0/src/vectors.S
     
     
    程序执行的入口是 _start
    _start 处开始执行,至   call cyg_start 完成启动部分。
    在函数 cyg_start 中,将进入 Redboot的执行流程。

    这并不是很长的一段代码,却完成了整个PC的启动和初始化的部分。其中相当部分代码为汇编,所以要阅读这些代码必须首先对i386体系结构有基本的了解。

    待续 

     

  • 2008-05-04

    CPU学习 - [architecture]

    最近,搞搞redboot也对一些不同的cpu体系架构开始有了兴趣。

    每种cpu拿到总是厚厚一大本书,看起来蛮吓人的。

    最近,接触的几种分别是x86, mips 和extensa。

    反正虱子多了不怕咬,书总是一大堆,就慢慢看吧。

    不过,各种看看也觉得没那么可怕了,每种虽然都不一样,
    但是也总有共同之处。慢慢有了点经验。
    每种cpu体系架构大致都有下面几部分:

    1 指令系统,这通常是内容最多的,每种都有很多指令,而且都不同。
    但是,其实实现的功能,就那么些,运算,转移,存储器访问等等。
    通常这部分,用的时候查就行了,看也记不住。

    2 寄存器,这部分内容通常不多,通用寄存器,状态寄存器,程序寄存器等等。
    这部分通常我都看得比较仔细,不过,还好不太花时间。

    3 中断异常,中断异常的处理一般比较复杂,牵涉很多东西,要仔细看,
    结合例子,而且一定要通过实际的程序运行,才能做得心里有底。

    4 内存管理,这部分说起来也有点复杂,不过,通常我们用到的不多。

    5 ABI,主要涉及栈的管理,参数传递等等

    6 可选部分,cache,float等

    虽然内容很多,但是不能像看小说一样从头到尾,手册更多是用于查的。
    简单看看大概,熟悉一下寄存器,指令,就要写点代码编译一下,跑跑看看。
    然后,一点一点去熟悉,如果时间紧,就要集中于任务需要的部分,其他的就先放放。
    光看书,效果比较差,要多跑,多在运行的时候发现问题,才能理解的更正确。
    熟悉差不多了,然后就能用c写的就尽量用c吧,毕竟汇编写起来太累了。

     

  • 前两篇实验讲了如何build一个redboot,从VMWARE启动。

    这次,要解释一些相关配置的过程。

    eCos最重要特点就算是可配置(Configurable )了。所以,现在我们就看看它是怎么配置的。

    一、基本概念

    1 package
    在eCos里,每一组源码的集合成为package,一个package,由一些源代码文件构成,实现一个独立的功能。 还有比package更小的单位,如:component,option,因为它们是更小的单元,对应的已经不是完整的源文件, 而是通过 C语言的宏来选择对应的代码功能和参数。

    比如,一个option:

    cdl_option CYGSEM_REDBOOT_DEFAULT_NO_BOOTP {
         user_value 1
    };

    生成在.h文件,就是

    #define CYGSEM_REDBOOT_DEFAULT_NO_BOOTP 1

    然后代码里面

    #ifdef CYGSEM_REDBOOT_DEFAULT_NO_BOOTP

    …… 

    #endif

    2 ecos.db
    ecos.db位于ecos-2.0/package目录下, 它包含两种元素:
    一种是package,如:
    package CYGPKG_DEVS_ETH_AMD_LANCEPCI {
     alias   { "AMD Lance PCI ethernet driver" lancepci_eth_driver }
     hardware
     directory devs/eth/amd/lancepci
     script  amd_lancepci_eth_drivers.cdl
     description     "Ethernet driver for AMD Lance PCI controller (vmWare)."
    }

    它指示了某一个package所在的路径,和配置文件cdl的名称。在ecos.db里索引了所有的package信息。

    另一种是target,如:
    target pc_vmWare {
            alias  { "i386 PC target (vmWare)" }
     packages        { CYGPKG_HAL_I386
                              CYGPKG_HAL_I386_GENERIC
                              CYGPKG_HAL_I386_PC
                              CYGPKG_HAL_I386_PCMB
         CYGPKG_IO_PCI
                              CYGPKG_IO_SERIAL_GENERIC_16X5X
                       CYGPKG_IO_SERIAL_I386_PC
         CYGPKG_DEVS_ETH_AMD_LANCEPCI
         CYGPKG_DEVS_ETH_I386_PC_LANCEPCI
                              CYGPKG_DEVICES_WALLCLOCK_DALLAS_DS12887
                              CYGPKG_DEVICES_WALLCLOCK_I386_PC
            }
            description "
             The pc_vmWare target provides the packages needed to run eCos binaries
                on a standard i386 PC under wmWare."
    }

    它指示某种目标机,因硬件配置选需要的package。

    3 cdl 文件
    CDL是the component definition language的缩写,每一个package都有相应的cdl文件,在其中有相关的功能选项,缺省配置等等。

    4 template
    template是指为某种目的build而建立的相应模板配置,比如选取一些包,配置一些选项等等。在目录 ecos-2.0/package/templates 下面有一些模板的目录。

    5 ecc 文件
      ecc 是eCos Configuration的缩写,是当前任务的配置文件。

    二、脚本解释
    前面简单介绍了一些基本概念,下面解释一下编译脚本的意义
    export PRJ_PATH=/home/andrew/share/project/redboot
    export ECOS_REPOSITORY=$PRJ_PATH/ecos/packages/
    export PATH=$PATH:$PRJ_PATH/gnutools/i386-elf/bin/:$PRJ_PATH/ecos-2.0/tools/bin
    rm build -rf
    mkdir build
    cd build
    ecosconfig new pc_vmWare redboot
    ecosconfig import ${PRJ_PATH}/ecos/packages/hal/i386/pc/current/misc/redboot_FLOPPY.ecm
    ecosconfig import ${PRJ_PATH}/nobootp.cfg
    ecosconfig tree
    make


    前面几个export,是配置一些基本路径,包括eCos代码所在路径(ECOS_REPOSITORY),
    编译工具所在路径,和配置工具(ecosconfig)所在路径。

    ecosconfig new pc_vmWare redboot
    这句话是通过ecosconfig建立一个新的配置文件ecos.ecc,其中两个重要的参数为target,和template。

    pc_vmWare 是target的取值,指我们的目标机是一个vmWare虚拟的pc,可以在ecos.db中找到 target pc_vmWare 在其中指明了该target的硬件配置,比如串行口,网卡,根据相关硬件,可以找到相应的驱动包。

    redboot 是template的取值,指我们要build一个redboot的启动文件。可以在ecos-2.0/package/templates/redboot/v2_0.ect 文件里,看到模板的内容。

    这条命令后会在build目录下产生一个当前的配置文件,ecos.ecc,在其中可以看到当前的相关的配置。

    之后两条 import 命令,是通过相关文件修改,ecos.ecc的配置。比如,nobootp.cfg内容:
    cdl_option CYGSEM_REDBOOT_DEFAULT_NO_BOOTP {
         user_value 1
    };

    就是将原有的CYGSEM_REDBOOT_DEFAULT_NO_BOOTP值从0改为1,也就是取消BOOTP。

    ecosconfig tree

    就是根据ecos.ecc, 生成相关文件,makefile,以及文件目录等等。

    最后通过make就生产了redboot.bin的文件

    这次就介绍到这里,在下次会分析一下相关源码,bye

  • 2008-04-25

    从U盘启动redboot - [实验]

    最近,又做了个小实验,从U盘引导redboot。

    Redboot的发布代码里面是从软盘启动的,不过现在找带软盘的机器还真挺难,只能在虚拟机上面比划了。总感觉不过瘾,就试着搞到U盘上启动一下。

    挺简单的,主要就是把那段装载代码(在文件 pcmb.inc 里)改一下,原来从软盘启动改成从U盘启动(当然要bios支持了)。
    驱动器号也由软盘的 0改成 80h(USB HDD模式), 另外磁盘的参数也要重新设置一下最主要的就是sectors/track了。软盘是18, U盘比较常用的值是32和63。像heads,cylinders一般来说递增就行了,因为redboot比较小,不会用到最大值。

    实验中间一直不行,我还以为是虚拟机和正常的PC不太兼容。结果查了半天还是sectors/track 的值没有搞对。

    原来一直不喜欢x86的指令系统,感觉搞得太复杂。做了这个简单的实验,感觉兼容性真的是很重要,x86的复杂很大程度上也是因为兼容性的缘故吧。

     一段10年甚至20年前的代码,仍然可以在现在的机器上面跑,从这个意义上说,x86的确也是伟大的。