| 
 通过前两篇文章的介绍,我们已经知道NBOOT用来引导EBOOT,继而EBOOT加载并引导WinCE操作系统(NK)。那么,WinCE6.0的启动过程又是怎样的呢?本文基于S3C2410的平台做一个详细的分析。需要说明的是,WinCE6.0的整个启动过程对于同一类型的MCU来说大同小异,如S3C2410和PXA270同属ARM平台的MCU,所以他们的启动过程是类似的,可以说唯一的不同就在OAL处,而WinCE操作系统的启动正是从OAL开始的。 
     OAL(OEM Adaptation Layer)即OEM适配层,它的主要作用是在移植WinCE到新的硬件平台时减少操作系统的修改,通俗的说就是为WinCE操作系统抹平MCU的差异,使其能很方便的在不同MCU上运行。所以,OAL包括了和系统硬件通讯的最底层代码。内核则通过OAL跟硬件进行交互。逻辑上,OAL是介于CE内核和设备硬件之间的一个代码层,是一个抽象的概念。物理上,OAL和其他一些库一起链接成可执行文件,在WinCE6.0中对应的文件是OAL.exe,这是OAL的客观存在。WinCE6.0中的OAL跟先前的OAL比,是有一些变化的,它从内核中分离出来成为OAL.exe,而内核则变成了Kernel.dll。这样做的好处是可以单独升级OAL。但整体的OAL结构并没有改变,OEM函数保持一致,OAL和Kernel的接口由共享结构NKGLOBAL实现。这一部分的具体内容下一篇再做介绍。下图所示为WinCE6.0的OAL设计。 
        在移植WinCE到新的硬件平台时,创建OAL是最复杂的任务之一。一般来说,最简单的方法是拷贝一个跟新的硬件平台类似的且成熟的OAL,然后根据硬件的不同进行修改,使其满足目标硬件的特定要求。这里不展开说明,回头再单独整理。 
     从EBOOT到OAL.exe的跳转是从OEMLaunch()开始的,函数OEMLaunch()中调用Launch(dwPhysLaunchAddr),它的实现代码如下:   
LEAF_ENTRY Launch 
 
    ldr    r2, = PhysicalStart 
    ldr     r3, = (VIR_RAM_START - PHY_RAM_START) 
 
    sub     r2, r2, r3 
 
    mov     r1, #0x0070             ; Disable MMU 
    mcr     p15, 0, r1, c1, c0, 0 
    nop 
    mov     pc, r2                  ; Jump to PStart 
    nop 
 
    ; MMU & caches now disabled. 
 
PhysicalStart 
 
    mov     r2, #0 
    mcr     p15, 0, r2, c8, c7, 0   ; Flush the TLB 
    mov     pc, r0            ; Jump to program we are launching. 
    函数Launch()的参数为物理地址,因为在跳转之前已将MMU关闭。该地址可通过VIEWBIN来查看,如下图所示: 
       
     如何确定这个地址对应的是NK.bin中的哪一个文件呢,先前说是OAL.exe,证据何在。在PB6.0中增加了浏览NK.bin的功能,我们可以利用此功能查看NK.bin的详细情况,如下图所示:       
  
     从上图中可以看出0x80205394处对应的是NK.exe,而这里的NK.exe即为OAL.exe。 
     至此,我们已经知道EBOOT是如何跳转到OAL.exe中的了。接下来继续看OAL.exe的执行过程。 
     OAL的启动代码如下:OAL的启动代码和EBOOT的启动代码经常复用,但为了代码的简洁,最好还是分开实现,而且在EBOOT中如果已经初始化了相关硬件,那么OAL的启动代码就可以省去那部分工作,可以很简练,如上面的代码所示。
   
LEAF_ENTRY StartUp 
 
        ; Compute the OEMAddressTable's physical address and  
        ; load it into r0. KernelStart expects r0 to contain 
        ; the physical address of this table. The MMU isn't  
        ; turned on until well into KernelStart.   
 
        add     r0, pc, #g_oalAddressTable - (. + 8) 
        bl      KernelStart 
 
可以看出,OAL的启动代码又调用了函数KernelStart(),而这个函数是在文件C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/LDR/ARM/armstart.s中实现的,代码如下:从上面的代码可以看出,KernelStart()通过OEMAddressTable初始化了MMU,然后通过调用函数ARMInit()获得kernel.dll的入口点,最后跳转到kernel.dll的入口点处。   
LEAF_ENTRY KernelStart 
 
        mov     r11, r0                         ; (r11) = &OEMAddressTable (save pointer) 
 
        ; figure out the virtual address of OEMAddressTable 
        mov     r1, r11                         ; (r1) = &OEMAddressTable (2nd argument to VaFromPa) 
        bl      VaFromPa 
        mov     r6, r0                          ; (r6) = VA of OEMAddressTable 
 
        ; convert base of PTs to Physical address 
        ldr     r4, =PTs                        ; (r4) = virtual address of FirstPT 
        mov     r0, r4                          ; (r0) = virtual address of FirstPT 
        mov     r1, r11                         ; (r1) = &OEMAddressTable (2nd argument to PaFromVa) 
        bl      PaFromVa 
 
        mov     r10, r0                         ; (r10) = ptr to FirstPT (physical) 
 
;       Zero out page tables & kernel data page 
 
        mov     r0, #0                          ; (r0-r3) = 0's to store 
        mov     r1, #0 
        mov     r2, #0 
        mov     r3, #0 
        mov     r4, r10                         ; (r4) = first address to clear 
        add     r5, r10, #KDEnd-PTs             ; (r5) = last address + 1 
18      stmia   r4!, {r0-r3} 
        stmia   r4!, {r0-r3} 
        cmp     r4, r5 
        blo     %B18 
 
        ; read the architecture information 
        bl      GetCpuId 
        mov     r5, r0 LSR #16                  ; r5 >>= 16 
        and     r5, r5, #0x0000000f             ; r5 &= 0x0000000f == architecture id 
         
;       Setup 2nd level page table to map the high memory area which contains the 
; first level page table, 2nd level page tables, kernel data page, etc. 
;       (r5) = architecture id 
 
        add     r4, r10, #HighPT-PTs            ; (r4) = ptr to high page table 
 
        cmp     r5, #ARMv6                      ; v6 or later? 
; ARMV6_MMU 
        orrge   r0, r10, #PTL2_KRW + PTL2_SMALL_PAGE + ARMV6_MMU_PTL2_SMALL_XN 
                                                ; (r0) = PTE for 4K, kr/w u-/- page, uncached unbuffered, nonexecutable 
; PRE ARMV6_MMU 
        orrlt   r0, r10, #PTL2_KRW + (PTL2_KRW << 2) + (PTL2_KRW << 4) + (PTL2_KRW << 6) 
                                                ; Need to replicate AP bits into all 4 fields 
        orrlt   r0, r0,  #PTL2_SMALL_PAGE + PREARMV6_MMU_PTL2_SMALL_XN 
                                                ; (r0) = PTE for 4K, kr/w u-/- page, uncached unbuffered, nonexecutable 
        str     r0, [r4, #0xD0*4]               ; store the entry into 4 slots to map 16K of primary page table 
        add     r0, r0, #0x1000                 ; step on the physical address 
        str     r0, [r4, #0xD1*4] 
        add     r0, r0, #0x1000                 ; step on the physical address 
        str     r0, [r4, #0xD2*4] 
        add     r0, r0, #0x1000                 ; step on the physical address 
        str     r0, [r4, #0xD3*4] 
 
        add     r8, r10, #ExceptionVectors-PTs  ; (r8) = ptr to vector page 
        orr     r0, r8, #PTL2_SMALL_PAGE        ; construct the PTE (C=B=0) 
 
;; The exception stacks and the vectors are mapped as a single kr/w page. 
;; Any alternative will use more physical memory. 
;; Multiple mappings don't provide any real protection: if the vectors were in a r/o page, 
;; they could still be corrupted via the kr/w setting required for the stacks. 
        cmp     r5, #ARMv6                      ; v6 or later? 
; ARMV6_MMU  
        orrge   r0, r0, #PTL2_KRW 
; PRE ARMV6_MMU 
        orrlt   r0, r0, #PTL2_KRW + (PTL2_KRW << 2) + (PTL2_KRW << 4) + (PTL2_KRW << 6) 
                                                ; Need to replicate AP bits into all 4 fields for pre-V6 MMU 
 
        str     r0, [r4, #0xF0*4]               ; store entry for exception stacks and vectors 
                                                ; other 3 entries now unused 
 
        add     r9, r10, #KPage-PTs             ; (r9) = ptr to kdata page 
        orr     r0, r9, #PTL2_SMALL_PAGE        ; (r0)=PTE for 4K (C=B=0) 
         
; ARMV6_MMU (condition codes still set) 
        orrge   r0, r0, #PTL2_KRW_URO           ; No subpage access control, so we must set this all to kr/w+ur/o 
; PRE ARMV6_MMU 
        orrlt   r0, r0, #(PTL2_KRW << 0) + (PTL2_KRW << 2) + (PTL2_KRW_URO << 4) 
                                                ; (r0) = set perms kr/w kr/w kr/w+ur/o r/o 
        str     r0, [r4, #0xFC*4]               ; store entry for kernel data page 
        orr     r0, r4, #PTL1_2Y_TABLE          ; (r0) = 1st level PTE for high memory section 
        add     r1, r10, #0x4000 
        str     r0, [r1, #-4]                   ; store PTE in last slot of 1st level table 
 
;       Fill in first level page table entries to create "statically mapped" regions 
; from the contents of the OEMAddressTable array. 
; 
;       (r5) = architecture id 
;       (r9) = ptr to KData page 
;       (r10) = ptr to 1st level page table 
;       (r11) = ptr to OEMAddressTable array 
 
        add     r10, r10, #0x2000               ; (r10) = ptr to 1st PTE for "unmapped space" 
 
        mov     r0, #PTL1_SECTION 
        orr     r0, r0, #PTL1_KRW               ; (r0)=PTE for 0: 1MB (C=B=0, kernel r/w) 
20      mov     r1, r11                         ; (r1) = ptr to OEMAddressTable array (physical) 
 
 
25      ldr     r2, [r1], #4                    ; (r2) = virtual address to map Bank at 
        ldr     r3, [r1], #4                    ; (r3) = physical address to map from 
        ldr     r4, [r1], #4                    ; (r4) = num MB to map 
 
        cmp     r4, #0                          ; End of table? 
        beq     %F29 
 
        ldr     r12, =0x1FF00000 
        and     r2, r2, r12                      ; VA needs 512MB, 1MB aligned. 
 
        ldr     r12, =0xFFF00000 
        and     r3, r3, r12                      ; PA needs 4GB, 1MB aligned. 
 
        add     r2, r10, r2, LSR #18 
        add     r0, r0, r3                      ; (r0) = PTE for next physical page 
 
28      str     r0, [r2], #4 
        add     r0, r0, #0x00100000             ; (r0) = PTE for next physical page 
 
        sub     r4, r4, #1                      ; Decrement number of MB left 
        cmp     r4, #0 
        bne     %B28                            ; Map next MB 
 
        bic     r0, r0, #0xF0000000             ; Clear Section Base Address Field 
        bic     r0, r0, #0x0FF00000             ; Clear Section Base Address Field 
        b       %B25                            ; Get next element 
 
 
29 
        sub     r10, r10, #0x2000               ; (r10) = restore address of 1st level page table 
 
        ; The minimal page mappings are setup. Initialize the MMU and turn it on. 
 
        ; there are some CPUs with pipeline issues that requires identity mapping before turning on MMU. 
        ; We'll create an identity mapping for the address we'll jump to when turning on MMU on and remove 
        ; the mapping after we turn on MMU and running on Virtual address. 
         
 
        ldr     r12, =0xFFF00000                ; (r12) = mask for section bits 
        and     r1, pc, r12                     ; physical address of where we are  
                                                ; NOTE: we assume that the KernelStart function never spam across 1M boundary. 
        orr     r0, r1, #PTL1_SECTION 
        orr     r0, r0, #PTL1_KRW               ; (r0) = PTE for 1M for current physical address, C=B=0, kernel r/w 
        add     r7, r10, r1, LSR #18            ; (r7) = 1st level PT entry for the identity map 
        ldr     r8, [r7]                        ; (r8) = saved content of the 1st-level PT 
        str     r0, [r7]                        ; create the identity map 
 
        mov     r1, #1 
        mtc15   r1, c3                          ; Setup access to domain 0 and clear other 
        mtc15   r10, c2                         ; setup translation base (physical of 1st level PT) 
 
        mov     r0, #0 
        mcr     p15, 0, r0, c8, c7, 0           ; Flush the I&D TLBs 
 
        mfc15   r1, c1 
        orr     r1, r1, #0x007F                 ; changed to read-mod-write for ARM920 Enable: MMU, Align, DCache, WriteBuffer 
 
        cmp     r5, #ARMv6                      ; r5 still set         
; ARMV6_MMU 
        orrge   r1, r1, #0x3000                 ; vector adjust, ICache 
        orrge   r1, r1, #1<<23                  ; V6-format page tables 
        orrge   r1, r1, #ARMV6_U_BIT            ; V6-set U bit, let A bit control unalignment support 
; PRE ARMV6_MMU 
        orrlt   r1, r1, #0x3200                 ; vector adjust, ICache, ROM protection 
 
        ldr     r0, VirtualStart 
        cmp     r0, #0                          ; make sure no stall on "mov pc,r0" below 
        mtc15   r1, c1                          ; enable the MMU & Caches 
        mov     pc, r0                          ;  & jump to new virtual address 
        nop 
 
; MMU & caches now enabled. 
; 
;       (r10) = physcial address of 1st level page table 
;       (r7)  = entry in 1st level PT for identity map 
;       (r8)  = saved 1st level PT save at (r7) 
VStart  ldr     r2, =FirstPT                    ; (r2) = VA of 1st level PT 
        sub     r7, r7, r10                     ; (r7) = offset into 1st-level PT 
        str     r8, [r2, r7]                    ; restore the temporary identity map 
        mcr     p15, 0, r0, c8, c7, 0           ; Flush the I&D TLBs 
 
; 
; setup stack for each modes: current mode = supervisor mode 
; 
        ldr     sp, =KStack 
        add     r4, sp, #KData-KStack           ; (r4) = ptr to KDataStruct 
 
        ; setup ABORT stack 
        mov     r1, #ABORT_MODE:OR:0xC0 
        msr     cpsr_c, r1                      ; switch to Abort Mode w/IRQs disabled 
        add     sp, r4, #AbortStack-KData 
 
        ; setup IRQ stack 
        mov     r2, #IRQ_MODE:OR:0xC0 
        msr     cpsr_c, r2                      ; switch to IRQ Mode w/IRQs disabled 
        add     sp, r4, #IntStack-KData 
 
        ; setup FIQ stack 
        mov     r3, #FIQ_MODE:OR:0xC0 
        msr     cpsr_c, r3                      ; switch to FIQ Mode w/IRQs disabled 
        add     sp, r4, #FIQStack-KData 
 
        ; setup UNDEF stack 
        mov     r3,  #UNDEF_MODE:OR:0xC0 
        msr     cpsr_c, r3                      ; switch to Undefined Mode w/IRQs disabled 
        mov     sp, r4                          ; (sp_undef) = &KData 
 
        ; switch back to Supervisor mode 
        mov     r0, #SVC_MODE:OR:0xC0 
        msr     cpsr_c, r0                      ; switch to Supervisor Mode w/IRQs disabled 
        ldr     sp, =KStack 
 
        ; continue initialization in C 
        add     r0, sp, #KData-KStack           ; (r0) = ptr to KDataStruct 
        str     r6, [r0, #pAddrMap]             ; store VA of OEMAddressTable in KData 
        bl      ARMInit          ; call C function to perform the rest of initializations 
        ; upon return, (r0) = entry point of kernel.dll 
 
        mov     r12, r0 
        ldr     r0, =KData 
        mov     pc, r12     ; jump to entry of kernel.dll 
 
为了找到Kernel.dll的入口点,用IDA反汇编kernel.dll文件,可以看到,Kernel.dll的入口点为NKStartup。 NKStartup()的实现在文件C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/ mdarm.c中,代码如下: NKStartup()的代码就不多解释了,注释已经很详细。该函数的最后又调用了KernelStart ()函数。注意这里的KernelStart()跟上面曾提到的KernelStart()是不一样的。这里KernelStart()的实现在文件C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/armtrap.s中,代码和反汇编的对比如下图所示。            
     可以看到,这里调用了KernelInit()和FirstSchedule()这两个函数。先说FirstSchedule(),它开始了WinCE6.0的第一个调度。它的实现跟KernelStart()在同一文件中,而实现代码跟WinCE5.0中完全一样。接下来,我们继续跟踪KernelInit()函数,其实现在文件C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/nkinit.c中,代码如下:    
// 
// NKStartup - entry point of kernel.dll. 
// 
// NK Loader setup only the minimal mappings, which includes ARMHigh area, and the cached static mapping area, 
// with *EVERYTHING UNCACHED*. Interrupt vectors are not setup either. So, the init sequence reqiures: 
// (1) pickup data passed from nk loader 
// (2) Find entry point of oal, exchange globals, find out the cache mode. 
// (3) fill in the rest of static mapped area (0xa0000000 - 0xbfffffff), PSL faulting address, interrupt vectors, 
//     mod stacks, etc. Then, change the 'cached' static mapping area to use cache, and flush I&D TLB. 
// (4) continue normal loading of kernel (find KITLdll, call OEMInitDebugSerial, etc.) 
// 
void NKStartup (struct KDataStruct * pKData) 
{ 
    PFN_OEMInitGlobals pfnInitGlob; 
    PFN_DllMain pfnKitlEntry; 
    DWORD dwCpuId = GetCpuId (); 
 
    // (1) pickup arguments from the nk loader 
    g_pKData            = pKData; 
    pTOC                = (const ROMHDR *) pKData->dwTOCAddr; 
    g_pOEMAddressTable  = (PADDRMAP) pKData->pAddrMap; 
 
    /* get architecture id and update page protection attributes */ 
    pKData->dwArchitectureId = (dwCpuId >> 16) & 0xf; 
    if (pKData->dwArchitectureId >= ARMArchitectureV6) { 
        // v6 or later 
        pKData->dwProtMask = PG_V6_PROTECTION; 
        pKData->dwRead     = PG_V6_PROT_READ; 
        pKData->dwWrite    = PG_V6_PROT_WRITE; 
        pKData->dwKrwUro   = PG_V6_PROT_URO_KRW; 
        pKData->dwKrwUno   = PG_V6_PROT_UNO_KRW; 
 
    } else { 
        // pre-v6 
        pKData->dwProtMask = PG_V4_PROTECTION; 
        pKData->dwRead     = PG_V4_PROT_READ; 
        pKData->dwWrite    = PG_V4_PROT_WRITE; 
        pKData->dwKrwUro   = PG_V4_PROT_URO_KRW; 
        pKData->dwKrwUno   = PG_V4_PROT_UNO_KRW; 
    } 
 
    // initialize nk globals 
    FirstROM.pTOC       = (ROMHDR *) pTOC; 
    FirstROM.pNext      = 0; 
    ROMChain            = &FirstROM; 
    KInfoTable[KINX_PTOC] = (long)pTOC; 
    KInfoTable[KINX_PAGESIZE] = VM_PAGE_SIZE; 
 
    g_ppdirNK = (PPAGEDIRECTORY) &ArmHigh->firstPT[0]; 
    pKData->pNk  = g_pNKGlobal; 
 
    // (2) find entry of oal 
    pfnInitGlob = (PFN_OEMInitGlobals) pKData->dwOEMInitGlobalsAddr; 
 
    // no checking here, if OAL entry point doesn't exist, we can't continue 
    g_pOemGlobal = pfnInitGlob (g_pNKGlobal); 
    g_pOemGlobal->dwMainMemoryEndAddress = pTOC->ulRAMEnd; 
    pKData->pOem = g_pOemGlobal; 
 
    // setup globals 
    pVMProc         = g_pprcNK; 
    pActvProc       = g_pprcNK; 
 
    g_pNKGlobal->pfnWriteDebugString = g_pOemGlobal->pfnWriteDebugString; 
 
    // (3) setup vectors, UC mappings, mode stacks, etc. 
    ARMSetup (); 
 
    // 
    // cache is enabled from here on 
    // 
 
    // (4) common startup code. 
 
    // try to load KITL if exist 
    if ((pfnKitlEntry = (PFN_DllMain) g_pOemGlobal->pfnKITLGlobalInit) || 
        (pfnKitlEntry = (PFN_DllMain) FindROMDllEntry (pTOC, KITLDLL))) { 
        (* pfnKitlEntry) (NULL, DLL_PROCESS_ATTACH, (DWORD) NKKernelLibIoControl); 
    } 
 
#ifdef DEBUG 
    CurMSec = dwPrevReschedTime = (DWORD) -200000;      // ~3 minutes before wrap 
#endif 
 
    OEMInitDebugSerial (); 
 
    // debugchk only works after we have something to print to. 
    DEBUGCHK (pKData == (struct KDataStruct *) PUserKData); 
    DEBUGCHK (pKData == &ArmHigh->kdata); 
 
    OEMWriteDebugString ((LPWSTR)NKSignon); 
 
    /* Copy interlocked api code into the kpage */ 
    DEBUGCHK(sizeof(struct KDataStruct) <= FIRST_INTERLOCK); 
    DEBUGCHK((InterlockedEnd-InterlockedAPIs)+FIRST_INTERLOCK <= 0x400); 
    memcpy((char *)g_pKData+FIRST_INTERLOCK, InterlockedAPIs, InterlockedEnd-InterlockedAPIs); 
 
    /* setup processor version information */ 
    CEProcessorType     = (dwCpuId >> 4) & 0xFFF; 
    CEProcessorLevel    = 4; 
    CEProcessorRevision = (WORD) dwCpuId & 0x0f; 
    CEInstructionSet    = PROCESSOR_ARM_V4I_INSTRUCTION; 
 
    RETAILMSG (1, (L"ProcessorType=%4.4x  Revision=%d/r/n", CEProcessorType, CEProcessorRevision)); 
    RETAILMSG (1, (L"OEMAddressTable = %8.8lx/r/n", g_pOEMAddressTable)); 
 
    OEMInit();          // initialize firmware 
 
    // flush I&D TLB 
    OEMCacheRangeFlush (NULL, 0, CACHE_SYNC_FLUSH_TLB); 
 
    KernelFindMemory(); 
 
    DEBUGMSG (1, (TEXT("NKStartup done, starting up kernel./r/n"))); 
 
    KernelStart (); 
 
    // never returned 
    DEBUGCHK (0); 
} 
 
  
//------------------------------------------------------------------------------ 
// KernelInit - Kernel initialization before scheduling the 1st thread 
//------------------------------------------------------------------------------ 
 
void KernelInit (void)  
{ 
#ifdef DEBUG 
    g_pNKGlobal->pfnWriteDebugString (TEXT("Windows CE KernelInit/r/n")); 
#endif 
    APICallInit ();         // setup API set 
    HeapInit ();            // setup kernel heap 
    InitMemoryPool ();      // setup physical memory 
    PROCInit ();            // initialize process 
    VMInit (g_pprcNK);      // setup VM for kernel 
    THRDInit ();            // initialize threads 
    MapfileInit (); 
 
#ifdef DEBUG 
    g_pNKGlobal->pfnWriteDebugString (TEXT("Scheduling the first thread./r/n")); 
#endif 
} 
     这段代码跟WinCE5.0中的结构基本一致,但实际上有很大的不同。跟WinCE6.0启动最紧密的函数是THRDInit (),这之前都是做相应的初始化。THRDInit ()的实现在文件C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/thread.c中,代码如下:        
//------------------------------------------------------------------------------ 
// THRDInit - initialize thread handling (called at system startup) 
//------------------------------------------------------------------------------ 
void THRDInit (void)  
{ 
    LPBYTE      pStack; 
 
    DEBUGLOG (1, g_pprcNK); 
 
    // don't allow thread create one memory drop below 1% available 
    if (g_cMinPageThrdCreate < PageFreeCount / 100) { 
        g_cMinPageThrdCreate = PageFreeCount / 100; 
    } 
     
    // map W32 thread priority if OEM choose to 
    if (g_pOemGlobal->pfnMapW32Priority) { 
        BYTE prioMap[MAX_WIN32_PRIORITY_LEVELS]; 
        int  i; 
        memcpy (prioMap, W32PrioMap, sizeof (prioMap)); 
        g_pOemGlobal->pfnMapW32Priority (MAX_WIN32_PRIORITY_LEVELS, prioMap); 
        // validate the the priority is mono-increase 
        for (i = 0; i < MAX_WIN32_PRIORITY_LEVELS-1; i ++) { 
            if (prioMap >= prioMap[i+1]) 
                break; 
        } 
 
        DEBUGMSG ((MAX_WIN32_PRIORITY_LEVELS-1) != i, (L"ProcInit: Invalid priority map provided by OEM, Ignored!/r/n")); 
        if ((MAX_WIN32_PRIORITY_LEVELS-1) == i) { 
            memcpy (W32PrioMap, prioMap, sizeof (prioMap)); 
        } 
    } 
 
    // allocate memory for the 1st thread 
    pCurThread = AllocMem (HEAP_THREAD); 
    DEBUGCHK (pCurThread); 
 
    dwCurThId = (DWORD) HNDLCreateHandle (&cinfThread, pCurThread, g_pprcNK) & ~1; 
    DEBUGCHK (dwCurThId); 
 
    InitThreadStruct (pCurThread, (HANDLE) dwCurThId, g_pprcNK, THREAD_RT_PRIORITY_ABOVE_NORMAL); 
 
    if (g_pOemGlobal->cbCoProcRegSize) { 
 
        DEBUGCHK (g_pOemGlobal->pfnInitCoProcRegs); 
        DEBUGCHK (g_pOemGlobal->pfnSaveCoProcRegs); 
        DEBUGCHK (g_pOemGlobal->pfnRestoreCoProcRegs); 
 
        // check the debug register related values. 
        if (g_pOemGlobal->cbCoProcRegSize > MAX_COPROCREGSIZE) { 
            g_pOemGlobal->cbCoProcRegSize = g_pOemGlobal->fSaveCoProcReg = 0; 
        } else { 
            PNAME pTmp = AllocName (g_pOemGlobal->cbCoProcRegSize); 
            DEBUGCHK (pTmp); 
            g_dwCoProcPool = pTmp->wPool; 
            FreeName (pTmp); 
        } 
    } else { 
        g_pOemGlobal->fSaveCoProcReg = FALSE; 
    } 
    DEBUGMSG (ZONE_SCHEDULE,(TEXT("cbCoProcRegSize = %d/r/n"), g_pOemGlobal->cbCoProcRegSize)); 
 
    AddToDListHead (&g_pprcNK->thrdList, &pCurThread->thLink); 
    g_pprcNK->wThrdCnt ++; 
 
 
#ifdef SHx 
    SetCPUGlobals(); 
    OEMCacheRangeFlush (0, 0, CACHE_SYNC_ALL); 
#endif 
 
 
    if (!OpenExecutable (NULL, TEXT("NK.EXE"), &g_pprcNK->oe, TOKEN_SYSTEM, NULL, 0)) { 
        LoadE32 (&g_pprcNK->oe, &g_pprcNK->e32, 0, 0, 0); 
        g_pprcNK->BasePtr = (LPVOID)g_pprcNK->e32.e32_vbase; 
        UpdateKmodVSize(&g_pprcNK->oe, &g_pprcNK->e32); 
    } 
     
    // create/setup stack 
    pStack = VMCreateStack (g_pprcNK, KRN_STACK_SIZE); 
    pCurThread->dwOrigBase = (DWORD) pStack; 
    pCurThread->dwOrigStkSize = KRN_STACK_SIZE; 
    pCurThread->tlsSecure = pCurThread->tlsNonSecure = pCurThread->tlsPtr = TLSPTR (pStack, KRN_STACK_SIZE); 
    pCurThread->hTok = TOKEN_SYSTEM; 
 
    // Save off the thread's program counter for getting its name later. 
    pCurThread->dwStartAddr = (DWORD) SystemStartupFunc; 
 
    MDSetupThread (pCurThread, (LPVOID)SystemStartupFunc, 0, TH_KMODE, 0); 
 
    CELOG_ThreadCreate(pCurThread, g_pprcNK, NULL); 
 
    MakeRun(pCurThread); 
    DEBUGMSG(ZONE_SCHEDULE,(TEXT("Scheduler: Created master thread %8.8lx/r/n"),pCurThread)); 
 
} 
    可以看到,这里开始了一个线程,线程处理函数为SystemStartupFunc(),其实现在文件C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/schedule.c,实现代码如下:        
//------------------------------------------------------------------------------ 
void 
SystemStartupFunc( 
    ulong param 
    ) 
{ 
    HANDLE hTh; 
 
    // record PendEvent address for SetInterruptEvent 
    KInfoTable[KINX_PENDEVENTS] = (DWORD) &PendEvents1; 
 
    KernelInit2(); 
 
    // adjust alarm resolution if it it's not in bound 
    if (g_pOemGlobal->dwAlarmResolution < MIN_NKALARMRESOLUTION_MSEC) 
        g_pOemGlobal->dwAlarmResolution = MIN_NKALARMRESOLUTION_MSEC; 
    else if (g_pOemGlobal->dwAlarmResolution > MAX_NKALARMRESOLUTION_MSEC) 
        g_pOemGlobal->dwAlarmResolution = MAX_NKALARMRESOLUTION_MSEC; 
     
    VERIFY (LoaderInit ()); 
     
    // initialize the compiler /GS cookie - this must happen before other threads 
    // start running 
    __security_init_cookie(); 
 
    PagePoolInit (); 
 
    // This can only be done after the loader initialization 
    LoggerInit();           // Initialization for CeLog, profiler, code-coverage, etc. 
    SysDebugInit ();        // initialize System Debugger (HW Debug stub, Kernel dump capture, SW Kernel Debug stub) 
 
    // do this now, so that we continue running after we've created the new thread 
#ifdef START_KERNEL_MONITOR_THREAD 
    hTh = CreateKernelThread(Monitor1,0,THREAD_RT_PRIORITY_ABOVE_NORMAL,0); 
    HNDLCloseHandle (g_pprcNK, hTh); 
#endif 
 
    pCleanupThread = pCurThread; 
    hAlarmThreadWakeup = NKCreateEvent(0,0,0,0); 
    DEBUGCHK(hAlarmThreadWakeup); 
    InitializeCriticalSection(&rtccs); 
    IntrEvents[SYSINTR_RTC_ALARM-SYSINTR_DEVICES] = LockIntrEvt (hAlarmThreadWakeup); 
    DEBUGCHK(IntrEvents[SYSINTR_RTC_ALARM-SYSINTR_DEVICES]->phdIntr); 
 
    // Give the OEM a final chance to do a more full-featured init before any 
    // apps are started 
    KernelIoctl (IOCTL_HAL_POSTINIT, NULL, 0, NULL, 0, NULL); 
 
    InitMsgQueue (); 
    InitWatchDog (); 
 
    // create the power handler event and guard thread 
    hEvtPwrHndlr = NKCreateEvent (NULL, FALSE, FALSE, NULL); 
    DEBUGCHK (hEvtPwrHndlr); 
    hTh = CreateKernelThread (PowerHandlerGuardThrd, NULL, THREAD_PWR_GUARD_PRIORITY, 0); 
    HNDLCloseHandle (g_pprcNK, hTh); 
 
    // dirty page event, initially set 
    hEvtDirtyPage = NKCreateEvent (NULL, FALSE, TRUE, NULL); 
    DEBUGCHK (hEvtDirtyPage); 
 
    // we don't want to waste a thread here (create a separate for cleaning dirty pages). 
    // Instead, RunApps thread will become "CleanDirtyPage" thread once filesys started 
    hTh = CreateKernelThread (RunApps,0,THREAD_RT_PRIORITY_NORMAL,0); 
    HNDLCloseHandle (g_pprcNK, hTh); 
 
#define ONE_DAY     86400000 
 
    while (1) { 
        KCall((PKFN)SetThreadBasePrio, pCurThread, dwNKAlarmThrdPrio); 
        NKWaitForSingleObject (hAlarmThreadWakeup, ONE_DAY); 
        NKRefreshKernelAlarm (); 
        PageOutIfNeeded(); 
    } 
} 
     这里创建了一个内核线程,处理函数为RunApps,继续跟踪RunApps,其实现在文件C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/kmisc.c中,代码如下: 
   
DWORD WINAPI 
RunApps( 
    LPVOID param 
    ) 
{ 
    HMODULE hFilesys; 
    DEBUGMSG (ZONE_ENTRY, (L"RunApps started/r/n")); 
 
    CELOG_LaunchingFilesys(); 
 
    hFilesys = (HMODULE) NKLoadLibraryEx (L"filesys.dll", MAKELONG (LOAD_LIBRARY_IN_KERNEL, LLIB_NO_PAGING), NULL); 
 
    if (hFilesys) { 
        FARPROC pfnMain = GetProcAddressA (hFilesys, (LPCSTR) 2);   // WinMain of filesys 
        HANDLE hFSReady, hTh; 
 
        DEBUGCHK (pfnMain); 
 
        hFSReady = NKCreateEvent (NULL, TRUE, FALSE, TEXT("SYSTEM/FSReady")); 
        hTh = CreateKernelThread ((LPTHREAD_START_ROUTINE)pfnMain, hFilesys, THREAD_RT_PRIORITY_NORMAL, 0); 
 
        DEBUGCHK (hTh && hFSReady); 
        HNDLCloseHandle (g_pprcNK, hTh); 
 
        // If pSignalStarted is NULL, we don't have filesys (tinykern). Don't bother waiting for it. 
        if (pSignalStarted) { 
            NKWaitForSingleObject (hFSReady, INFINITE); 
 
            DEBUGCHK (SystemAPISets[SH_FILESYS_APIS]); 
 
            // Initialize MUI-Resource loader (requires registry) 
            InitMUILanguages(); 
 
            // Read system settings from registry 
            InitSystemSettings (); 
 
            // signal filesys that we're done 
            (* pSignalStarted) (0); 
        } 
        HNDLCloseHandle (g_pprcNK, hFSReady); 
    
    } else { 
        RETAILMSG (1, (L"Filesys doesn't exist, no app started/r/n")); 
    } 
 
    // instead of exiting, we're make this thread cleaning dirty pages in the background. 
    CleanPagesInTheBackground (); 
 
    // should've never returned 
    DEBUGCHK (0); 
    NKExitThread (0); 
 
    return 0; 
} 
      终于启动filesys.dll了。这个过程简单说明一下,启动filesys.dll后等待其执行的情况,在完成了文件系统的相应的初始化之后,这里继续初始化MUI和系统设置,完成后再通知filesys这边的工作已经完成,filesys继续启动。这一部分的具体内容请参考MSDN,File System Boot Process:http://msdn.microsoft.com/en-us/library/aa912276.aspx。总之,filesys会完成WinCE的最后启动过程,包括gwes.dll和explorer.exe等。至此,WinCE6.0启动完成,如果有LCD且驱动能正常工作,现在就应该能看见可爱的WinCE6.0的界面了。 呵,没想到WinCE6.0的启动过程竟然这么繁长。不过,弄清楚这个启动流程对于移植BSP相当有好处。总结一下整个过程,如下图所示。      
  
 |