Windows CE60启动过程分析 在Windows CE 60中,内核(Kenerl)和OEM代码被分成oalexe、kerneldll和kitldll三个部分,其中启动代码(startup)和 OAL层的实现部分不再与内核链接生成NKexe,取而代之的是启动代码(startup)和硬件相关且独立于内核的OAL层的实现部分编译成 oalexe,而与内核相关且独立于硬件的OAL层代码包含在kerneldll中;内核无关传输层(KITL)的支持代码从OAL层分离出来编译成 kitldll。 从表面上看,好像只是代码重新组合了一下,从帮助 文档中BSP的移植过程看好像也是这么一回事,实际上,整个Windows CE 60内核布局发生了很大的改变。Windows CE 60的启动过程也是如此,如果你想按照Windows CE 50的启动顺序去分析Windows CE 60的启动顺序,可能会走到一个死胡同。主要是因为Windows CE 60在启动过程中调用了kerneldll和kitldll两个动态链接库的原因,而且Windows CE60不再编译生成KernKitlProfexe内核文件。 从Windows CE 60的帮助文档可以看出,WinCE60的启动只与oalexe和kerneldll有关,至于kitldll,只有将 *** 作系统编译成具有 KITL功能时才用到。分析Windows CE 60的启动过程实际上找到编译oalexe和kerneldll的源码位置。 首先看一下将WinCE60编译成诸如 WinCE50所说的基本内核情况,即kernexe。对于oalexe源码位置比较容易找到,因为oalexe是启动代码与硬件相关的OAL层 实现文件编译而成,所以只需在BSP的OAL目录中便能找到。而对于kerneldll,在BSP目录结构中,基本上无法找到kerneldll的编 译文件,所以必须从其他方面着手。 下面为WinCE 60的编译日志输出文件:makeimgout在文件复制过程的一部分: Copying E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\oalexe to E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\nkexe for debugger Copying E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\kerndll to E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\kerneldll for debugger 从日志输出文件可以看出,在文件复制过程 中,WinCE60编译器将oalexe更名为nkexe,而将kerndll文件更名为kerneldll,也就是说,kerndll文件 的实现部分就是kerneldll的实现体。根据前面的分析,oalexe是与硬件相关独立于内核的OAL层的实现部分,而kerneldll为内 核相关独立于硬件的OAL层的实现部分。同样可以从最后整合后的二进制配置文件cebib文件中看出端倪。 ; @CESYSGEN IF CE_MODULES_NK nkexe E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\oalexe NK SHZ kitldll E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\kitldll NK SHZ kerneldll E:\WINCE600\OSDesigns\xsbase270\xsbase270\RelDir\XSBase270_ARMV4I_Release\kerndll NK SHZ ; @CESYSGEN ENDIF 而kerndll动态库在整个Windows CE60中没有显式编译过程,即没有一个sources文件有kerndll的编译过程,所以只能从 *** 作系统的编译文件Makefile中寻找其编译 过程。下面看一下$(_PUBLICROOT)\common\CESYSGEN\makefile中的部分内容: nk::$(NK_COMPONENTS) $(NK_REPLACE_COMPONENTS) @copy $(SG_INPUT_LIB)\oemstubpdb $(SG_OUTPUT_OAKLIB) @copy $(SG_INPUT_LIB)\oemstublib $(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)\nkmainlib $(SG_INPUT_LIB)\fulllibclib $(MAKECMD) /NOLOGO NOLIBC=1 kerndll 从上述代码中可以发现,原来kerndll动态库是从oemstublib编译而来,而且与nkmainlib有关。 在理顺了上述文件的相互之间的关系之后,再来分析Windows CE 60的启动过程可能就比较容易啦。 在理清了上述文件的关系之后,便可以分析任意一款基于ARM微处理器的Windows CE 60的启动过程,现在以深圳亿道电子技术有限公司开发的基于PXA270 ARM开发平台为例,分析Windows CE 60 *** 作系统启动过程。 1、Startup函数: 从Windows CE 60的帮助文档可以看出,WinCE60的启动只与oalexe和kerneldll有关,至于kitldll,只有将 *** 作系统编译成具有 KITL功能时才用到。分析Windows CE 60的启动过程实际上找到编译oalexe和kerneldll的源码位置。 oalexe的通过Startup函数完成硬件 的初始化。Startups代码与该硬件平台的Bootloader启动代码共用,其中PreInit函数主要完成将ARM处理器工作模式切换到管理员 模式、同时关闭MMU,并检测系统启动原因,如果是热启动、即在该函数调用之前已经启动了Bootloader程序,相当基本硬件初始化已经完成,则直接 跳转到OALStartUp函数中;否则需要进行硬件中断屏蔽、内存、系统时钟频率、电源管理等硬件的基本初始化过程。(具体过程见代码的分析) $(_PLATFORMROOT)\xsbase270\src\common\Startup\Startups 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_cfginc): $(_PLATFORMROOT)\xsbase270\src\oal\OalLib\Startups 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指令跳转到kerneldll的入口位置,即NKStartup函数中。 $(_PRIVATEROOT)WINCEOS\COREOS\NK\LDR\ARM\armstarts 4、ARMInit函数: 在ARMInit之前,系统仍无法使用全局变量, 因为系统的全局还在ROM区域,对于 *** 作系统而言,出于安全考虑,只有XIP程序才有读取ROM区域数据的权利,对于大部分Windows CE *** 作系统,只有将数据拷贝到RAM区域后才能进行读写,ARMInit函数中通过调用KernelRelocate函数对pTOC全局变量重新定位,定位 之后,对内核启动所需要的KDataStruct结构体进行初始化,其中OEMInitGlobals便是交换oalexe和kerneldll之间 的全局指针,ARMInit函数返回kerneldll的入口位置。并在KernelStart函数最后利用mov pc, r12指令跳转到kerneldll的入口位置,即NKStartup函数中。 $(_PRIVATEROOT)WINCEOS\COREOS\NK\LDR\ARM\arminitc 5、NKStartup函数: 硬件平台初始化完成后,oalexe的启动任务基本完成,余下的启动工作由内核相关且独立于内核的OAL层实现体kerneldll接管。kerneldll主要作用: ◆从结构体参数KDataStruct pKData提取内核启动时所必须的全局变量,同时初始化内核全局变量; ◆定位对Windows CE 60特有的OEMGLOBAL结构体的初始化函数OEMInitGlobals地址,该结构体构建了内核和OAL层之间进行通信的桥梁。在 OEMGLOBAL结构体定义了OAL层所必须的函数,该结构体在oemglobalc文件中被初始化,并被编译在OEMMainlib和 OEMMain_StaticKITLlib两个库中,如果OAL链接这两个库,则必须要有该结构体中函数实现体; ◆通过调用ARMSetup设置物理地址和非缓冲的虚拟内存地址的映射、ARM中断向量以及内核模式所需要的堆栈。 ◆调用OEMInitDebugSerial函数初始化调试串口; ◆调用OEMInit进行平台初始化; 需要注意的时,NKStartup函数调用OEMInitDebugSerial和 OEMInit函数的过程与Windows CE 60之前的版本完全不同,这是因为在Windows CE 60以前的版本中,由于内核(kernel)、OAL和KITL编译在一个可执行的文件中,它们之间的共享变量只需简单利用extern关键字申明便可 相互之间进行访问,而在Windows CE 60中,由于内核(kernel)、OAL和KITL被编译成不同的可执行文件,变量之间的相互访问无法使用extern关键字实现共享,即内核无法使 用extern DWORD varX方法访问OAL层的变量varX,当然OAL层的实现体同样无法通过同样的方式访问内核变量。为实现内核和OAL访问共享信息,Windows CE 60定义了OEMGLOBAL和GLOBAL两个结构体。 在 Windows CE 60的内核启动时,OS找到OAL的入口位置,然后调用入口函数与全局块进行指针交换,这样内核才能使用OAL层中的信息,同样OAL层才能访问内核(kernel)导出的函数。 所以上述两个函数的调用实际上通过OEMGLOBAL结构体实现的。实际调用位置为$(_PRIVATEROOT)\winceos\coreos \nk\oemstub\oemstubc中的OEMInitDebugSerial和OEMInit,这两个函数中通过OEMGLOBAL结构体指针 访问OAL层中的OEMInitDebugSerial和OEMInit。 首先看一下将WinCE60编译成诸如WinCE50所说的基本内核情况,即kernexe。对于oalexe源码位置比较容易找到,因为 oalexe是启动代码与硬件相关的OAL层实现文件编译而成,所以只需在BSP的OAL目录中便能找到。而对于kerneldll,在BSP目录结 构中,基本上无法找到kerneldll的编译文件,所以必须从其他方面着手。 调用KernelFindMemory()函数分割RAM区域,在Windows CE *** 作系统中,RAM空间主要分为存储内存和程序内存,存储内存主要为文件的存储空间,包括内核文件和复制到系统中所有目标文件,程序内存为运行程序时所需要的存储空间。 ◆KernelStart ()启动内核。 $(_PRIVATEROOT)\WINCEOS\COREOS\NK\KERNEL\ARM\mdarmc void NKStartup (struct KDataStruct pKData) { 。。。。 } 6、KernelSstart函数: 这里的KernelStart函数与前面的KernelStart函数的属于两个完全不 同的函数,NKStartup函数中调用的KernelStart函数为$(_PRIVATEROOT)\WINCEOS\COREOS\NK \KERNEL\ARM\armtraps文件中的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 60的内核初始化函数同其他版本的内核初始化函数基本相近,主要完成在启动第一个线程前对内核进行初始化,主要包括API函数集初始化、堆的初始化、初始化内存池、进程初始化、线程初始化和文件映射初始化等 *** 作。 void KernelInit (void) 。。。{ } 8、FirstSchedule: FirstSchedule函数为Windows CE *** 作系统启动过程中最后无条件跳转的一个函数,windows CE进行第一个调度,实际为一个空闲线程,因为windows CE系统还没有完成启动,只有当windows CE完全启动并进入稳定状态,然后启动文件系统filesysdll,设备管理devicedll,窗体图像子系统gewsdll和shell程序 exploreexe。
__swi(0x00) void SwiHandle1(int Handle);其实没有函数体,执行这个语句后就自动把Handle的值赋给了R0,接着执行下面的代码。__swi(0x00)是软件中断,0为软中断指令中的24位立即数,但是通过R0寄存器来传递参数具体的函数体,当然是要在swi的中断处理程序中去找了可以在复位时的异常向量表里面找到swi中断服务程序的入口地址。
SWI 执行的流程是,先进入异常中断向量表,然后跳到向量地址处,接着一小段汇编 *** 作,把功能号读入到一个寄存器中,然后 switch 判断这个功能号是多少,接着跳转到对应的终端服务程序,如果函数有参数,则根据ATPCS规则进行参数的传递;如只有一个参数,则用 R0 来传递,超过4个参数,超出的部分用堆栈来传递。
__swi是ADS编译器的关键字,用它做前缀可以声明一个软中断调用,格式为:
__swi(功能号) 返回值 名称 (参数列表)
功能号:即软中断指令中的24位立即数,软中断号
名 称:即调用软中断时用于描述软中断的函数名称
参 数:软中断函数的参数,根据ATPCS规则,如果软中断函数有不超过4个参数时,通过R0~R3传递,超过4个参数时用堆栈来传递。
__swi(0x00) void SwiHandle1(int Handle)。其中0x00为软中断功能号(软中断号);软中断函数名称为SwiHandle1;只有一个参数,则使用R0来传递;函数没有返回值。紧接着这句代码的是定义了4个宏,分别表示禁能IRQ函数、使能IRQ函数、禁能FIQ函数、使能IFQ函数,其实调用的软中断函数是一样的,只是参数不同而已。例如在用户程序中调用“IRQEnable( );”时,处理器会产生软中断。位于启动代码中的那些是软中断处理函数,当发生软中断时,PC被强制指向0x00000008,这个地址中存放的是软中断异常的处理函数的地址,所以程序会跳转至标号“SoftwareInterrupt ”处执行。SoftwareInterrupt 函数的功能是判断R0的值(R0的值为软中断函数传递过来的参数)是否小于4,如果小于4则跳转至标号“SwiFunction”执行,如果不是则函数返回。SwiFunction函数是一个散转函数,它的功能是根据R0的值跳转至对应的函数处执行,即如果参数为1,则函数会跳转至IRQEnable处执行,将IRQ中断使能。
本文件SWIs位于ARM Executable Image for LPC2294工程模板中,故不考虑SWI触发前为Thumb态;SWI异常一旦触发,内核硬件完成:
♂ 进入Supervisor模式;
♂ 拷贝CPSR至SPSR_svc
♂ 拷贝异常返回地址至LR_svc
♂ 将0x00000008装入PC
因此,当触发SWI软中断前内核处于Supervisor模式,SPSR_svc、LR_svc中的值将被破坏;
3、SWI指令编码中自带24bit数据作为软中断号(swi_num),因此可通过取SWI指令编码获取软中断号;LDR r0,[lr,#-4]就是这样;
4、SWI_Exception_Function函数一般采用C编码(也可汇编),采用C编码可直接套用switch根据swi_nun软中断号切换,SWI_Exception_Function函数的编制是灵活的,比如可以为带参或不带参函数;
5、一个SWI调用允许带1~4个字型参数和1~4个字型返回值,触发SWI调用时四个参数依次保存在R0~R3中,返回值也存于R0~R3内,这和ATPCS函数调用一致;
6、在C中声明一个典型的无参无返回值的SWI调用为:”__swi(0x00) void IRQEnable();“这样随时都可以使用”IRQEnable();“触发一个软中断(中断号0),其允许IRQ中断的功能必须在SWI_Exception_Function软中断处理函数中实现;
7、以下为带参数的SWI调用,SWI调用和普通函数调用一样遵循ATPCS标准,Handle参数存放在R0中:
__swi(0x01) void SwiHandle(int Handle);
#define IRQDisable() SwiHandle(0)
#define IRQEnable() SwiHandle(1)
#define FIQDisable() SwiHandle(2)
#define FIQEnable() SwiHandle(3)
转自:>
以上就是关于如何让WINCE系统启动直接运行自己的程序全部的内容,包括:如何让WINCE系统启动直接运行自己的程序、// 使能/禁能IRQ、FIQ中断 __swi(0x00) void SwiHandle1(int Handle);这句话怎么分析、等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)