http://www.esdevelop.cn/category/windowsce/200808/wince_startup.html
Windows CE内核启动分析
移植或者创建一个BSP,也许需要先熟悉Windows CE的内核启动过程.
目录
基于ARM的Windows CE内核启动分析1
1.startup.s2
2.KernelStart2
2.1 ARMInit()3
2.1.1 OALIntrInit3
2.1.2 OALTimerInit4
2.1.2.1 Variable Tick Scheduler4
2.2 KernelInit()4
2.3 FirstSchedule5
1.startup.s
内核入口点startup.S,内核从这里启动.因为内核经过bootloader加载,内核运行时候,已经由bootloader完成了硬件的基本初始化(关闭watchdog, pll设置等等)所以,startup.S的任务比较简单,只是将oemaddrtab_cfg.inc里面的g_oalAddressTable数组地址作为参数,传递给KernelStart,这个数组用来描述和实现物理地址到虚拟地址的映射.
(. + 8)是流水线处理.KernelStart()位于
PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/armtrap.s
2.KernelStart
ARMInit()位于本目录的mdram.c文件.
KernelInit()位于PRIVATE/WINCEOS/COREOS/NK/KERNEL/kwin32.c中.
FirstSchedule()位于armtrap.s的一个label.
主要关注ARMInit()和KernelInit(),前一个进行目标板的初始化,后一个负责内核的初始化.FirstSchdule()开始调度第一个程序.
2.1 ARMInit()
先看看ARMInit()它的几个关键性动作如下:
KernelRelocate()是进行重定位.KernelFindMemory()是查找系统可用内存,并分成应用内存和object store两部分.这2个函数都已由MS自己实现.我们需要添加的函数是名字以OEM开头的函数.
OEMInitDebugSerial()初始化一个调试口,我们一般使用一个串口来作为调试口,这个函数需要自己实现,在 PLATFORM/SMDK2440A/Src/Kernel/Oal/debug.c中定义这个函数.比如可以将串口0设置为调试口,在这个函数中对串口0进行初始化.
OEMInit()是一个比较重要的函数,
OALCacheGlobalsInit()在PLATFORM/COMMON/SRC/ARM/COMMON/CACHE/init.s中实现,这部分代码以PQOAL的形式提供.
OALIntrInit()初始化中断.
OALTimerInit()初始化定时器TIMER4,作为系统时钟(tick),
configGPIO()初始化gpio口,设置相关寄存器.
InitDisplay()初始化LCD.有时候,我们希望在oal启动和内核加载期间显示一副等待图片或者显示LOGO,为达到这个目的,需要先初始化LCD.
OALKitlStart()准备启动KITL.
此外,在ARMInit还会通过调试口打印一些基本信息,开始时候打印”Windows CE Kernel for ARM….”字样, 中间打印处理器类型等等信息.结束时候打印” ARMInit done.”
2.1.1 OALIntrInit
调用OALIntrMapInit()初始化2个数组g_oalSysIntr2Irq,g_oalIrq2SysIntr,这2个数组表征irq和逻辑中断SysIntr的映射关系.
然后初始化中断寄存器,
最后,留一个接口给oem: BSPIntrInit(),如果oem需要在这个阶段初始化一些中断,可以定义这个函数并实现.
2.1.2 OALTimerInit
这个函数比较重要. 都知道所有WinCE系统都需要一个定时器来提供一个heartbeat,
g_oalTimer包含各种系统时钟相关的变量.
curridlehigh, curridlelow,这2个32位的DWORD变量合起来实现一个64位的计数器,反映了系统处于空闲模式(Idle mode)的时间。一般在OEMIdle()函数内更新。用户程序通过调用GetIdleTime()函数可以得到这个值。
初始化内核函数指针:pQueryPerformanceFrequency, pQueryPerformanceCounter.通过这两个函数实现高精度的计时器. 这两个函数的原型也已经由PQOAL实现.
初始化TIMER4作为系统时钟.TIMER4是一个16bit的定时器.此函数将TIMER4设置成为自动转载模式.
2.1.2.1 Variable Tick Scheduler
可变的系统时钟节拍,这个是WinCE5.0中增加的新的性能.
每一次定时器中断时候,内核分析所有线程后决定切换到哪个线程运行.假如所有线程都在等待状态,系统将进入idle状态.在这个状态的时候,任何中断都会唤醒系统重新开始调度.一般系统大部分时间是处于idle状态的,内核会调用OEMIdle()进入idle状态,我们已经知道这个状态会被任何中断唤醒. 在以前的版本中,系统中断(即上面的TIMER4中断)每毫秒产生一次,查看系统是否需要重新调度. 为了节电,不希望中断那么频繁.于是WinCE5.0中,在调用OEMIdle()之前会先调用pOEMUpdateRescheduleTime().通过这个函数重新设置侠义次系统时钟中断的时间.
2.2 KernelInit()
再看看KernelInit()函数
不过多关注KernelInit().
2.3 FirstSchedule
位于armtrap.s的一个label.开始第一个线程调度.整个内核开始运行.
//------------------------------------------------------------//
//
//-----------------------------------------------------------//
上一次写了一点critical section的用法,实际上还是比较肤浅,本来做得就不多。这两天主要是研究wince的线程调度。
wince调度的基本单位应该是线程,而且对每一个线程都有一个优先级。wince的优先级是0~255,其中0~247是实时优先级,248~255一般为应用程序的优先级。一个应用程序的优先级默认是251。创建线程的时候没有办法直接设置优先级,但是应用程序可以动态提升自己的线程的优先级,有两个API可用SetThreadPriority和CeSetThreadPriority。前者可在248~255范围内调节,后者可提升至实时线程。对于提升优先级,应用程序的优先级不要高于设备驱动,否则可能会有问题。
线程调度和时间片大小有很大关系,时间片大小一般在OEMinit()中初始化,也可以通过SetThreadQuantum来修改,当然也有对应的Get函数。一般默认是100ms。
wince的调度原则是高优先级线程可以实时抢占低优先级线程的cpu资源。所以在调用SetThreadPriority的时候需要多加注意。因为一旦提升了的线程的优先级高于本线程,本线程将无法得到执行。而把SetThreadPriority放入被提升的线程中也是会引发难以想象的问题。我用的一种办法是主调线程先提升自己的优先级,然后再创建新线程,并设CREATE_SUSPENDED,SetThreadPriority之后再ResumeThread。但是有些场合不太适合这样用。
对于同等级的线程,会轮流使用时间片。同一个进程的多个线程也会参与公平竞争。
对于windows的微内核结构,驱动程序都不是内核进程,device.exe会用一个线程执行驱动程序的函数。而线程的默认优先级就是251,不爽。呵。
去复习考试了,今晚又要少睡。其实从最近几个月来看,过去的几年是睡多了。
//------------------------------------Wince调度----------------//
1.CE线程调度信息
Windows CE调度是基于可抢占时间片轮转调度算法[1],系统根据线程的当前优先级BCPrio来确定哪个线程运行,一个正在运行的线程被抢占运行权后,其剩下的时间信息应保留。
?/P>
名称 |
含义 |
BBPrio |
基本的优先级 |
BCPrio |
当前优先级 |
DwQuantum |
线程所拥有的时间片 |
DwQuantleft |
线程所剩下的时间片 |
2.调度时机
x 线程状态转换
x 可运行队列的头部插入了一个线程
x 时间片用完或被抢占
x 中断处理完后
3.关于调度的文件
C:/WINCE410/PRIVATE/WINCEOS/COREOS/NK/KERNEL/schedule.c
C:/WINCE410/PRIVATE/WINCEOS/COREOS/NK/INC/ schedule.h
C:/WINCE410/PRIVATE/WINCEOS/COREOS/NK/INC/kernel.h
//-----------------挖掘WinCE 5系统调用过程 ------------------------//
//网址:http://cpuwolf.blogspot.com/2008/07/wince-5.html
//说明:这个兄弟的博客都是写Wince的,相当有参考价值
int shellcode[] =
{
0xE59F0014, // ldr r0, [pc, #20]
0xE59F4014, // ldr r4, [pc, #20]
0xE3A01000, // mov r1, #0
0xE3A02000, // mov r2, #0
0xE3A03000, // mov r3, #0
0xE1A0E00F, // mov lr, pc
0xE1A0F004, // mov pc, r4
0x0101003C, // IOCTL_HAL_REBOOT
0xF000FE74, // trap address of KernelIoControl
};
这是黑客常用的缓冲攻击代码形式,这里的32为常数,都是ARM机器指令。
我就要想你展示的不是如何攻击WinCE内核,而是看看WinCE的系统调用是如何实现的。WinCE一个常用的API,KernelIoControl,它的实现体实在内核NK.exe,而function caller只是一个trusted的user级别的application。
你可以想象,这样的API的实现,必然要经历CPU从user processer mode切换到supervisor processer mode。
BOOL KernelIoControl(
DWORD dwIoControlCode,
LPVOID lpInBuf,
DWORD nInBufSize,
LPVOID lpOutBuf,
DWORD nOutBufSize,
LPDWORD lpBytesReturned
);
KernelIoControl超过了4个参数,那么在参数传递时,超过的部分使用堆栈完成的。IOCTL_HAL_REBOOT的参数都没什么用,所以这个我们忽略,传0就可以了,因此r1,r2,r3赋值0.
IOCTL_HAL_REBOOT的数值必须放在寄存器r0。
上面的代码中r4的内容会变为0xF000FE74,代码最后就是跳转到0xF000FE74,你可以看到这个地址很大,在整个内存空间的高地址。同时这个地址是经过编码得到,公式是:
0xf0010000-(256*apiset+apinr)*4
对于KernelIoControl,apiset是0,apinr是99。0xF000FE74是这样得到的。
当代码跳转的这样一个高地址时,会引发prefetch abort,这样exception会被内核 ,也就是NK.exe抓到。然后再对这个出错地址进行解析,就可以知道应用程序想访问那个system cal。
综上,WinCE并不是靠SWI这样的软中断实现system call,而是prefetch abort。