在Windows CE 6.0中,内核(Kenerl)和OEM代码被分成oal.exe、kernel.dll和kitl.dll三个部分,其中启动代码(startup)和OAL层的实现部分不再与内核链接生成NK.exe,取而代之的是启动代码(startup)和硬件相关且独立于内核的OAL层的实现部分编译成oal.exe,而与内核相关且独立于硬件的OAL层代码包含在kernel.dll中;内核无关传输层(KITL)的支持代码从OAL层分离出来编译成kitl.dll。
从表面上看,好像只是代码重新组合了一下,从帮助文档中BSP的移植过程看好像也是这么一回事,实际上,整个Windows CE 6.0内核布局发生了很大的改变。Windows CE 6.0的启动过程也是如此,如果你想按照Windows CE 5.0的启动顺序去分析Windows CE 6.0的启动顺序,可能会走到一个死胡同。主要是因为Windows CE 6.0在启动过程中调用了kernel.dll和kitl.dll两个动态链接库的原因,而且Windows CE6.0不再编译生成KernKitlProf.exe内核文件。
从Windows CE 6.0的帮助文档可以看出,WinCE6.0的启动只与oal.exe和kernel.dll有关,至于kitl.dll,只有将操作系统编译成具有KITL功能时才用到。分析Windows CE 6.0的启动过程实际上找到编译oal.exe和kernel.dll的源码位置。
首先看一下将WinCE6.0编译成诸如WinCE5.0所说的基本内核情况,即kern.exe。对于oal.exe源码位置比较容易找到,因为oal.exe是启动代码与硬件相关的OAL层实现文件编译而成,所以只需在BSP的OAL目录中便能找到。而对于kernel.dll,在BSP目录结构中,基本上无法找到kernel.dll的编译文件,所以必须从其他方面着手。
下面为WinCE 6.0的编译日志输出文件:makeimg.out在文件复制过程的一部分:
Copying E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\oal.exe to E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\nk.exe for debugger Copying E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\kern.dll to E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\kernel.dll for debugger
从日志输出文件可以看出,在文件复制过程中,WinCE6.0编译器将oal.exe更名为nk.exe,而将kern.dll文件更名为kernel.dll,也就是说,kern.dll文件的实现部分就是kernel.dll的实现体。根据前面的分析,oal.exe是与硬件相关独立于内核的OAL层的实现部分,而kernel.dll为内核相关独立于硬件的OAL层的实现部分。同样可以从最后整合后的二进制配置文件ce.bib文件中看出端倪。
; @CESYSGEN IF CE_MODULES_NK

 

nk.exe E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\oal.exe NK SHZ

kitl.dll E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\kitl.dll NK SHZ

kernel.dll E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\kern.dll NK SHZ

; @CESYSGEN ENDIF

而kern.dll动态库在整个Windows CE6.0中没有显式编译过程,即没有一个sources文件有kern.dll的编译过程,所以只能从操作系统的编译文件Makefile中寻找其编译过程。下面看一下$(_PUBLICROOT)\common\CESYSGEN\makefile中的部分内容:
nk::$(NK_COMPONENTS) $(NK_REPLACE_COMPONENTS)

 

@copy $(SG_INPUT_LIB)\oemstub.pdb $(SG_OUTPUT_OAKLIB)

@copy $(SG_INPUT_LIB)\oemstub.lib $(SG_OUTPUT_OAKLIB)

set TARGETTYPE=DYNLINK

set TARGETNAME=kern

set RELEASETYPE=OAK

set DLLENTRY=NKStartup

set DEFFILE=NO_DEF_FILE

set TARGETLIBS=

set SOURCELIBS=%%NKLIBS%% $(SG_INPUT_LIB)\nkmain.lib $(SG_INPUT_LIB)\fulllibc.lib

$(MAKECMD) /NOLOGO NOLIBC=1 kern.dll

从上述代码中可以发现,原来kern.dll动态库是从oemstub.lib编译而来,而且与nkmain.lib有关。
在理顺了上述文件的相互之间的关系之后,再来分析Windows CE 6.0的启动过程可能就比较容易啦。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

在理清了上述文件的关系之后,便可以分析任意一款基于ARM微处理器的Windows CE 6.0的启动过程,现在以深圳亿道电子技术有限公司开发的基于PXA270 ARM开发平台为例,分析Windows CE 6.0操作系统启动过程。
1、Startup函数:
从Windows CE 6.0的帮助文档可以看出,WinCE6.0的启动只与oal.exe和kernel.dll有关,至于kitl.dll,只有将操作系统编译成具有KITL功能时才用到。分析Windows CE 6.0的启动过程实际上找到编译oal.exe和kernel.dll的源码位置。
oal.exe的通过Startup函数完成硬件的初始化。Startup.s代码与该硬件平台的Bootloader启动代码共用,其中PreInit函数主要完成将ARM处理器工作模式切换到管理员模式、同时关闭MMU,并检测系统启动原因,如果是热启动、即在该函数调用之前已经启动了Bootloader程序,相当基本硬件初始化已经完成,则直接跳转到OALStartUp函数中;否则需要进行硬件中断屏蔽、内存、系统时钟频率、电源管理等硬件的基本初始化过程。(具体过程见代码的分析)
$(_PLATFORMROOT)\xsbase270\src\common\Startup\Startup.s

 

LEAF_ENTRY StartUp

 

bl PreInit

 

tst r10, #RCSR_HARD_RESET

 

beq OALStartUp

 

tst r10, #RCSR_GPIO_RESET

 

bne Continue_StartUp

 

bl xlli_mem_init ;初始化内存控制器

 

ldr r0, =xlli_PMRCREGS_PHYSICAL_BASE;

 

ldr r0, [r0, #xlli_PSPR_offset];

 

mov r1, r10;

 

bl XllpPmValidateResumeFromSleep;

 

cmp r0, #0;

 

bne Failed_Sleep_Resume;

Sleep_Reset

 

ldr r0, =xlli_PMRCREGS_PHYSICAL_BASE;

 

ldr r0, [r0, #xlli_PSPR_offset];

 

mov r1, r10;

 

b XllpPmGoToContextRestoration;

 

Failed_Sleep_Resume

 

ldr r1, =xlli_RCSR_SMR

 

bic r10, r10, r1

 

Continue_StartUp

 

bl xlli_intr_init; ;初始化中断控制器

 

bl EnableClks; ;使能内核时钟(内存/OS定时器/FFART时钟之需)

 

bl OALXScaleSetFrequencies ;设置系统频率

 

bl xlli_mem_Topt

 

bl xlli_mem_restart ;复位内存,使其处于工作模式

 

bl xlli_ost_init ;初始化操作系统定时器

 

bl xlli_pwrmgr_init ;初始化电源管理

 

bl xlli_IMpwr_init ;初始化内部存储器

 

b

 

ENTRY_END

 

2、OALStartUp函数:
在系统硬件初始化完毕之后,Startup调用OALStartUp函数,OALStartUp函数主要完成将OEMAddressTable表传递给内核;然后调用KernelStart函数跳转到内核OEMAddressTable表的主要作用表的每一个入口都定义了一个内存中的物理位置、内存的大小以及映射这物理地址的静态虚拟地址;

 

◆静态虚拟内存地址被定义在缓冲存储器的范围之内;

 

◆内核可以创建非缓冲的内存地址指向到相同的物理地址;

 

◆对于同一物理地址,既有一个缓冲的虚拟内存地址,也有一个非缓冲的虚拟内存地址;

 

◆OEMAddressTable最后必须以0结尾;

 

◆对于MIPS和SHx类型的CPU,物理地址与虚拟地址的映射由CPU完成,无需创建OEMAddressTable

 

$(_PLATFORMROOT)\xsbase270\src\Inc\ Oemaddrtab_cfg.inc):

 

ALIGN g_oalAddressTable

 

DCD 0x80000000, 0xA0000000,64; XSBASE270: SDRAM (64MB).

 

DCD 0x84000000, 0x5C000000,1; BULVERDE: Internal SRAM (64KB bank 0).

 

DCD 0x84100000, 0x58000000,1; BULVERDE: Internal memory PM registers.

 

DCD 0x84200000, 0x4C000000,1; BULVERDE: USB host controller.

 

DCD 0x84300000, 0x48000000,1; BULVERDE: Memory controller.

 

DCD 0x84400000, 0x44000000,1; BULVERDE: LCD controller.

 

DCD 0x84500000, 0x40000000,32; BULVERDE: Memory-mapped registers

 

DCD 0x86500000, 0x00000000,64; XSBASE270: nCS0: Boot Flash (64MB).

 

DCD 0x96600000, 0x3C000000,64; BULVERDE: PCMCIA S1 common memory space.

 

DCD 0x8A600000, 0x38000000,32; BULVERDE: PCMCIA S1 attribute memory space.

 

DCD 0x8C600000, 0x30000000,32; BULVERDE: PCMCIA S1 I/O space.

 

DCD 0x8E500000, 0x2C000000,64; BULVERDE: PCMCIA S0 common memory space.

 

DCD 0x92500000, 0x28000000,32; BULVERDE: PCMCIA S0 attribute memory space.

 

DCD 0x94500000, 0x20000000,32; BULVERDE: PCMCIA S0 I/O space.

 

DCD 0x96500000, 0xE0000000,1; XSBASE270: Zero-bank .

 

DCD 0x96600000, 0x14000000,1; XSBASE270: nCS5: eXpansion board header.

 

DCD 0x96600000, 0x10000000,64; XSBASE270: nCS4: USB2.0/IDE controller.

 

DCD 0x9A700000, 0x0C000000,1; XSBASE270: nCS3: SMSC 91C111 Ethernet controller.

 

DCD 0x9A800000, 0x0A000000,1; XSBASE270: nCS2 : Board registers (CPLD).

 

DCD 0x9A900000, 0x04000000,32; XSBASE270: nCS1: Secondary flash (32MB).

 

DCD 0x9F900000, 0x50000000,1; BULVERDE: Camera peripheral interface.

 

DCD 0x9FA00000, 0x14700000,1

 

DCD 0x00000000, 0x00000000,0;end of table

 

END

$(_PLATFORMROOT)\xsbase270\src\oal\OalLib\Startup.s

ALIGN

 

LEAF_ENTRY OALStartUp

 

add r0, pc, #g_oalAddressTable - (. + 8)

 

mov r11, r0

 

b KernelStart

 

nop

 

nop

 

nop

 

nop

 

nop

 

nop

STALL

 

b STALL ;Spin forever.

 

3、KernelStart函数主要作用:

 

◆完成OEMAddressTable表中的物理地址到虚拟地址和虚拟地址到物理地址之间的映射;

 

◆对存储器页表和内核参数区存储空间(RAM或DRAM)进行清零处理。

 

◆读出CPU的ID号,内核需要根据该ID决定ARM的MMU处理,因为ARMV6和ARMV6之前的ARM处理器的MMU处理过程有所区别;

 

◆设置并开启MMU和Cache,因为在Startup函数关闭MMU和Cache;

 

◆设置ARM处理器工作模式的SP指针,ARM处理器共用7种不同的工作模式(USER、FIQ、IRQ、Supervisor、Abort、Undefined、System),除用户模式(USER)和系统模式(System)之外,其他5种工作模式都有具有特定的SP指针寄存器(ARM处理器称其为影子寄存器);

 

◆读取内核启动所需要的KDataStruct结构体;

 

◆调用ARMInit函数重新定位Windows CE内核参数pTOC和初始化OEMInitGlobals全局变量;

 

◆利用mov pc, r12指令跳转到kernel.dll的入口位置,即NKStartup函数中。

 

$(_PRIVATEROOT)WINCEOS\COREOS\NK\LDR\ARM\armstart.s

 

 

LEAF_ENTRY KernelStart

 

mov r11, r0 ;(r11) = &OEMAddressTable (save pointer)

 

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 VaFromPa

 

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

 

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)

 

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

 

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

 

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

 

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

VirtualStart DCD VStart

ENTRY_END KernelStart

 

4、ARMInit函数:
在ARMInit之前,系统仍无法使用全局变量,因为系统的全局还在ROM区域,对于操作系统而言,出于安全考虑,只有XIP程序才有读取ROM区域数据的权利,对于大部分Windows CE 操作系统,只有将数据拷贝到RAM区域后才能进行读写,ARMInit函数中通过调用KernelRelocate函数对pTOC全局变量重新定位,定位之后,对内核启动所需要的KDataStruct结构体进行初始化,其中OEMInitGlobals便是交换oal.exe和kernel.dll之间的全局指针,ARMInit函数返回kernel.dll的入口位置。并在KernelStart函数最后利用mov pc, r12指令跳转到kernel.dll的入口位置,即NKStartup函数中。

 

$(_PRIVATEROOT)WINCEOS\COREOS\NK\LDR\ARM\arminit.c

LPVOID ARMInit (struct KDataStruct *pKData)

{

 

/* Initialize kernel globals */

 

KernelRelocate (pTOC);

 

/* The only argument passed to the entry point of kernel.dll is the address */

 

/* of KData, we need to put everything we need to pass to in in KData. */

 

pKData->dwTOCAddr = (DWORD) pTOC;

 

pKData->dwOEMInitGlobalsAddr = (DWORD) OEMInitGlobals;

 

SetOsAxsDataBlockPointer(pKData);

 

return FindKernelEntry (pTOC);

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5、NKStartup函数:
硬件平台初始化完成后,oal.exe的启动任务基本完成,余下的启动工作由内核相关且独立于内核的OAL层实现体kernel.dll接管。kernel.dll主要作用:

 

◆从结构体参数KDataStruct * pKData提取内核启动时所必须的全局变量,同时初始化内核全局变量;

 

◆定位对Windows CE 6.0特有的OEMGLOBAL结构体的初始化函数OEMInitGlobals地址,该结构体构建了内核和OAL层之间进行通信的桥梁。在OEMGLOBAL结构体定义了OAL层所必须的函数,该结构体在oemglobal.c文件中被初始化,并被编译在OEMMain.lib和OEMMain_StaticKITL.lib两个库中,如果OAL链接这两个库,则必须要有该结构体中函数实现体;

 

◆通过调用ARMSetup设置物理地址和非缓冲的虚拟内存地址的映射、ARM中断向量以及内核模式所需要的堆栈。

 

◆调用OEMInitDebugSerial函数初始化调试串口;

 

◆调用OEMInit进行平台初始化;

 

需要注意的时,NKStartup函数调用OEMInitDebugSerial和OEMInit函数的过程与Windows CE 6.0之前的版本完全不同,这是因为在Windows CE 6.0以前的版本中,由于内核(kernel)、OAL和KITL编译在一个可执行的文件中,它们之间的共享变量只需简单利用extern关键字申明便可相互之间进行访问,而在Windows CE 6.0中,由于内核(kernel)、OAL和KITL被编译成不同的可执行文件,变量之间的相互访问无法使用extern关键字实现共享,即内核无法使用extern DWORD varX方法访问OAL层的变量varX,当然OAL层的实现体同样无法通过同样的方式访问内核变量。为实现内核和OAL访问共享信息,Windows CE 6.0定义了OEMGLOBAL和GLOBAL两个结构体。

 

 

 

在 Windows CE 6.0的内核启动时,OS找到OAL的入口位置,然后调用入口函数与全局块进行指针交换,这样内核才能使用OAL层中的信息,同样OAL层才能访问内核(kernel)导出的函数。

 

 

 

 

所以上述两个函数的调用实际上通过OEMGLOBAL结构体实现的。实际调用位置为$(_PRIVATEROOT)\winceos\coreos\nk\oemstub\oemstub.c中的OEMInitDebugSerial和OEMInit,这两个函数中通过OEMGLOBAL结构体指针访问OAL层中的OEMInitDebugSerial和OEMInit。

 

 

 

 

 

首先看一下将WinCE6.0编译成诸如WinCE5.0所说的基本内核情况,即kern.exe。对于oal.exe源码位置比较容易找到,因为oal.exe是启动代码与硬件相关的OAL层实现文件编译而成,所以只需在BSP的OAL目录中便能找到。而对于kernel.dll,在BSP目录结构中,基本上无法找到kernel.dll的编译文件,所以必须从其他方面着手。

 

调用KernelFindMemory()函数分割RAM区域,在Windows CE操作系统中,RAM空间主要分为存储内存和程序内存,存储内存主要为文件的存储空间,包括内核文件和复制到系统中所有目标文件,程序内存为运行程序时所需要的存储空间。

 

 
◆KernelStart ()启动内核。
$(_PRIVATEROOT)\WINCEOS\COREOS\NK\KERNEL\ARM\mdarm.c

 

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;

 

} 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 ();

 

// (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);

}

 

6、KernelSstart函数:

 

这里的KernelStart函数与前面的KernelStart函数的属于两个完全不同的函数,NKStartup函数中调用的KernelStart函数为$(_PRIVATEROOT)\WINCEOS\COREOS\NK\KERNEL\ARM\armtrap.s文件中的KernelStart函数,主要完成调用内核初始化函数KernelInit,并跳转到操作系统的第一个启动的任务。

 

 

LEAF_ENTRY KernelStart

 

ldr r4, =KData ; (r4) = ptr to KDataStruct

 

ldr r0, =APIRet

 

str r0, [r4, #pAPIReturn] ; set API return address

 

mov r1, #SVC_MODE

 

msr cpsr_c, r1 ; switch to Supervisor Mode w/IRQs enabled

 

CALL KernelInit ; initialize scheduler, etc.

 

mov r0, #0 ; no current thread

 

mov r1, #ID_RESCHEDULE

 

b FirstSchedule

 

ENTRY_END

 

7、KernelInit函数:
Windows CE 6.0的内核初始化函数同其他版本的内核初始化函数基本相近,主要完成在启动第一个线程前对内核进行初始化,主要包括API函数集初始化、堆的初始化、初始化内存池、进程初始化、线程初始化和文件映射初始化等操作。
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 threadsv

 

MapfileInit ();

#ifdef DEBUG

g_pNKGlobal->pfnWriteDebugString (TEXT("Scheduling the first thread.\r\n"));

 

#endif

}

8、FirstSchedule:
FirstSchedule函数为Windows CE操作系统启动过程中最后无条件跳转的一个函数,windows CE进行第一个调度,实际为一个空闲线程,因为windows CE系统还没有完成启动,只有当windows CE完全启动并进入稳定状态,然后启动文件系统filesys.dll,设备管理device.dll,窗体图像子系统gews.dll和shell程序explore.exe。
posted on 2010-12-25 19:27  GT_Andy  阅读(1167)  评论(0编辑  收藏  举报