观's profileTo the world,you may jus...PhotosBlogListsMore Tools Help

Blog


    21/11/2009

    开发杂记

        一忙起来又有两个星期没有更新space了,麻烦的事情真的太多,nandflash分区还没搞定,那篇“下”都不知道什么时候发表了,往后拖拖吧。上个星期将产品的话逻辑整理了下,同时也解决了几个程序的bug,但是发现一个新的问题就是画图的速度比较慢,还是需要想办法解决这个问题。这个星期整理了下电源管理的资料,准备搞定电源管理,不过也很不容易,毕竟没有真正意义上的电源管理芯片,同时要做好功耗还是个难题。这些问题是从老大的原理图开始的,之前没有任何的沟通就直接改了,现在又要求这样低的功耗,真的是没有什么好的办法,尽力而为了!
        昨天又抽空将SD卡的问题解决了,真的不明白为什么要有这样的开发板公司,SD1和SD2竟然不能同时使用,初期为什么就不解决这样的问题,唉!现在修改了硬件之后两个SDIO接口都可以使用了,目前还没有发现什么问题,但是有点担心SD2的读写存在问题,先看看吧。
        最近加了个群,找到了些快速编译的方法,做个记录吧,挺实用的,以前浪费在这个上面太多的时间了。快速编译的前提是先整体的编译一次,当其他情况可以按照以下方法去编译:
    1. 如果更改了driver,以WinCE6.0为例,我们可以在菜单里面选择“build”->“Open Release Directory in Build Window” ,然后在弹出的命令行窗口中,通过dos命令切换到你要编译的driver的目录下面,然后运行“build”就可以了。也可以运行“build -c”会强制把所有的文件都编译一遍。被编译后的driver的dll会被自动拷贝到release目录下面,然后再切换的工程的release目录下面,运行一下“make image”就可以了。
    2. 如果改变了OAL部分的代码,同样用上面介绍的方法,需要注意的是,OAL部分可能包含多个文件夹,如果改变了OAL里面的代码,不要进入OAL里面的文件夹去编译,一定要在OAL这层进行编译,这样OAL部分的lib,dll才会被重新编译并拷贝到release目录下面。
    3. 如果改变了配置文件,比如config.bib,platform.reg文件,那么直接将这些文件拷贝到你的工程目录下面,然后运行一下“make image”就可以了。
    4. 如果改变了eboot部分的代码,那按照步骤1的方法就可以了,可能你都不需要运行“make image”命令,因为可能你只需要eboot.bin或者eboot.nb0。

        以上都是关于快速编译的一些技巧,同时PB的Build OS菜单有一些不同的编译选项,也记录下这些选项的功能吧。

    • Sysgen:不用多说,当你在"Catalog"中添加或删除了新的item的时候就用这个。
    • Build and Sysgen:当你更新了\public目录下的源代码的时候,你就需要用这个了。一般比如在打patch以后,可能就需要进行Build and Sysgen了。
    • Build and Sysgen current BSP: 当你只改变了你的BSP部分的代码,就可以用这个选项。据说,当你改变了\platform目录下的代码,也可以用这个,具体没有试过。

    了解了这些之后大大的节省了编译的时间,本来沉浸在编译的痛苦之中的,这下可以解放出来了。

    06/11/2009

    飞思卡尔MX27+andflsh+wince5.0上的HIVE注册表实现(上)

    今天整了一天的HIVE注册表的实现,发现这个和Flash的分区联系非常紧密。和官方中的说明比起来就显得非常的复杂了,下面来说下如何建立HIVE的具体操作:
    官方的操作说明十分简单,总共分六步:
    1. Add the Hive-based Registry Catalog item to your OS design.
    2. Verify the following registry settings in the Platform.reg file for your OS design.

      [HKEY_LOCAL_MACHINE\init\BootVars]
          "SystemHive"="<your system hive location>"
          "Start DevMgr"=dword:<your value>
      For more information about configuring the registry to support the hive-based registry, see Hive-based Registry Setup.

    3. Set the following registry value in the Platform.reg file for your OS design to determine the default hive to load.

      [HKEY_LOCAL_MACHINE\init\BootVars]
          "DefaultUser"="<username>"

    4. Verify that all registry entries necessary for starting drivers in the first boot phase are wrapped in the comments.

      The following code example shows typical comments. 
      ; HIVE BOOT SECTION
      <your registry settings>
      ; END HIVE BOOT SECTION
      These tags are commands that tell the ROM registry builder to add the entries to the boot hive.

    5. Set the following flag bit on each driver that is loaded during the first boot phase.

      [HKEY_LOCAL_MACHINE\Drivers\...]
          "Flags"=dword:1000

      The flags are a bitmask to OR with any existing settings. This flag tells the Device Manager to load your driver in the first boot phase with the boot registry, and not to load it a second time in the second boot phase with the system registry. It prevents the driver from being started twice.

    6. Set the MountFlags registry value equal to DWORD:2.

      Set this value on the Storage Manager profile of the file system driver for the medium that contains the registry.This indicates that the file system contains the following registry key.  [HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\<ProfileName>\<FileSystemName>]   "MountFlags"=dword:2

    7. Decide whether a registry flushing mechanism is necessary.

      Outstanding registry data will be flushed on a suspend or resume cycle and any time the system goes through a software shutdown.However, data may be lost if power is suddenly removed. If a software shutdown is not an option, you may need to create a thread that periodically flushes data. To ensure that data is not lost, call RegFlushKey. This will flush any unsaved changes in the hive to the persistent file. It will not damage anything to call RegFlushKey when no data has changed; in that case, the file will not be touched. RegFlushKey should be called on both the system hive HKEY_LOCAL_MACHINE and the user hive HKEY_CURRENT_USER.

    以上就是官方建立HIVE注册表的步骤了,可是实际操作远远没有这么简单。

    2009-11-06 10:35 在自己项目的平台上折腾了两天了,今天才恍然大悟,改动了eboot却没有将eboot烧到板子上,真是失策。现在板子又出问题了,真是祸不单行。

    2009-11-06 15:38 问题终于解决,是串口板的问题,换了块串口板就可以烧eboot了。可是又有新的问题出现了,eboot烧了进去之后跑不起来。我的天啊!
    现在问题找到了,浪费了大把的时间。具体问题记录下来:
      1. 通过Advanced ToolKit V1.60串口工具将nandloard和eboot烧入flash上电之后系统无反应。但是烧以前的eboot却可以。
      2. 这样只有烧入以前的eboot之后再通过eboot的选项来更新eboot,PB打开eboot.bin通过usb口下载更新然后出错,错误为:eboot的大小超出了保留的大小,崩溃!回头一查298k,确实是超过了256k的大小,可是为什么会变这么大,仔细一想,自己更新过开机启动的画面,加了一幅图进取,所以整个eboot变大了。
      3. 删除开机启动画面的图片,重新编译,更新eboot失败。再次崩溃!
      4. 重新回到串口更新eboot的程序,重新来一遍,终于ok了,唉!

    通过更改eboot目前可以将整个flash分为三个分区了:part00(binfs),part001(fatfs),part03(boot)但是可惜的是fat分区还是没有挂起,明天来搞挂起吧,希望不要这么曲折了。

    04/11/2009

    NandFlash的分区实现

    提到分区就需要知道MBR,了解分区表。
    什么是MBR

         硬盘的0柱面、0磁头、1扇区称为主引导扇区,NANDFLASH由BLOCK和Sector组成,所以NANDFLASH的第0 BLOCK,第1 Sector为主引导扇区,FDISK程序写到该扇区的内容称为主引导记录(MBR)。该记录占用512个字节,它用于硬盘启动时将系统控制权交给用户指定的,并在分区表中登记了的某个操作系统区。

    MBR的组成

    一个扇区的硬盘主引导记录MBR由如图6-15所示的4个部分组成。
    • 主引导程序(偏移地址0000H—0088H),它负责从活动分区中装载,并运行系统引导程序。
    • 出错信息数据区,偏移地址0089H--00E1H为出错信息,00E2H--01BDH全为0字节。
    • 分区表(DPT,Disk Partition Table)含4个分区项,偏移地址01BEH--01FDH,每个分区表项长16个字节,共64字节为分区项1、分区项2、分区项3、分区项4。
    • 结束标志字,偏移地址01FE--01FF的2个字节值为结束标志55AA,如果该标志错误系统就不能启动。
    0000-0088
    Master Boot Record
    主引导程序
    主引导
    程序
    0089-01BD 出错信息数据区 数据区
    01BE-01CD
    分区项1(16字节)
    分区表
    01CE-01DD
    分区项2(16字节)
    01DE-01ED
    分区项3(16字节)
    01EE-01FD
    分区项4(16字节)
    01FE
    55
    结束标志
    01FF
    AA
                                 图6-15 MBR的组成结构图

    MBR中的分区信息结构

         占用512个字节的MBR中,偏移地址01BEH--01FDH的64个字节,为4个分区项内容(分区信息表)。它是由磁盘介质类型及用户在使用 FDISK定义分区说确定的。在实际应用中,FDISK对一个磁盘划分的主分区可少于4个,但最多不超过4个。每个分区表的项目是16个字节,其内容含义 如表6-19所示。
    表6-19 分区项表(16字节)内容及含义

    存贮字节位 内容及含义
    第1字节 引导标志。若值为80H表示活动分区,若值为00H表示非活动分区。
    第2、3、4字节
    本分区的起始磁头号、扇区号、柱面号。其中:
    磁头号——第2字节;
    扇区号——第3字节的低6位;
    柱面号——为第3字节高2位+第4字节8位。
    第5字节
    分区类型符:
    00H——表示该分区未用(即没有指定);
    06H——FAT16基本分区;
    0BH——FAT32基本分区;
    05H——扩展分区;
    07H——NTFS分区;
    0FH——(LBA模式)扩展分区(83H为Linux分区等)。
    第6、7、8字节
    本分区的结束磁头号、扇区号、柱面号,其中:
    磁头号——第6字节;
    扇区号——第7字节的低6位;
    柱面号——第7字节的高2位+第8字节。
    第9、10、11、12字节 本分区之前已用了的扇区数
    第13、14、15、16字节 本分区的总扇区数

            EBOOT中对NAND分区主要代码,eboot目录下的fmd.cpp文件,与NAND驱动基本相同,所以,要对NAND进行分区,就得对NAND驱动非常熟悉。透彻了解。然后就是E:\WINCE500\PUBLIC\COMMON\OAK\DRIVERS\ETHDBG\BOOTPART\bootpart.cpp文件了。该文件主要通过调用NANDFLASH的读写操作来写入MBR,也是今天主要的分析对象。

    主要函数。
    /*  BP_OpenPartition
    *
    *  Opens/creates a partition depending on the creation flags.  If it is opening
    *  and the partition has already been opened, then it returns a handle to the
    *  opened partition.  Otherwise, it loads the state information of that partition
    *  into memory and returns a handle.
    *
    *  ENTRY
    *      dwStartSector - Logical sector to start the partition.  NEXT_FREE_LOC if none
    *          specified.  Ignored if opening existing partition.
    *      dwNumSectors - Number of logical sectors of the partition.  USE_REMAINING_SPACE
    *          to indicate to take up the rest of the space on the flash for that partition (should
    *          only be used when creating extended partitions).  This parameter is ignored
    *          if opening existing partition.
    *      dwPartType - Type of partition to create/open.
    *      fActive - TRUE indicates to create/open the active partition.  FALSE for
    *          inactive.
    *      dwCreationFlags - PART_CREATE_NEW to create only.  Fail if it already
    *          exists.  PART_OPEN_EXISTING to open only.  Fail if it doesn't exist.
    *          PART_OPEN_ALWAYS creates if it does not exist and opens if it
    *          does exist.
    *
    *  EXIT
    *      Handle to the partition on success.  INVALID_HANDLE_VALUE on error.
    */

     

    HANDLE BP_OpenPartition(DWORD dwStartSector, DWORD dwNumSectors, DWORD dwPartType, BOOL fActive, DWORD dwCreationFlags)
      (注:示例代码为本人EBOOT中分区实现源码(WINCE5.0+S3C2440+128MNAND,MBR写在第4个BLOCK,分一个BINFS格式分区和一个FAT格式分区)。)
    BOOL WriteRegionsToBootMedia(DWORD dwImageStart, DWORD dwImageLength, DWORD dwLaunchAddr)
     
    在把SDRAM中的NK烧写到NAND中去之前,先创建一个BINFS分区。
    hPart = BP_OpenPartition( (NK_START_BLOCK+1)*PAGES_PER_BLOCK,  // next block of MBR     BINFS_BLOCK*PAGES_PER_BLOCK,//SECTOR_TO_BLOCK_SIZE(FILE_TO_SECTOR_SIZE(dwBINFSPartLength))*PAGES_PER_BLOCK,  //align to block
                                  PART_BINFS,
                                  TRUE,
                                  PART_OPEN_ALWAYS);
    第一个参数分区的起始sector 为(NK_START_BLOCK+1)*PAGES_PER_BLOCK,
    第二个参数分区的结束 sector为BINFS_BLOCK*PAGES_PER_BLOCK,
    第三个参数分区的格式为PART_BINFS,即BINFS格式,
    第四个参数指示该分区为活动分区,fActive = TURE,
    第五个参数PART_OPEN_ALWAYS指示如果分区不存在就创建该分区,存在就OPEN该分区,返回分区句柄。
     
    HANDLE BP_OpenPartition(DWORD dwStartSector, DWORD dwNumSectors, DWORD dwPartType, BOOL fActive, DWORD dwCreationFlags)
    {
            DWORD dwPartIndex;
            BOOL fExists;
            ASSERT (g_pbMBRSector);
            if (!IsValidMBR()) {
                DWORD dwFlags = 0;
                //fly
                 RETAILMSG(1, (TEXT("BP_OpenPartition:: dwStartSector=0x%x ,dwNumSectors= 0x%x.,dwPartType = 0x%x\r\n"), dwStartSector, dwNumSectors,dwPartType));
                if (dwCreationFlags == PART_OPEN_EXISTING) {
                    RETAILMSG(1, (TEXT("OpenPartition: Invalid MBR.  Cannot open existing partition 0x%x.\r\n"), dwPartType));
                    return INVALID_HANDLE_VALUE;
                }
                RETAILMSG(1, (TEXT("OpenPartition: Invalid MBR.  Formatting flash.\r\n")));
                if (g_FlashInfo.flashType == NOR) {
                    dwFlags |= FORMAT_SKIP_BLOCK_CHECK;
                }
                //fly
                 RETAILMSG(1, (TEXT("BP_LowLevelFormat: g_pbMBRSector=0x%x, g_dwMBRSectorNum= 0x%x.\r\n"), *g_pbMBRSector, g_dwMBRSectorNum));
                BP_LowLevelFormat (SECTOR_TO_BLOCK(dwStartSector), SECTOR_TO_BLOCK(dwNumSectors), dwFlags);
                dwPartIndex = 0;
                fExists = FALSE;
            }
            else {
                fExists = GetPartitionTableIndex(dwPartType, fActive, &dwPartIndex);      
            }
            RETAILMSG(1, (TEXT("OpenPartition: Partition Exists=0x%x for part 0x%x.\r\n"), fExists, dwPartType));
            if (fExists) {
                // Partition was found.
                if (dwCreationFlags == PART_CREATE_NEW)
                    return INVALID_HANDLE_VALUE;
                if (g_partStateTable[dwPartIndex].pPartEntry == NULL) {
                    // Open partition.  If this is the boot section partition, then file pointer starts after MBR
                    g_partStateTable[dwPartIndex].pPartEntry = (PPARTENTRY)(g_pbMBRSector + PARTTABLE_OFFSET + sizeof(PARTENTRY)*dwPartIndex);
                    g_partStateTable[dwPartIndex].dwDataPointer = 0;
                }
               if ( dwNumSectors > g_partStateTable[dwPartIndex].pPartEntry->Part_TotalSectors )
                  return CreatePartition (dwStartSector, dwNumSectors, dwPartType, fActive, dwPartIndex);
               else        
                       return (HANDLE)&g_partStateTable[dwPartIndex];          
            }
            else {
                // If there are already 4 partitions, or creation flag specified OPEN_EXISTING, fail.
                if ((dwPartIndex == NUM_PARTS) || (dwCreationFlags == PART_OPEN_EXISTING))
                    return INVALID_HANDLE_VALUE;
                // Create new partition
                return CreatePartition (dwStartSector, dwNumSectors, dwPartType, fActive, dwPartIndex);
            }
            return INVALID_HANDLE_VALUE;
    }
     
    进入函数,首先做的事就是检测MBR的有效性。通过函数IsValidMBR()实现。
    检测MBR的有效性,首先要知道MBR保存在哪里,前面说过NANDFLASH的第0 BLOCK,第1 Sector为主引导扇区,也就是MBR,但是NAND如果被当作启动芯片,○地址一般被BOOTLOADER代码占据,MBR只有放在后面的BLOCK中。所以我把第0 个BLOCK放NBOOT,第1个BLOCK放TOC,第2个BLOCK放EBOOT,第3个BLOCK保留,第4个BLOCK就放MBR。
     
    static BOOL IsValidMBR()
    {
        // Check to see if the MBR is valid
        // MBR block is always located at logical sector 0
        g_dwMBRSectorNum = GetMBRSectorNum();      
        RETAILMSG (1, (TEXT("IsValidMBR: MBR sector = 0x%x\r\n"), g_dwMBRSectorNum));
        if ((g_dwMBRSectorNum == INVALID_ADDR) || !FMD_ReadSector (g_dwMBRSectorNum, g_pbMBRSector, NULL, 1)) {
           RETAILMSG (1, (TEXT("IsValidMBR-----return FALSE-------------------\r\n")));
            return FALSE;
        }  
        return ((g_pbMBRSector[0] == 0xE9) &&
             (g_pbMBRSector[1] == 0xfd) &&
             (g_pbMBRSector[2] == 0xff) &&
             (g_pbMBRSector[SECTOR_SIZE_FS-2] == 0x55) &&
             (g_pbMBRSector[SECTOR_SIZE_FS-1] == 0xAA));
    }
     
    IsValidMBR()实现的第一行就是给全局变量g_dwMBRSectorNum 赋值,显而易见,g_dwMBRSectorNum就是指示保存MBR的那个Sector了。
    g_dwMBRSectorNum = GetMBRSectorNum();   //是获得保存MBR的那个Sector
     
    static DWORD GetMBRSectorNum ()
    {
        DWORD dwBlockNum = 3, dwSector = 0;
        SectorInfo si;
       
    while (dwBlockNum < g_FlashInfo
    .dwNumBlocks) {
            if (!IS_BLOCK_UNUSABLE (dwBlockNum)) {
                dwSector = dwBlockNum * g_FlashInfo.wSectorsPerBlock;
                if (!FMD_ReadSector (dwSector, NULL, &si, 1)) {
                    RETAILMSG(1, (TEXT("GetMBRSectorNum: Could not read sector 0x%x.\r\n"), dwSector));
                    return INVALID_ADDR;
                }
                // Check to see if logical sector number is 0
                if (si.dwReserved1 == 0) {
                  //RETAILMSG(1,(TEXT("dwBlockNum=%d\r\n"),dwBlockNum));
                    return dwSector;
                }
            }
            dwBlockNum++;
        }
        return INVALID_ADDR;
    }
     
    这里dwBlockNum直接给了个3,因为NBOOT,TOC,EBOOT已经把前三个BLOCK用了。所以MBR的选择直接排除了前三个BLOCK了。
    #define IS_BLOCK_UNUSABLE(blockID) ((FMD_GetBlockStatus (blockID) & (BLOCK_STATUS_BAD|BLOCK_STATUS_RESERVED)) > 0)
    然后确定BLOCK是否可使用的BLOCK,最后通si.dwReserved1 == 0来判断是不是选择这个Sector来保存MBR。
    IsValidMBR()中还有一个重要的结构就是g_pbMBRSector数组,它就是MBR了。
    函数返回时,MBR必须符合下列记录。
       
    return ((g_pbMBRSector[0] == 0xE9) &&
             (g_pbMBRSector[1] == 0xfd) &&
             (g_pbMBRSector[2] == 0xff) &&
             (g_pbMBRSector[SECTOR_SIZE_FS-2] == 0x55) &&
             (g_pbMBRSector[SECTOR_SIZE_FS-1] == 0xAA));
     
    可以看到只有开始三个字节为0XE9,FD,FF,当然,还有熟悉的结束标志符0X55AA。
    如果没有检测到MBR,则先对NANDFLASH进行低级格式化。BP_LowLevelFormat (SECTOR_TO_BLOCK(dwStartSector), SECTOR_TO_BLOCK(dwNumSectors), dwFlags);再创建分区,CreatePartition (dwStartSector, dwNumSectors, dwPartType, fActive, dwPartIndex);。
     
    BOOL BP_LowLevelFormat(DWORD dwStartBlock, DWORD dwNumBlocks, DWORD dwFlags)
    {
        dwNumBlocks = min (dwNumBlocks, g_FlashInfo.dwNumBlocks);
        RETAILMSG(1,(TEXT("fly::Enter LowLevelFormat [0x%x, 0x%x].\r\n"), dwStartBlock,dwNumBlocks));// dwStartBlock + dwNumBlocks - 1));
        // Erase all the flash blocks.
        if (!EraseBlocks(dwStartBlock, dwNumBlocks, dwFlags))
            return(FALSE);
        // Determine first good starting block
        while (IS_BLOCK_UNUSABLE (dwStartBlock) && dwStartBlock < g_FlashInfo.dwNumBlocks) {
            dwStartBlock++;
        }
        if (dwStartBlock >= g_FlashInfo.dwNumBlocks) {
            RETAILMSG(1,(TEXT("BP_LowLevelFormat: no good blocks\r\n")));      
            return FALSE;
        }
        // MBR goes in the first sector of the starting block.  This will be logical sector 0.
        g_dwMBRSectorNum = dwStartBlock * g_FlashInfo.wSectorsPerBlock;
        RETAILMSG(1,(TEXT("fly:g_dwMBRSectorNum=%d\r\n"),g_dwMBRSectorNum));
        // Create an MBR.
        CreateMBR();
        return(TRUE);
    }
     
    在对NANDFLASH进行低格时,主要对坏块的处理。if (!EraseBlocks(dwStartBlock, dwNumBlocks, dwFlags))检测每一个Sector,每个BLOCK只要有一个Sector不能读写这个块都会被处理成坏块,这样才能保证系统的稳定性。在函数的最后调用了    CreateMBR();来创建一个MBR。
     
    static BOOL CreateMBR()
    {
        // This, plus a valid partition table, is all the CE partition manager needs to recognize
        // the MBR as valid. It does not contain boot code.
        memset (g_pbMBRSector, 0xff, g_FlashInfo.wDataBytesPerSector);
        g_pbMBRSector[0] = 0xE9;
        g_pbMBRSector[1] = 0xfd;
        g_pbMBRSector[2] = 0xff;
        g_pbMBRSector[SECTOR_SIZE_FS-2] = 0x55;
        g_pbMBRSector[SECTOR_SIZE_FS-1] = 0xAA;
        // Zero out partition table so that mspart treats entries as empty.
        memset (g_pbMBRSector+PARTTABLE_OFFSET, 0, sizeof(PARTENTRY) * NUM_PARTS);
        return WriteMBR();
     
    当然。因为还没有进行分区,这里写入的MBR分区表部分是空的。
     
    static BOOL WriteMBR()
    {
        DWORD dwMBRBlockNum = g_dwMBRSectorNum / g_FlashInfo.wSectorsPerBlock;
        //dwMBRBlockNum = 1 ;
        RETAILMSG(1, (TEXT("WriteMBR: MBR block = 0x%x,g_dwMBRSectorNum = 0x%x.\r\n"), dwMBRBlockNum,g_dwMBRSectorNum));
        memset (g_pbBlock, 0xff, g_dwDataBytesPerBlock);
        memset (g_pSectorInfoBuf, 0xff, sizeof(SectorInfo) * g_FlashInfo.wSectorsPerBlock);
        // No need to check return, since a failed read means data hasn't been written yet.
        ReadBlock (dwMBRBlockNum, g_pbBlock, g_pSectorInfoBuf);
        if (!FMD_EraseBlock (dwMBRBlockNum)) {
            RETAILMSG (1, (TEXT("CreatePartition: error erasing block 0x%x\r\n"), dwMBRBlockNum));
            return FALSE;
        }
        memcpy (g_pbBlock + (g_dwMBRSectorNum % g_FlashInfo.wSectorsPerBlock) * g_FlashInfo.wDataBytesPerSector, g_pbMBRSector, g_FlashInfo.wDataBytesPerSector);
        g_pSectorInfoBuf->bOEMReserved &= ~OEM_BLOCK_READONLY;
        g_pSectorInfoBuf->wReserved2 &= ~SECTOR_WRITE_COMPLETED;
        g_pSectorInfoBuf->dwReserved1 = 0;
        RETAILMSG(1, (TEXT("fly::WriteMBR: MBR block = 0x%x.\r\n"), dwMBRBlockNum));
        if (!WriteBlock (dwMBRBlockNum, g_pbBlock, g_pSectorInfoBuf)) {
            RETAILMSG (1, (TEXT("CreatePartition: could not write to block 0x%x\r\n"), dwMBRBlockNum));
            return FALSE;
        }
        return TRUE;
    }
     
    在WriteMBR()函数中,就写入了判断MBR 的一些标志到BLOCK,    g_pSectorInfoBuf->bOEMReserved &= ~OEM_BLOCK_READONLY;
        g_pSectorInfoBuf->wReserved2 &= ~SECTOR_WRITE_COMPLETED;
        g_pSectorInfoBuf->dwReserved1 = 0;
    Wince系统启动时,具体是NANDFLASH驱动加载成功后,MOUNT文件系统到NANDFLASH之前,也会通过读取这些SectorInfo来得到MBR 保存的BLOCK,进而读取MBR,获得分区信息,从而把各分区MOUNT到相应文件系统。格式化完成,MBR也写入成功后就可以开始新建分区了。
     
    /*  CreatePartition
    *
    *  Creates a new partition.  If it is a boot section partition, then it formats
    *  flash.
    *
    *  ENTRY
    *      dwStartSector - Logical sector to start the partition.  NEXT_FREE_LOC if
    *          none specified.
    *      dwNumSectors - Number of logical sectors of the partition.  USE_REMAINING_SPACE
    *          to indicate to take up the rest of the space on the flash for that partition.
    *      dwPartType - Type of partition to create.
    *      fActive - TRUE indicates to create the active partition.  FALSE for
    *          inactive.
    *      dwPartIndex - Index of the partition entry on the MBR
    *
    *  EXIT
    *      Handle to the partition on success.  INVALID_HANDLE_VALUE on error.
    */
    static HANDLE CreatePartition (DWORD dwStartSector, DWORD dwNumSectors, DWORD dwPartType, BOOL fActive, DWORD dwPartIndex)
    {
        DWORD dwBootInd = 0;
        RETAILMSG(1, (TEXT("CreatePartition: Enter CreatePartition for 0x%x.\r\n"), dwPartType));
        if (fActive)
            dwBootInd |= PART_IND_ACTIVE;
        if (dwPartType == PART_BOOTSECTION || dwPartType == PART_BINFS || dwPartType == PART_XIP)
            dwBootInd |= PART_IND_READ_ONLY;  
         // If start sector is invalid, it means find next free sector
        if (dwStartSector == NEXT_FREE_LOC) {      
            dwStartSector = FindFreeSector();
            if (dwStartSector == INVALID_ADDR) {
                RETAILMSG(1, (TEXT("CreatePartition: can't find free sector.\r\n")));
                return INVALID_HANDLE_VALUE;
            }
            // Start extended partition on a block boundary
            if ((dwPartType == PART_EXTENDED) && (dwStartSector % g_FlashInfo.wSectorsPerBlock)) {
                dwStartSector = (dwStartSector / g_FlashInfo.wSectorsPerBlock + 1) * g_FlashInfo.wSectorsPerBlock;
            }
        }
        // If num sectors is invalid, fill the rest of the space up
        if (dwNumSectors == USE_REMAINING_SPACE) {
            DWORD dwLastLogSector = LastLogSector();
            if (dwLastLogSector == INVALID_ADDR)
                return INVALID_HANDLE_VALUE;
            // Determine the number of blocks to reserve for the FAL compaction when creating an extended partition.
            DWORD dwReservedBlocks = g_FlashInfo.dwNumBlocks / PERCENTAGE_OF_MEDIA_TO_RESERVE;
            if((dwReservedBlocks = g_FlashInfo.dwNumBlocks / PERCENTAGE_OF_MEDIA_TO_RESERVE) < MINIMUM_FLASH_BLOCKS_TO_RESERVE) {
                dwReservedBlocks = MINIMUM_FLASH_BLOCKS_TO_RESERVE;
            }
            dwNumSectors = dwLastLogSector - dwStartSector + 1 - dwReservedBlocks * g_FlashInfo.wSectorsPerBlock;
        }
        if (!AreSectorsFree (dwStartSector, dwNumSectors)){
            RETAILMSG (1, (TEXT("fly:::::CreatePartition: sectors [0x%x, 0x%x] requested are out of range or taken by another partition\r\n"), dwStartSector, dwNumSectors));
            return INVALID_HANDLE_VALUE;
        }
        RETAILMSG(1, (TEXT("CreatePartition: Start = 0x%x, Num = 0x%x.\r\n"), dwStartSector, dwNumSectors));
        AddPartitionTableEntry (dwPartIndex, dwStartSector, dwNumSectors, (BYTE)dwPartType, (BYTE)dwBootInd);
        if (dwBootInd & PART_IND_READ_ONLY) {
            if (!WriteLogicalNumbers (dwStartSector, dwNumSectors, TRUE)) {
                RETAILMSG(1, (TEXT("CreatePartition: can't mark sector info.\r\n")));
                return INVALID_HANDLE_VALUE;
            }
        }
        if (!WriteMBR())
            return INVALID_HANDLE_VALUE;
        g_partStateTable[dwPartIndex].pPartEntry = (PPARTENTRY)(g_pbMBRSector + PARTTABLE_OFFSET + sizeof(PARTENTRY)*dwPartIndex);
        g_partStateTable[dwPartIndex].dwDataPointer = 0;
        return (HANDLE)&g_partStateTable[dwPartIndex];          
    }
     
    如果第二个参数为-1,则视为将余下的所有空间划为一个分区。LastLogSector();函数获得最后一个逻辑Sector。
     
    static DWORD LastLogSector()
    {
        if (g_dwLastLogSector) {
           return g_dwLastLogSector;
        }
        DWORD dwMBRBlock = g_dwMBRSectorNum / g_FlashInfo.wSectorsPerBlock;
        DWORD dwUnusableBlocks = dwMBRBlock;
        for (DWORD i = dwMBRBlock; i < g_FlashInfo.dwNumBlocks; i++) {
            if (IS_BLOCK_UNUSABLE (i))
                dwUnusableBlocks++;
        }
        g_dwLastLogSector = (g_FlashInfo.dwNumBlocks - dwUnusableBlocks) * g_FlashInfo.wSectorsPerBlock - 1;
        RETAILMSG(1, (TEXT("fly:::LastLogSector: Last log sector is: 0x%x.\r\n"), g_dwLastLogSector));
        return g_dwLastLogSector;
    }
     
    即g_dwLastLogSector = (g_FlashInfo.dwNumBlocks - dwUnusableBlocks) * g_FlashInfo.wSectorsPerBlock - 1;//(NAND 的BLOCK总数 – MBR保存的那个BLOCK)* 每个BLOCK的Sector数 – 保存MBR的那个Sector。得到的就是从MBR那个Sector之后的所有Sector,即逻辑大小。
    AreSectorsFree (dwStartSector, dwNumSectors)函数判断参数提供的起始Sector和个数有没有超出来NAND的界限,或者逻辑分区的界限。
    重头戏开始了。通过AddPartitionTableEntry (dwPartIndex, dwStartSector, dwNumSectors, (BYTE)dwPartType, (BYTE)dwBootInd); 准备分区信息写入分区表。
     
    /*  AddPartitionTableEntry
    *
    *  Generates the partition entry for the partition table and copies the entry
    *  into the MBR that is stored in memory.
    *
    *
    *  ENTRY
    *      entry - index into partition table
    *      startSector - starting logical sector
    *      totalSectors - total logical sectors
    *      fileSystem - type of partition
    *      bootInd - byte in partition entry that stores various flags such as
    *          active and read-only status.
    *
    *  EXIT
    */
    static void AddPartitionTableEntry(DWORD entry, DWORD startSector, DWORD totalSectors, BYTE fileSystem, BYTE bootInd)
    {
        PARTENTRY partentry = {0};
        Addr startAddr;
        Addr endAddr;
        ASSERT(entry < 4);
        // no checking with disk info and start/total sectors because we allow
        // bogus partitions for testing purposes
        // initially known partition table entry
        partentry.Part_BootInd = bootInd;
        partentry.Part_FileSystem = fileSystem;
        partentry.Part_StartSector = startSector;
        partentry.Part_TotalSectors = totalSectors;
        // logical block addresses for the first and final sector (start on the second head)
        startAddr.type = LBA;
        startAddr.lba = partentry.Part_StartSector;
        endAddr.type = LBA;
        endAddr.lba = partentry.Part_StartSector + partentry.Part_TotalSectors-1;
        // translate the LBA addresses to CHS addresses
        startAddr = LBAtoCHS(&g_FlashInfo, startAddr);
        endAddr = LBAtoCHS(&g_FlashInfo, endAddr);
        // starting address
        partentry.Part_FirstTrack = (BYTE)(startAddr.chs.cylinder & 0xFF);
        partentry.Part_FirstHead = (BYTE)(startAddr.chs.head & 0xFF);
        // lower 6-bits == sector, upper 2-bits = cylinder upper 2-bits of 10-bit cylinder #
        partentry.Part_FirstSector = (BYTE)((startAddr.chs.sector & 0x3F) | ((startAddr.chs.cylinder & 0x0300) >> 2));
        // ending address:
        partentry.Part_LastTrack = (BYTE)(endAddr.chs.cylinder & 0xFF);
        partentry.Part_LastHead = (BYTE)(endAddr.chs.head & 0xFF);
        // lower 6-bits == sector, upper 2-bits = cylinder upper 2-bits of 10-bit cylinder #
        partentry.Part_LastSector = (BYTE)((endAddr.chs.sector & 0x3F) | ((endAddr.chs.cylinder & 0x0300) >> 2));
        memcpy(g_pbMBRSector+PARTTABLE_OFFSET+(sizeof(PARTENTRY)*entry), &partentry, sizeof(PARTENTRY));
    }
     
    这里面的地址信息是一种叫CHS(Cyinder/Head/Sector)的地址。eboot中有将逻辑地址LBS(Logical Block Addr)与这种地址互相转换的函数LBAtoCHS,CHSToLBA。
     
    Addr LBAtoCHS(FlashInfo *pFlashInfo, Addr lba)
    {
        Addr chs;
        DWORD tmp = pFlashInfo->dwNumBlocks * pFlashInfo->wSectorsPerBlock;
        chs.type = CHS;
        chs.chs.cylinder = (WORD)(lba.lba / tmp);                                      // 柱面,应该始终是0
        tmp = lba.lba % tmp;
        chs.chs.head = (WORD)(tmp / pFlashInfo->wSectorsPerBlock);                     // 块地址
        chs.chs.sector = (WORD)((tmp % pFlashInfo->wSectorsPerBlock) + 1);     // 扇区+1
        return chs;
    }
    Addr CHStoLBA(FlashInfo *pFlashInfo, Addr chs)
    {
        Addr lba;
        lba.type = LBA;
        lba.lba = ((chs.chs.cylinder * pFlashInfo->dwNumBlocks + chs.chs.head)
            * pFlashInfo->wSectorsPerBlock)+ chs.chs.sector - 1;
    return lba;
    }
     
    如果分区的格式有只读属性,则通过WriteLogicalNumbers()函数写分区的Sectorinfo,把这部分空间保护起来。
     
    static BOOL WriteLogicalNumbers (DWORD dwStartSector, DWORD dwNumSectors, BOOL fReadOnly)
    {
        DWORD dwNumSectorsWritten = 0;
        DWORD dwPhysSector = Log2Phys (dwStartSector);
        DWORD dwBlockNum = dwPhysSector / g_FlashInfo.wSectorsPerBlock;
        DWORD dwOffset = dwPhysSector % g_FlashInfo.wSectorsPerBlock;
        while (dwNumSectorsWritten < dwNumSectors) {
            // If bad block, move to the next block
            if (IS_BLOCK_UNUSABLE (dwBlockNum)) {
                dwBlockNum++;
                continue;
            }
            memset (g_pbBlock, 0xff, g_dwDataBytesPerBlock);
            memset (g_pSectorInfoBuf, 0xff, sizeof(SectorInfo) * g_FlashInfo.wSectorsPerBlock);
            // No need to check return, since a failed read means data hasn't been written yet.
            ReadBlock (dwBlockNum, g_pbBlock, g_pSectorInfoBuf);
            if (!FMD_EraseBlock (dwBlockNum)) {
                return FALSE;
            }
            DWORD dwSectorsToWrite = g_FlashInfo.wSectorsPerBlock - dwOffset;
            PSectorInfo pSectorInfo = g_pSectorInfoBuf + dwOffset;
            // If this is the last block, then calculate sectors to write if there isn't a full block to update
            if ((dwSectorsToWrite + dwNumSectorsWritten) > dwNumSectors)
                dwSectorsToWrite = dwNumSectors - dwNumSectorsWritten;
            for (DWORD iSector = 0; iSector < dwSectorsToWrite; iSector++, pSectorInfo++, dwNumSectorsWritten++) {
                // Assert read only by setting bit to 0 to prevent wear-leveling by FAL
                if (fReadOnly)
                    pSectorInfo->bOEMReserved &= ~OEM_BLOCK_READONLY;
                // Set to write completed so FAL can map the sector
                pSectorInfo->wReserved2 &= ~SECTOR_WRITE_COMPLETED;      
                // Write the logical sector number
                pSectorInfo->dwReserved1 = dwStartSector + dwNumSectorsWritten;          
            }
            if (!WriteBlock (dwBlockNum, g_pbBlock, g_pSectorInfoBuf))
                return FALSE;
            dwOffset = 0;
            dwBlockNum++;
        }
        return TRUE;
    }
     
    这就是为什么系统启动后,我们无法写入文件的BINFS文件系统格式分区的原因了。而FAT格式就可以。最后调用WriteMBR()完全MBR的写入,分区完毕。
    让我们继续回到BP_OpenPartition函数中,如果从一开始IsValidMBR()就检测到有效的MBR,GetPartitionTableIndex(dwPartType, fActive, &dwPartIndex);获得分区表。和dwPartIndex分区表的索引号。
     
    static BOOL GetPartitionTableIndex (DWORD dwPartType, BOOL fActive, PDWORD pdwIndex)
    {
        PPARTENTRY pPartEntry = (PPARTENTRY)(g_pbMBRSector + PARTTABLE_OFFSET);
        DWORD iEntry = 0;
        for (iEntry = 0; iEntry < NUM_PARTS; iEntry++, pPartEntry++) {
            if ((pPartEntry->Part_FileSystem == dwPartType) && (((pPartEntry->Part_BootInd & PART_IND_ACTIVE) != 0) == fActive)) {
                *pdwIndex = iEntry;
                return TRUE;
            }
            if (!IsValidPart (pPartEntry)) {
                *pdwIndex = iEntry;
                return FALSE;
            }
        }
        return FALSE;
    }
    重要结构:PARTENTRY
    // end of master boot record contains 4 partition entries
    typedef struct _PARTENTRY {
            BYTE            Part_BootInd;           // If 80h means this is boot partition
            BYTE            Part_FirstHead;         // Partition starting head based 0
            BYTE            Part_FirstSector;       // Partition starting sector based 1
            BYTE            Part_FirstTrack;        // Partition starting track based 0
            BYTE            Part_FileSystem;        // Partition type signature field
            BYTE            Part_LastHead;          // Partition ending head based 0
            BYTE            Part_LastSector;        // Partition ending sector based 1
            BYTE            Part_LastTrack;         // Partition ending track based 0
            DWORD           Part_StartSector;       // Logical starting sector based 0
            DWORD           Part_TotalSectors;      // Total logical sectors in partition
    } PARTENTRY;
    分区表就是通过这个结构写入MBR,起始地址,分区大小,分区格式,对应结构写入MBR所在的Sector就可以了。在检测有效分区时static BOOL IsValidPart (PPARTENTRY pPartEntry)
    {
        return (pPartEntry->Part_FileSystem != 0xff) && (pPartEntry->Part_FileSystem != 0);
    }
    就是通过对分区表文件系统格式的判断了。
    把NAND后面的空间,全部分为一个FAT格式的分区。
        //
        // create extended partition in whatever is left
        //
        hPartEx = BP_OpenPartition( (NK_START_BLOCK+1+BINFS_BLOCK) * PAGES_PER_BLOCK,
                                    NEXT_FREE_LOC,   // (1024 - (NK_START_BLOCK+1+SECTOR_TO_BLOCK_SIZE(FILE_TO_SECTOR_SIZE(dwBINFSPartLength)))) * PAGES_PER_BLOCK,
                                    PART_DOS32,
                                    TRUE,
                                    PART_OPEN_ALWAYS);
        if (hPartEx == INVALID_HANDLE_VALUE )
        {
            EdbgOutputDebugString("*** WARN: StoreImageToBootMedia: Failed to open/create Extended partition ***\r\n");
        }

    03/11/2009

    SOCKET模型之重叠I/O

    最近的项目中有WIFI模块来和其他设备进行音视频的通信,在网络编程中发现有很多东西需要学习,所以就想记录下来。

    整合了网上的一些文章。

    目录:
    • 重叠模型的优点
    • 重叠模型的基本原理
    • 关于重叠模型的基础知识
    • 重叠模型的实现步骤
    • 多客户端情况的注意事项

    一. 重叠模型的优点

    1. 可以运行在支持Winsock2的所有Windows平台 ,而不像完成端口只是支持NT系统。
    2. 比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。因为它和这4种模型不同的是,使用重叠模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接被拷贝到投递的缓冲区。而这4种模型种,数据到达并拷贝到单套接字接收缓冲区中,此时应用程序会被告知可以读入的容量。当应用程序调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区,差别就体现出来了。
    3. 从《windows网络编程》中提供的试验结果中可以看到,在使用了P4 1.7G Xero处理器(CPU很强啊)以及768MB的回应服务器中,最大可以处理4万多个SOCKET连接,在处理1万2千个连接的时候CPU占用率才40% 左右 ―― 非常好的性能,已经直逼完成端口了^_^

    二. 重叠模型的基本原理

        说了这么多的好处,你一定也跃跃欲试了吧,不过我们还是要先提一下重叠模型的基本原理。
        概括一点说,重叠模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock I/O请求。针对这些提交的请求,在它们完成之后,应用程序会收到通知,于是就可以通过自己另外的代码来处理这些数据了。
        需要注意的是,有两个方法可以用来管理重叠IO请求的完成情况(就是说接到重叠操作完成的通知):
      1. 事件对象通知(event object notification)
      2. 完成例程(completion routines) ,注意,这里并不是完成端口
            而本文只是讲述如何来使用事件通知的的方法实现重叠IO模型,完成例程的方法准备放到下一篇讲 :) (内容太多了,一篇写不完啊) ,如没有特殊说明,本文的重叠模型默认就是指的基于事件通知的重叠模型。

            既然是基于事件通知,就要求将Windows事件对象与WSAOVERLAPPED结构关联在一起(WSAOVERLAPPED结构中专门有对应的参数),通俗一点讲,就是。。。。对了,忘了说了,既然要使用重叠结构,我们常用的send, sendto, recv, recvfrom也都要被WSASend, WSASendto, WSARecv, WSARecvFrom替换掉了, 它们的用法我后面会讲到,这里只需要注意一点,它们的参数中都有一个Overlapped参数,我们可以假设是把我们的WSARecv这样的操作操作“绑定”到这个重叠结构上,提交一个请求,其他的事情就交给重叠结构去操心,而其中重叠结构又要与Windows的事件对象“绑定”在一起,这样我们调用完WSARecv以后就可以“坐享其成”,等到重叠操作完成以后,自然会有与之对应的事件来通知我们操作完成,然后我们就可以来根据重叠操作的结果取得我们想要的数据了。

        三. 关于重叠模型的基础知识

            下面来介绍并举例说明一下编写重叠模型的程序中将会使用到的几个关键函数。

        1. WSAOVERLAPPED结构

        这个结构自然是重叠模型里的核心,它是这么定义的
        typedef struct _WSAOVERLAPPED {
                  DWORD Internal;
                   DWORD InternalHigh;
                 DWORD Offset;
                 DWORD OffsetHigh;
                 WSAEVENT hEvent;      // 唯一需要关注的参数,用来关联WSAEvent对象
        } WSAOVERLAPPED, *LPWSAOVERLAPPED;
            我们需要把WSARecv等操作投递到一个重叠结构上,而我们又需要一个与重叠结构“绑定”在一起的事件对象来通知我们操作的完成,看到了和hEvent参数,不用我说你们也该知道如何来来把事件对象绑定到重叠结构上吧?大致如下:
        WSAEVENT event;                   // 定义事件
        WSAOVERLAPPED AcceptOverlapped ; // 定义重叠结构
        event = WSACreateEvent();         // 建立一个事件对象句柄
        ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED)); // 初始化重叠结构
        AcceptOverlapped.hEvent = event;    // Done !!
         

        2. WSARecv系列函数

        在重叠模型中,接收数据就要靠它了,它的参数也比recv要多,因为要用刀重叠结构嘛,它是这样定义的:
        int WSARecv(
                SOCKET         s,                            // 当然是投递这个操作的套接字
                LPWSABUF    lpBuffers,               // 接收缓冲区,与Recv函数不同,这里需要一个由WSABUF结构构成的数组
                DWORD        dwBufferCount,      // 数组中WSABUF结构的数量
                LPDWORD     lpNumberOfBytesRecvd// 如果接收操作立即完成,这里会返回函数调用所接收到的字节数
                LPDWORD    lpFlags,                    // 说来话长了,我们这里设置为0 即可
                LPWSAOVERLAPPED lpOverlapped,   // “绑定”的重叠结构
                LPWSAOVERLAPPED_COMPLETION_ROUTINE    lpCompletionRoutine   // 完成例程中将会用到的参数,我们这里设置为 NULL
        );
        返回值:
        WSA_IO_PENDING : 最常见的返回值,这是说明我们的WSARecv操作成功了,但是I/O操作还没有完成,所以我们就需要绑定一个事件来通知我们操作何时完成
        举个例子:(变量的定义顺序和上面的说明的顺序是对应的,下同)
        SOCKET s;
        WSABUF DataBuf;           // 定义WSABUF结构的缓冲区,初始化一下DataBuf
        #define DATA_BUFSIZE 5096
        char buffer[DATA_BUFSIZE];
        ZeroMemory(buffer, DATA_BUFSIZE);
        DataBuf.len = DATA_BUFSIZE;
        DataBuf.buf = buffer;
        DWORD dwBufferCount = 1, dwRecvBytes = 0, Flags = 0;

         

        // 建立需要的重叠结构
        WSAOVERLAPPED AcceptOverlapped ;// 如果要处理多个操作,这里当然需要一个WSAOVERLAPPED数组
        WSAEVENT event;     // 如果要多个事件,这里当然也需要一个WSAEVENT数组。需要注意的是可能一个SOCKET同时会有一个以上的重叠请求,也就会对应一个以上的WSAEVENT
        Event = WSACreateEvent();
        ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
        AcceptOverlapped.hEvent = event;     // 关键的一步,把事件句柄“绑定”到重叠结构上作了这么多工作,终于可以使用WSARecv来把我们的请求投递到重叠结构上了,呼。。。。
        WSARecv(s, &DataBuf, dwBufferCount, &dwRecvBytes,
        &Flags, &AcceptOverlapped, NULL);
        其他的函数我这里就不一一介绍了,因为我们毕竟还有MSDN这么个好帮手,而且在讲后面的完成例程和完成端口的时候我还会讲到一些 ^_^
         
        3. WSAWaitForMultipleEvents 函数

        熟悉WSAEventSelect模型的朋友对这个函数肯定不会陌生,不对,其实大家都不应该陌生,这个函数与线程中常用的WaitForMultipleObjects函数有些地方还是比较像的,因为都是在等待某个事件的触发嘛。
        因为我们需要事件来通知我们重叠操作的完成,所以自然需要这个等待事件的函数与之配套。
        DWORD WSAWaitForMultipleEvents(
                 DWORD  cEvents,                        // 等候事件的总数量
                  const WSAEVENT* lphEvents,       // 事件数组的指针
                  BOOL     fWaitAll,     // 这个要多说两句:
                                             // 如果设置为 TRUE,则事件数组中所有事件被传信的时候函数才会返回
                                             // FALSE则任何一个事件被传信函数都要返回
                                             // 我们这里肯定是要设置为FALSE的
                  DWORD  dwTimeout,   // 超时时间,如果超时,函数会返回 WSA_WAIT_TIMEOUT
                                                 // 如果设置为0,函数会立即返回
                                                 // 如果设置为 WSA_INFINITE只有在某一个事件被传信后才会返回
                                                 // 在这里不建议设置为WSA_INFINITE,因为。。。后面再讲吧..-_-b
                  BOOL     fAlertable     // 在完成例程中会用到这个参数,这里我们先设置为FALSE
        );
        返回值:
            WSA_WAIT_TIMEOUT :最常见的返回值,我们需要做的就是继续Wait
            WSA_WAIT_FAILED :   出现了错误,请检查cEvents和lphEvents两个参数是否有效
            如果事件数组中有某一个事件被传信了,函数会返回这个事件的索引值,但是这个索引值需要减去预定义值 WSA_WAIT_EVENT_0才是这个事件在事件数组中的位置。
            具体的例子就先不在这里举了,后面还会讲到

        注意:WSAWaitForMultipleEvents函数只能支持由WSA_MAXIMUM_WAIT_EVENTS对象定义的一个最大值,是 64,就是说WSAWaitForMultipleEvents只能等待64个事件,如果想同时等待多于64个事件,就要 创建额外的工作者线程,就不得不去管理一个线程池,这一点就不如下一篇要讲到的完成例程模型了。

        4. WSAGetOverlappedResult 函数

        既然我们可以通过WSAWaitForMultipleEvents函数来得到重叠操作完成的通知,那么我们自然也需要一个函数来查询一下重叠操作的结果,定义如下         
         BOOL WSAGetOverlappedResult  (
                  SOCKET      s,                   // SOCKET,不用说了
                  LPWSAOVERLAPPED    lpOverlapped,  // 这里是我们想要查询结果的那个重叠结构的指针
                  LPDWORD   lpcbTransfer,     // 本次重叠操作的实际接收(或发送)的字节数
                  BOOL         fWait,               // 设置为TRUE,除非重叠操作完成,否则函数不会返回
                                                          // 设置FALSE,而且操作仍处于挂起状态,那么函数就会返回FALSE
                                                          // 错误为WSA_IO_INCOMPLETE
                                                          // 不过因为我们是等待事件传信来通知我们操作完成,所以我们这里设
                                                          // 置成什么都没有作用…..-_-b  别仍鸡蛋啊,我也想说得清楚一些…
                  LPDWORD  lpdwFlags       // 指向DWORD的指针,负责接收结果标志
        );
            这个函数没什么难的,这里我们也不需要去关注它的返回值,直接把参数填好调用就可以了,这里就先不举例了
        唯一需要注意一下的就是如果WSAGetOverlappedResult完成以后,第三个参数返回是 0 ,则说明通信对方已经关闭连接,我们这边的SOCKET, Event之类的也就可以关闭了。

        项目中待解决问题

           项目的时间越来越近了,听到公司的意向订单也越来越多,压力袭来,必须尽快了,还有一些待解决问题:

        • 电源管理中对电池电量的探测以及通知(公司为了节约成本,将本来用的一块电源管理芯片MC13783替换成了一个简单的供电芯片,很多东西都要自己来改。以后一定要记住,对功耗要求高的产品,前期一定要重视起电源管理!)
        • Winceshell的定制,需要解决将wince自带的UI屏蔽掉。
        • 处理音视频解码中,定时器对整个程序性能的影响问题。
        • 低功耗处理(这个是最难的了,我会另起一篇文章来写这个的)

          总的就是这么多了,想到就继续补充吧!
        04/07/2009

        WINCE应用的UI实现方案 —— 上篇:几种UI实现方案比较

        一、MFC的硬伤

        在接手现在这个项目之前,我对WINDOWS平台上的UI开发还是个白痴,除了MFC,就只知道GDI了。而且居然大言不惭地说用MFC只能画画灰色的对话框和按钮。但不论如何,在嵌入式这种对成本极度敏感的项目上,我是不会拍板用MFC的。假设极端情况,定制后的系统是31.8M,我放一个ARMV4I上的MFC DLL进去,大概500多K,那么只有两种选择,要么把32M的FLASH换成64M的——我的上司会把我给砍了,要么把应用层的UI代码全部重写——我的下属会把我给剁了。另一方面,WINCE上的应用软件我看过不少开源代码,也接触了一些外包的软件,还真没见过谁用MFC的。网上公论用MFC后会导致程序在不同平台上移植性降低,因为你不能指望别人的平台给你准备好奢侈的MFC。另一方面,多数高手都不屑用。我不是高手,但可以学人家摆谱,于是“不会用”就变成了“不屑用” ^_^

        二、GDI的痛苦

        把整套UI从CreateWindow开始写起,的确很累人。我写了500多行才勉强实现BUTTON类,另一个同事也用了500行左右才实现了TRACK BAR类,而且还未经测试,也没有很正式的CODE REVIEW。如果工业设计中心多增加几种图样,那么我们就得多些几个基类,然后再赔进去CODE REVIEW的时间、测试时间、BUG FIX的时间。不痛苦,那是不可能滴~。

        三、GWES的探路,我不是先锋

        群众的智慧是无穷的。当我这组同事的思维都受制于我的GDI方案时,从通信部过来协助完成项目的软件工程师从WINCE500的一个应用SAMPLE CODE里把DialogBox函数给抓出来了。我认为自己在定UI实现方案上很失败的一点就是习惯性思维地从eVC里建立DIALOG RESOURCE后,立刻就要去点Class Wizard, 然后就是关联MFC类。而他却画出来的DIALOG和BUTTON后,拿着RESOUCE ID从DialogBox函数建立起UI。并且我又习惯性思维地认为DialogBox并不在STANDARD SDK 500里面,但他确实从STANDARDSDK_500里不引用其它LIB和DLL就把DialogBox和BUTTON用起来了,然后过来找我谈论如何把图片叠加在DIALOG和BUTTON上。泪奔一百里~ 我应该去找块豆腐撞死~

        四、最后的攻关,GWES API能否成为我们需要的坚实地基
        GWES系列API能否实现我们所需的所有UI功能呢?没有人知道,需要评估。刚才起草稿时,我把这些都写在同一篇文章里了。现在觉得还是分篇好些,毕竟主题不同。

        嵌入式UI架构设计

        //========================================================================
        TITLE: 嵌入式UI架构设计漫谈
        AUTHOR: norains
        DATE: Friday 31-October-2008
        Environment:  NONE
        //========================================================================

            和桌面清一色的采用explorer不同,嵌入式设备更多的采用是自定义的简单UI,即使是含有explorer的wince也是如此。因为对于嵌入式设备而言,功能强大并不是主打,简单易用才是根本。以目前国内的手持车载设备为例,大部分的公司卖的都是硬件,利润很大一部分取决于硬件成本的多寡。并且,每个系列的产品都会有不同的外围器件,而这也决定了无法所有的产品都用同一个UI程序。
            
            虽然UI程序无法使用同一个,但从总体上而言,基本上是相同的;最有可能不同的地方无非是界面多了某些按钮,调用某些功能而已。另一方面,UI程序往往也需要配合产品的外观,风格尽可能和外观相符合。
            
            于是由此,基于可重用性考虑,嵌入式设备的UI基本上必须具有如下特点:
            1.界面更换方便
            2.功能增删方便
          
            下面我们就这两点具体到代码的层次去说说相应的设计。
            
            界面更换方便,这个方便不仅是对于程序编写者而言,也是针对图片设计者。如果是方案提供商,则后者显得更为重要。如果程序能够做到每次更换图片不需要重新编译,那么对于客户而言,他们只需要重新设计图片,然后替换就能立马看到效果。这点是非常重要的,如果每更换一次图片就必须要重编译,意味着多一个客户就会多一个烦恼。
            
            以读取BMP图片为例,最简单的方法就是将bmp图片导入到IDE环境的resource中,在使用的时候调用MAKEINTRESOURCE宏来获取相应的字符串地址即可。不过这会有一个非常严重的问题,因为图片是全部包含于可执行文件中,如果图片很多容量很大,那么单一的可执行文件的大小就会非常可观了。何况在wince中还会有个问题,如果程序体积大于8M,那么读取程序内部包含的图片将有可能会导致失败。
            
            鉴于以上原因,图片放在外部读取是最佳的选择。
            
            如果图片放在外部存储器,读取的速度将是一个不能忽略的问题。假使一张bmp图片的大小为800*480,然后再加上界面上的ICON,如果每次显示时都会分配缓冲然后绘制图片,那么会感觉到有延迟。一般像这种情况之下,我们都会选择一次读取,多次使用的方式。也就是说,只有第一次使用的使用才会将图片保存到缓冲区,以后都只是从这缓冲区获取图片数据而已。
            
            为了能够最大限度节省内存,以及使用的便利性,我们将对图片的读取和获取采用类封装的形式。最为简便的方式是,我们传入一个图片的序号,然后获取一个可供绘制的HDC。
            
            基本的形式概括如下;
            namespace ImageTab
            {
             enum ImageIndex
             {
              NONE,
              BKG_WND_MAIN,
              
              ...
              
              }
              
             struct ImageInfo
             {
              HDC hdc;
              SIZE size;
             }; 
            }

            class CImageTabBase
            {
            public:
             bool GetImageInfo(ImageTab::ImageIndex imgIndex, ImageTab::ImageInfo &imgInfo) ;
             
             ...
            }

            绘制图片时可以简单如此:
            ImageTab::ImageInfo imgInfo = {0};
            if(m_ImgTab.GetImageInfo(m_BkInfo.Image,imgInfo) != false)
            {
             StretchBlt(memDC.GetDC(),0,0,iWndWidth,iWndHeight,imgInfo.hdc,0,0,imgInfo.size.cx,imgInfo.size.cy,SRCCOPY);
            }

            使用类的方式还有一个好处,就是如果遇到图片架构变更,只要添加新的ImageTab类即可,甚至可以通过配置文件来确定当前运行的程序应该选用何种界面:
            switch(m_Option.GetImgTab())
           {
            case Option::IMG_A:
             m_pImgTab = new CImageTabA(); 
             break;
            case Option::IMG_B:
             m_pImgTab = new CImageTabB(); 
             break;
            default:
             m_pImgTab = new CImageTabA(); 
             break;
           }
           
           这对于需要频繁更改界面的需求无疑是一个比较好的方式。
           
           
           和图片类似,显示的文字也是一个比较重要的议题。不像桌面PC,日常使用中只需要一种语言。当然,对于嵌入式设备,平时确实也只是一种,但这只是对于用户而言;如果对于开发者,则必须考虑到多种语言如何方便性地共存。最典型的例子便是手机,普遍性地说,手机的系统语言都会有英文,简体中文和繁体中文选项。
           
           语言的切换其实很简单,关键在于方式的简便与否。
           
           最为笨拙的方法无非是直接采用switch方式:
           switch(language)
           {
             case EN:
              m_Info1.SetText(TEXT(""))
              break;
            case CHS:
              m_Info1.SetText(TEXT(""))
              break;
            case CHT:
              m_Info1.SetText(TEXT(""))
              break;
           }
           
           ....
           
           switch(language)
           {
             case EN:
              m_Info2.SetText(TEXT(""))
              break;
            case CHS:
              m_Info2.SetText(TEXT(""))
              break;
            case CHT:
              m_Info2.SetText(TEXT(""))
              break;
           }
           
           从代码中就可以很容易看见这种方式的弊端:每增加一个信息显示控件就必须增加一个switch语句块,每增加一个语言就必须要增加一个case语句。而对于嵌入式设备而言,增加新的控件和语言是常事,这弊端带来的结果就是不胜其烦的代码添加。更为严重的一个问题是,语言资源在代码编译阶段已经确定,如果是通过资源配置而达成文字的不同,则需要对源代码进行大量的更替。基于以上原因考虑,该方式为鸡肋。
           
           所以还是和图片方式一样,采用类封装的方式:

          namespace StrTab
          {
           enum Language
           {
            LANG_EN = 0x01,
            LANG_CHS,
            LANG_CHT
           };
           
           enum String
           { 
            STR_NONE,
            STR_INFO1,
            STR_INFO2,
          
              ....
           };
          }
          
          class CStrTabBase
          {
          public:
           void SetLanguage(StrTab::Language lang);
           virtual TSTRING GetString(StrTab::String strIndex);
          };

            采用类封装方式,之前通过switch语句更新资源的代码可以更改如下:
            CStrTabBase StrTab;
            
            ...
            
            //设置语言
            StrTab.SetLanguage(StrTab::LANG_CHS);
            
            ...
            
            //设置字符串
            m_Info1.SetText(StrTab.GetString(StrTab::STR_INFO1));            
            m_Info2.SetText(StrTab.GetString(StrTab::STR_INFO2));         
            
            这样的好处显而易见,语言只需要设置一次,然后文字设置可以避免采用大量的switch语句。还有另外一个不为人注意的好处是,字符串的设置只和CStrTabBase的GetString函数有联系,而不管其内部是如何获得的。也就是说,语言的资源即可以在编译时确定,也可以在运行时获取,但究竟采用何种方式,对于界面的字符串设置代码来说都是一致的,并不需要做任何更改。
            
            因为动态获取语言资源方式繁多,在此不再累赘,只是简单说说如何编译时期确定语言资源如何才能做到最简便。如果还是像之前采用switch块,则显得有点换汤不换药的味道。鉴于此,我们采用STL库的map。
            
            我们使用三个map变量,用来存储相应的语言资源:
            std::map<StrTab::String,TSTRING> mpChinesSimplified;
            mpChinesSimplified.insert(std::make_pair(StrTab::STR_TV,TEXT("移动电视")));
            m_mpString.insert(std::make_pair(StrTab::LANG_CHS,mpChinesSimplified));
            
            std::map<StrTab::String,TSTRING> mpChinesTraditional; 
            mpChinesTraditional.insert(std::make_pair(StrTab::STR_TV,TEXT("數位電視")));
            m_mpString.insert(std::make_pair(StrTab::LANG_CHT,mpChinesTraditional));
             
            std::map<StrTab::String,TSTRING> mpEnglish; 
            mpEnglish.insert(std::make_pair(StrTab::STR_TV,TEXT("TV")));
            m_mpString.insert(std::make_pair(StrTab::LANG_EN,mpEnglish));
            
            获取函数则可以非常简单:
            TSTRING CStrTabBase::GetString(StrTab::String strIndex)
            { 
             return (m_mpString[m_Lang])[strIndex];
            }
            
            以后当我们需要添加新的字符串资源时,只需要在初始化添加相应的字符串即可。这样不仅避免了大量的case语句,还能令代码条理清晰,方便简洁。
            
            细心的朋友可能发现,CImgTabBase和CStrTabBase有所不同。对于图片资源来说,不同的方案,表现的图片会不一样,比如说,同样代表“设置”的图标,可能给A公司和给B公司的完全不同(否则就撞车了);但文字资源,无论是A公司或是B公司,功能都叫“设置”。因此,图片类实际获取资源的是取决于子类,而文字资源则是基类。文字资源的子类,只是更改部分某些不同的数值而已。
            
            与此相对,一些常用的数值也可以先用类封装,方便之后的更改: 
            
            namespace ValTab
            {
             enum CtrlColor
             {
              COLOR_TXT_ITEM,
              COLOR_TXT_WND_TITLE,
              ...
             };
            
            
             enum CtrlSize
             {
              TXT_ITEM_WEIGHT,
              TXT_ITEM_POINT_SIZE,
                ...
             };
            
            }
            
            class CValTabBase
            {
            public:
             virtual COLORREF GetColor(ValTab::CtrlColor ctrlColor);
             virtual DWORD GetSize(ValTab::CtrlSize ctrlSize);
            
            };
            
            类似于此,很多随着环境会改变的数值,按钮的位置等等,都可以采用此形式封装。
            
            如果全部采用封装形式,每次添加新值只需要在初始化中添加相应的数值即可:

            BOOL CMainCtrl::Initialize(HINSTANCE hInstance)
            { 
             switch(m_Option.GetImgTab())
             {
              case Option::IMG_A:
               m_pImgTab = new CImageTabA(); 
               break;
              case Option::IMG_B:
               m_pImgTab = new CImageTabB(); 
               break;
              
               ...
               
              //如果有新值,在这里添加
              
               ...
              
              default:
               m_pImgTab = new CImageTabA(); 
               break;
             }
            
             switch(m_Option.GetPosTab())
             {
              case Option::POS_A:
               m_pPosTab = new CPosTabA();
               break;
              case Option::POS_B:
               m_pPosTab = new CPosTabB();
               break;
              
               ...
               
              //如果有新值,在这里添加
              
               ...
              
              default:
               m_pPosTab = new CPosTabA();
               break;
             }
            
             switch(m_Option.GetValTab())
             {
              case Option::VAL_A:
               m_pValTab = new CValTabA();
               break;
              case Option::VAL_B:
               m_pValTab = new CValTabB();
               break;
              
               ...
               
              //如果有新值,在这里添加
              
               ...
              
              default:
               m_pValTab = new CValTabA();
               break;
             }
            
             switch(m_Option.GetStrTab())
             {
              case Option::STR_A:
               m_pStrTab = new CStrTabA();
               break;
              case Option::STR_B:
               m_pStrTab = new CStrTabB();
               break;
              
               ...
               
              //如果有新值,在这里添加
              
               ...
              
              default:
               m_pStrTab = new CStrTabA();
               break;
             }
             m_pStrTab->SetLanguage(m_Option.GetLanguage());
            
            
             return TRUE;
            }

            万变不离其宗,对于windows程序而言,最主要的还是窗口。很多时候,大家常用的做法是一个界面,就写一个源代码文件。这样当然简单,但带来的问题就是代码重复度高,没增加一个窗口就要增加一个文件,显得很累赘。所以,关于窗口,我们是采用只用一个窗口类,通过设置不同的数值,来生成形式各异的界面。
            
            我们先来分析一下像这种简单UI程序不同界面的区别。一般来说,像界面无非是有如下控件:
            
            1.按钮:用来实现特定功能
            
            2.文本:用来显示不同的文字
            
            3.进度条:用来显示某些特殊功能的状态
            
            4.背景:窗口的背景图片。
            
            这四点,便是界面的共通点。所以我们在设计窗口类时,给这四点留出接口即可:
            
            class CUserWnd
            {
            public:
             BOOL SetButtonInfo(const std::vector<UserData::ButtonInfo> &vtBtnInfo);
             BOOL SetTextInfo(const std::vector<UserData::TextInfo> &vtTxtInfo); 
             BOOL SetProgressInfo(const std::vector<CProgress> &vtPrgInfo);
             BOOL SetBackground(UserData::BackgroundInfo bkInfo); 
              
              ...
            }
            
            然后对于不同功能的界面,我们只需要设置不同的数值即可:
            
            //背光窗口
            pWndBacklight->SetButtonInfo(vtBtnInfo1);
            pWndBacklight->SetTextInfo(vtTxtInfo1);
            pWndBacklight->SetBackground(bkInfo1);
            
            //电池窗口
            pWndBattery->SetButtonInfo(vtBtnInfo2);
            pWndBattery->SetTextInfo(vtTxtInfo2);
            pWndBattery->SetBackground(bkInfo2);
            
            这样我们只需要一个窗口类,就能实现变化各异的界面。
            
            在这里还有一点需要提一下,一般来说,我们显示界面和功能的实现应该分开,这样代码看起来就不会变得杂乱无章。我们可以采用这么一种做法,定义一个CCommand类,主要是执行按钮的相关功能操作,然后窗口类继承于该类即可:
            
            class CCommand
            {
            protected:  
             BOOL ExecuteCmd(UserData::CtrlIndex ctrlIndex,DWORD dwParam);
             
             ....
             
            }
            
            BOOL CCommand::ExecuteCmd(UserData::CtrlIndex ctrlIndex,DWORD dwParam)
            {
             switch(ctrlIndex)
             {   
              case UserData::BTN_EXIT:
              {
               return OnCmdExit();
              }
              case UserData::BTN_EXPLORER:
              {
               //Execute the explorer and then exit the application
               CCommon::Execute(m_Option.GetPathTab(Option::PATH_EXPLORER).c_str());
              }
              
              ...
              
             }
            }
            
            //窗口的继承
            class CUserWnd:
             public CCommand
            {
              ...
            }
            
            基本上,嵌入式UI架构的设计如此了。其实,只要能做到界面更换简单,功能添加简便,基本上往后的工作就会非常容易,所以初始的架构设计就显得非常重要了。
            
            
            
            后记:好久没写这种纯理论到连自己看了也觉得不知所云的东西了...文中的代码是从我所写的工程中搬出来的,直接用到别的地方肯定是行不通,所以大家仅仅是看看,做做参考就好。:-)

        18/04/2009

        整晚的失眠

        失眠...对于我来说挺遥远的,特别是因为情感问题!但是,昨天我失眠了,失得很彻底,很窝囊!我努力的寻找着我该处在的位置,我找不到...为什么我苦苦寻求的简单的生活方式却又要有这样的事情发生呢?到底是谁在耍我?

        07/04/2009

        本命年伊始:心烦意乱

        一年的时间很快过去,本命年到来了,强迫我的妹妹买了根红绳给我挂着...我妹妹问:哥,你还信这个啊?我心里默默的说:我索要的是祝福,不是迷信。所以我回家了。这一年的时间里,我越来越感觉到亲情的重要了,我工作和生活的绝大部分动力都来自于我的家庭。朋友见面的时间越来越少了,言语之间,话题在变,但还是很开心。

        今年年过得很开心,参加了同学的婚礼,见到了所有我想见的人。大家都有所改变,但是聊天时还不乏校园的一种稚气,只是越来越模糊了!不知道为什么,这些年来,想到同学的时候心里总是有一些伤感,总是再回忆过去。大家的生活渐行渐远,也不止大家是否一如既往的开心。

        就先写一点吧,其实也不知道该写些什么,也许是我自己太感伤了!

        21/06/2008

        生病

                 真的很奇怪,想来已提出离职就感冒。

        离忆

                  想起那些日子,不能回忆的是高考后的心理压力,同时还有的就是离别的痛苦,也让我不能自拔了好长一段的时间。但是,有段回忆却是彩色的,暂且叫它离忆吧。

        25/05/2008

        辞职

                  关于辞职,我已经想过很久了。从3月底部门架构重组后到现在,思考的时间也差不多历时一个月了。前天从公司的一大堆通告、表格中找到了《员工辞职申请表》,下定决心离职!

                  这是我的第一份工作,在即将离别之际,我不由的想起初由学校来深圳那些日子。毕业之处的茫然挂在每个人的脸上,当时的我们不断的宣泄着离别带来的痛苦,亦或是自我麻痹——我们每天都去学校后门的网吧,玩着游戏、吃便宜炒饭、聊通宵的天。这是我们选择的对将逝去的校园生活的一种祭奠。这不是堕落,也不是发泄,是抵抗,是依赖。这是在表达感情,没有人能要求什么,改变什么。最后的这段时光,随着分别的日子,随风飘走。走的那天,我清晰记得的眼泪也散落在了视野之外。这是我的追忆!

                  依稀记得在学校门口和涛的一段对话。

                  涛:毕业了有什么打算?

                  我:不知道,想还是想做网络方面的吧。你呢?

                  涛:我还是在南昌这边,先做段时间,看看朋友们那边怎么样。

                  我:朋友那边,做什么?

                  涛:我们家那边很多做铝合金生意的,他们比我早出来,现在已经很不错了,可能会先跟着他们混。

                  我:哦,我还是那个想法,想做3G方面的东西,找个这方面的工作吧。

                  涛:恩,你还是可以的,努力去做啊!

                  我:恩,一起加油。

                  这其中其实已经是最简陋的一个“职业规划”了,我们都有各自的想法。不知道涛哥现在还记不记得,我一直都记得。这样的交流在同学们之间都很多,我知道,但很模糊。这想法随着我的旅程也到了深圳,在这城市的第四天便去书城买了《移动通信类职位应聘指南》这本书,虽然其中的内容大多跟我的专业相差比较远,但是我看到移动通信产业链中sp这一块,这是我为之兴奋的一点。随后,我开始跑人才市场、投简历、面试,然后循环这一过程,我逐渐适应,但是慢慢的忘却了想法。就业——是我那个时候的必要任务。当时的日子我想众众应该印象深刻,这里我不想说,以后再写吧!

                   我想天道酬勤是不会错的,一面、笔试、二面一路pass第一份工作就找到了,而且一干就是一年。这一年工作中的心态也经历了几种转变。重一开始的充满激情,到对工作出现不满,一直到现在发现我真的不适合做着份工作,我开始了新的思考。这段时间以来,我一直在回溯我的记忆,找寻着自我。在大学里,都在摸索着,但是现在怎么就不对劲呢?一句话无意中点醒了我——知之者不如好之者,好之者不如乐之者。我的兴趣不在这里,我的工作不应该是那些,辞职!

                   最近,阿甘这边沟通也很多,他的想法要稍多些,我毕竟不在这个行当里,想法自然少些。但是我们对我们的目标少了些激情,我不认为这是件坏事,至少我们现在都理智了很多。对待一些已有的想法也更趋于实际。但我还是担心的,因为他说过我们不能共事,说实话,我一直都猜不透这句话。这次我回南昌为的就是这个事情!

                   兄弟们,记得自己曾经想要的,去追求吧,厚积薄发!

        04/05/2008

        忍者神龟

                昨天我看了07年上映的美国动画片《忍者神龟》。其实,这部电影我早就想看了,只是当时上映的时间正好是我毕业的时间,没有时间去看。想看这部影片的初衷很简单,小时候我们经常玩这个游戏,所以有点怀旧的味道在里面。

               这次看感觉还不错,也找到了我应有的回忆。惯使武士刀的“蓝霹雳”莱昂纳多、手持木棒的“爱因斯坦”多纳泰罗、暗藏忍者匕首的“火爆红”拉斐尔和双节棍舞得虎虎有声的“掉链子”米开朗琪罗,利落身手和聪明头脑比任何超级英雄都毫不逊色的小龟们,个个都是个性十足、忍者功夫精到的全能高手。

                                             忍者神龟

               除此之外,我也还发现不少的东西。莱昂与拉斐尔之间的矛盾体现了真正的友谊、四兄弟的并肩作战体现了什么是团队精神、忍者的修炼之路体现了什么是奋斗。这些都是在现实中不那么清晰的东西。其中的人物性格刻画我也十分喜欢,莱昂的自负、多纳泰罗的责任心、米开朗琪罗真实的自我和拉斐尔的执着都是让我心生共鸣的地方。

                                             莱昂

                如此性格迥异的四只乌龟却能为了一个共同的目标而团结再一起,进而去做自己想做的事情,这是让我感动的地方。鼠老大说:“你的兄弟都有优点和弱点,当他们处于弱势时你就应强大起来,如果你不能认识到这个,那么这个家没有希望..”,这句话让我感觉到什么是真正的兄弟。是的,我也有我的兄弟,我们也有着共同的理想,但是我们有点在生活中迷失了自我,正如影片开始的时候那样。记住自己心中所想的,如拉斐尔一样的执着,我想是会有再聚在一起的一天的!我们都在修炼,蓄势待发!

                                             四兄弟

        01/05/2008

        间断性思维

               这个词是在上班的时候突然想到的,想到了就记下来了。其实我想这个词是最能表达我这段时间以来做事的一个方式了。

               很多的时候,我的思维和我行为完全的脱离开来了。而思维一直都是断断续续的分散在行为中间,一段一段的......我想这个就是我对待生活比较疲软的原因吧!清晰的思路常常出现在起床去上班的时候、中午休息的时候和晚上躺在床上的时候,这个时候我才想着我最想要的是什么,我所追求的是什么。但是,一旦我进入到工作中,甚至是娱乐中,我会忘记那些我所想!我不知道我这是为什么,我想我是害怕了,害怕这些太遥远,不是我现在的生活所能够给予的,正如我害怕开始一样。想变......

               我想,我乱......

               不止一次的朋友和我谈起关于职业的方向选择的事,我想我是有一个坚定的立场的,我想。这些思路也在我的脑海里以间断性的思维不断的出现,我是相信我仍然相信的,我是要回到我的兴趣所在的。而我这段时间以来,很多羁绊,社会经验不丰富也好、职场经验不足也好、经济条件制约也好、自己害怕也好,我想我是习惯这些了。

        21/04/2008

        状态

              一直都在关注却未改变过的东西,这是我对我目前的状态下的一个定义。

        恍惚的思念

                  天气莫名其妙的好,心情也跟着很好。太阳射进窗户的那一刹那,我还静静的躺在床上,很舒服,很温暖!躺在窗上静静的回忆的,思绪断断续续。冬天深圳的阳光难得有这样的柔和的,让我想起了在学校。工作就是这样,让人忘却的东西太多了,你的激情,你的精力,你所在乎的东西都在一点一点的消失,患得患失!
        22/04/2007

        在百度的日子里

            时间过得真的挺快的`等我想起来这里写点什么的时候,我已经身在深圳了``也许决定来深圳的理由很简单-IT公司多达1650多家,俗话说瘦死的骆驼比马大,所以我就来了``但是我的决定是没有错的,深圳是一个移民城市,不象北京、上海存在“歧视”问题。而且深圳是经济特区,一个在高度发展中的城市,对我来说这是一个不错的选择。
            刚下火车那会,并没有意料中的那种激动与兴奋,相反的是心情极度的平静。也许,是因为我没有怀着一个大的目标,一个远大的理想才来到这个城市的关系吧,我只想在这样一个拥挤的城市里找到一个属于我空间。我量力而行。
            心情上的平静不能取代随之而来的焦虑与紧张,因为一切都是这样的陌生,这样的不可控制。我努力的去调整自己的心态,不断的给自己鼓励,希望自己能有一个好的开始。在众众的表姐家呆了两天后,我转住到了我叔叔家,那边离人才市场会更近一些,这样我不用每天的就这样的来回的跑了。准备好了行头之后(所谓的行头就衬衫、西裤和皮鞋  注:皮鞋被宰花了275心痛啊~~),便开始跑人才市场了。
            初八一大早,我和众众带着上个世纪的简历冲到了人才大市场,脑袋一片空白。在这样一个拥挤不堪,人头篡动的空间里,我感觉到了竞争。
        五元一张门票这样的现场招聘会天天都有,但是好象找工作的永远比提供的岗位多得多。
        14/12/2006

            今天算是忙了一天了,为了画一个系统结构图上网兜了一圈,想找一个专业的工具来描述一个系统。开始,我想到的是UML,可是OOD中没有系统结构图这个概念,而且UML中的视图(view)是对软件系统进行面对对象的描述和建模,而我是想通过系统分析建立一个系统的物理模型,类似系统的数据流图中的顶层图,通过这个图来对系统做个简要的描述。遍历了baidu之后,我发现了一个工具-Microsoft Office Visio 2007 -他是一款商用和科技图表制作程序,应用的范围很广,从家居规划到工程制图,从各种软件和数据库视图到商务的工作流程图甚至是营销图标,可以说是囊括所有的商业应用图表。下载...安装...打开来,没有我需要的系统结构图,但是研究了一会之后发现,它里面给出视图都是以模板的形式给出的,也就是说你可以创建自己需要的图表,嘿嘿,开工了```为了节省时间,我用了程序结构图做为模板进行绘制的。我所要描述的是一个搜索引擎系统,下面就是它的系统结构图了:
         
             简要的说明一下,系统分为两个部分:
                1. Front-end process
                2. Back-end  process  

             前端用来处理用户的查询请求,后台用来对用户的请求做出分析,然后在Index Files中查找到相应的数据对数据做出排序,再把结果返回给用户,整个图是按照数据流来绘制的。

             通过这个图的绘制,个人感觉visio还是一个不错的软件,它能够很好的诠释你的想法,过程和系统,让你更好的完成整个系统的设计与分析。

        10/12/2006

        在台灯下度过的日子

              嗯...扳手指头一算,这个星期来已经通宵了4,5天了,白天几乎都是在睡眠中度过的,晚上陪伴我的就只有我那盏小台灯了。在这样阴冷的天气里,有一个功率40w的取暖器还真的挺舒服的,而且我也很喜欢它泛出的那种柔和的黄光,让我有一种思念的感觉,孤独而又温暖。
             最近一段时间都在"忙"着写论文,为什么要打双引号呢!这个我身边的人都知道,这个忙是要打折扣的。譬如,有人问你:嗨!最近在忙什么呢? 这个时候在你的大脑里浮现的肯定是对你,或者是对你的生活起着促进作用的事情,而现在我唯一要做的也就是好好的把毕业论文给写完了。(至于什么好好学习,天天向上之类的都是些后话了,在很长一段时间内我根本都不想提及这些)。
            提及论文还真有些话是要说的。先说说给我们安排论文分组的计算机系秘书吧。丫就是一噱头,论文按你的姓氏来分组,根本不顾及你的专业方向和本人的意向,很不幸的我(网络模块)被分到了一个研究软件的小组里了,课题也和我的网络一点关系也没有,完全就是软件领域的课题。当我很气愤地跑到系办公室抗议:这不公平!教学秘书一句话:“你觉得这个社会什么对你来说才是公平的呢?”顿时,我无言以对。

        基于JDK1.5.0+tomcat5.0+Eclipse3.2.1的web应用程序开发平台的搭建

              最近由于论文的需要,所以要搭建一个web应用程序的开发环境,程序采用java进行开发,所以下面对整个应用程序的开发环境的配置做个介绍:
         
             1. JDK1.5(Java Development Kit)
         
             这是SUN公司的一个开发工具集,它为java提供了一个丰富的语言和运行环境,开发人员和最终用户都可以利用这个工具来开发java程序。
            
         
             下载完成后开始安装JDK1.5,下载下来的是一个安装文件,双击它就可以安装,安装过程比较简单基本上一路next就可以了,不过要注意下你的安装目录还有你所使用的浏览器。之后会要求你重起系统,这里建议先不要同意,等JDK的环境变量配置完成后再重新启动计算机。
         
             JDK配置:
                  1. 用鼠标右键单击桌面上的我的电脑,在弹出的快捷菜单中选择“属性”选项。(这里我就不贴图了,麻烦 -_-! 我尽量写详细些吧)
                  2. 在弹出的“系统属性”中选择“高级”选项卡中的“环境变量”按钮,弹出“环境变量”对话框。
         
                  3. 单击“系统变量”区域中的新建按钮,在弹出的“新建系统变量”对话框中,添加如下环境变量 (假定JDK安装在D:\java\jdk1.5.0)
                             变量名: CLASSPATH
                             变量值: D:\Java\jdk1.5.0\;D:\Java\jdk1.5.0\lib\tools.jar;D:\Java\jdk1.5.0\lib\dt.jar;D:\Java\jdk1.5.0\bin
         
                  4. 选中“系统变量”区域中的Path变量,在弹出的“编辑系统变量”对话框中为path变量,追加如下变量值:
                             变量名: PATH
                             变量值: D:\Java\jdk1.5.0\bin\
                               
             配置完成后,重新启动计算机,ok~ JDK配置完毕。
         
             2. Tomcat5.0
         
             Tomcat是Apache Group Jakarta 小组开发的支持JSP和Servlet的免费服务器软件。
         
         
              tomcat的安装比较简单,一路next就ok了,需要注意的是tomcat服务的启动需要jdk,因为tomcat本身就是用java编写的程序,所以程序的运行就需要java解释器。
         
              tomcat配置:
                   1. 安装Tomcat后,在我的电脑->属性->高级->环境变量->系统变量中添加以下环境变量(假定你的tomcat安装在c:\tomcat):
          
                CATALINA_HOME=c:\tomcat
                CATALINA_BASE=c:\tomcat
          
                2. 然后修改环境变量中的classpath,把tomat安装目录下的common\lib下的(可以根据实际追加)servlet.jar追加到classpath中去,修改后的classpath如下 (%JAVA_HOME%指的是JDK的安装目录):
          
                classpath=.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;%CATALINA_HOME%\common\lib\servlet.jar;
          
                3. 接着可以启动tomcat,在IE中访问http://localhost:8080,如果看到tomcat的欢迎页面的话说明安装成功了。
             
              3. Eclipse
            
             Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括 Java 开发工具(Java Development Tools,JDT)。
         
              下载地址:http://www.eclipse.org/downloads
         
              下载下来的将是一个压缩包,解压之后就可以运行了,不需要安装。
         
              从eclipse官方网站下载的只带有基本的几个插件,如果要多种J2EE的元素、Web应用的开发和最流行的应用服务器结合为一体就要向eclipse中添加插件,现在网上这样的插件有很多,这里我推荐使用lomboz(它Eclipse的一个主要的开源插件(open-source plug-in),Lomboz插件能够使Java开发者更好的使用Eclipse去创建,调试和部署一个100%基于J2EEJava应用服务器。)
         
            至于如何向eclipse中添加lomboz这样的文章网上有很多,稍微baidu一下就可以找到答案了,我就不在这里敖述了。这里我要介绍一个IBM的Callisto Simultaneous Release project,这个是IBM的一个developerWorks,它提供了带有集成开发环境插件包的eclips它们分别是:
              • Web Tools Platform (WTP)
              • Graphical Modeling Framework (GMF)
              • Test and Performance Tools Platform (TPTP)

            其中第一项就是关于web应用程序开发的,我们可以下载,地址是:http://www-128.ibm.com/developerworks/eclipse。这其中已经包括了web应用程序的开发环境了,不用进行任何多余的配置了,挺爽的,功能比Lomboz还要多些。 (o.O 呵呵,走了一条捷径~~)

            这样一个基于JDK1.5.0+Tomcat5.0+Eclipse3.2.1的web应用程序开发平台基本上就搭建完毕了,呼呼~~~~