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。
调用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。