通過前兩篇文章的介紹,我們已經知道NBOOT用來引導EBOOT,繼而EBOOT加載並引導WinCE操作系統(NK)。那麼,WinCE6.0的啟動過程又是怎樣的呢?本文基於S3C2410的平台做一個詳細的分析。需要說明的是,WinCE6.0的整個啟動過程對於同一類型的MCU來說大同小異,如S3C2410和PXA270同屬ARM平台的MCU,所以他們的啟動過程是類似的,可以說唯一的不同就在OAL處,而WinCE操作系統的啟動正是從OAL開始的。
前兩篇文章
- http://www.linuxidc.com/Linux/2016-06/132034.htm
- http://www.linuxidc.com/Linux/2016-06/132035.htm
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),它的實現代碼如下:
Code
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的啟動代碼如下:
Code
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的啟動代碼和EBOOT的啟動代碼經常復用,但為了代碼的簡潔,最好還是分開實現,而且在EBOOT中如果已經初始化了相關硬件,那麼OAL的啟動代碼就可以省去那部分工作,可以很簡練,如上面的代碼所示。
可以看出,OAL的啟動代碼又調用了函數KernelStart(),而這個函數是在文件C:\WINCE600\PRIVATE\WINCEOS\COREOS\NK\LDR\ARM\armstart.s中實現的,代碼如下:
Code
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
從上面的代碼可以看出,KernelStart()通過OEMAddressTable初始化了MMU,然後通過調用函數ARMInit()獲得kernel.dll的入口點,最後跳轉到kernel.dll的入口點處。
為了找到Kernel.dll的入口點,用IDA反匯編kernel.dll文件,可以看到,Kernel.dll的入口點為NKStartup。
NKStartup()的實現在文件C:\WINCE600\PRIVATE\WINCEOS\COREOS\NK\KERNEL\ARM\ mdarm.c中,代碼如下:
Code
//
// 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);
}
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中,代碼如下:
Code
//------------------------------------------------------------------------------
// 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中,代碼如下:
Code
//------------------------------------------------------------------------------
// 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[i] >= 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,實現代碼如下:
Code
//------------------------------------------------------------------------------
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中,代碼如下:
Code
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相當有好處。總結一下整個過程,如下圖所示。
本文通過跟蹤代碼的方式,介紹了WinCE6.0的啟動流程。流於表面了一點,很多細節應該進一步研究,以後再慢慢看吧。文中有不確切的地方,也請您不吝賜教.