Starting the Operating System
Image Preparation
当编译一个系统内核的时候,下面的一些工作必须完成,为内核执行做好准备:
1. 适当的准备好系统镜像的执行。(与.bib文件的CONFIG段一致)
2. 创建一个专用的结构体,包含内核镜像的信息和一个列表(TOC)。
3. 在Nk.exe中分配pTOC的值,pTOC是TOC的一个指针。
这样内核镜像就包含了一个描述这个镜像内容的结构体。为了运行系统,bootloader必须加载镜像到正确的地址;然后必须确认在镜像起始地址的0x40偏移位置,CECE的签名必须存在。紧接着,有一个指向ROMHDR结构体的指针和一个TOC结构体的指针。Bootloader读取TOC结构体指针的值并且确认NK.exe 的入口。然后,跳转到内核启动函数的入口地址。
系统启动函数StartUp()是用汇编写的。执行的过程可以独立开来。一部分运行在platform的common code(/PLATFORM/COMMON/SRC/SOC/<SOC_DIR/OAL/STARTUP/>和/PLATFORM/COMMON/SRC/<CPU_FAMILY>/COMMON/STARTUP),一部分在BSP 代码中(/PLATFORM/<PLATFORM_NAME>/SRC/OAL/OALLIB/)。这个函数的代码严格依赖于平台。StartUP函数的主要工作是转换处理器到一个特定的状态和完成底层的硬件初始化,包括初始化内存控制器,关闭中断,TLB cashe和内存管理单元模块,还有完成SOC的初始化。当处理器初始化完毕,StartUp函数就会调用KernelStart或者Kernellnitialize(x86)(/PRIVATE|WINCEOS/COREOS/NK/LDR/<CPU_FAMILY>/)。KernelStart/Kernellnitialize函数完成以下主要功能:
1. 拷贝ROMHDR中定义的段到RAM中(通过KernelRelocate()函数)。然后,Nk.exe的全局变量就可以进行读写操作。
2. 初始化OEMAddressTable中的第一页(ARM或者x86)。
3. 打开MMU模块和cashe (ARM或者x86)。
4. 找到Kernel.dll的入口点(FindKernelEntry)。
5. 通过传递一个指针到KdataStrucet结构体中,调用内核入口点。该结构体还包括了OEMinitGlobals和OEMAddressTable函数的指针(ARM或者x86)。
在当前的实现中,kernel,dll入口函数是NKStartup()(/PRIVATE/WINCEOS/COREOS/KERNEL/<CPU_FAMILY>/)。内核入口函数完成以下的功能:
1. 初始化NKGLOBALS结构体。这个结构体包括了内核(Kernel to OAL)和KITL(如果作为一个独立的DLL库存在的话)导出的所有的函数和变量。
2. 调用OEMInitGlobals函数,传递NKGLOBALS结构体给它。
3. OEMInitGlobals函数 返回OEMGLOBALS结构体。该结构体包含了所有OAL(OAL to KERNEL)和KITL导出的函数和变量。
4. ARM处理器调用ARMSetup()函数,这里MIPS处理器调用MIPSSetup()函数。
5. 如果内核包括KITL,内核尝试加载它和调用入口函数。
6. 调用OEMInitDebugSerial()函数。
7. 通过OEMWriteDebugString()函数,内核输出“Windows CE Kernel for....”启动消息。
8. 调用OEMInit()函数初始化硬件平台。
9. 调用KernelFindMemory()函数(/PRIVATE/WINCEOS/COREOS/NK/KERNEL/loader.c)。
10. 调用KernelInit()函数(/PRIVATE/WINCEOS/COREOS/NK/KERNEL/nkinit.c)。如果是ARM处理器, 调用KernelStart()(/PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/armtrap.s)函数来调用Kernellinit()函数。
11. 在一些体系结构中,当KernelInit退出后将强制重新调度。(貌似不应该这么翻译)
Startup Process
7-1图显示了一部分系统启动流程:StartUp() - KernelStart()/KernelInitialize() – NKStartup()(<Kernel Entry>())。OAL层的代码用灰色显示。图中还显示了主要完成的一些任务和被调用的函数。
OEMInit()函数在OAL层中实现,它主要完成平台的初始化,包括中断,时钟,KITL和总线。
KernelInit()调用以下的函数:
1. APICallInit()函数配置了系统的API:/PRIVATE/WINCEOS/COREOS/NK/KERNEL/apicall.c
2. HeapInit()函数初始化了内核堆:/PRIVATE/WINCEOS/COREOS/NK/KERNEL/heap.c
3. InitMemoryPool()函数初始化了物理内存池:/PRIVATE/WINCEOS/COREOS/NK/KERNEL/physmem.c
4. PROCinit()函数初始化了基本进程调度:/PRIVATE/WINCEOS/COREOS/NK/KERNEL/process.c
5. VMInit()函数初始化了虚拟内存:/PRIVATE/WINCEOS/COREOS/NK/KERNEL/vm.c
6. THRDInit()函数初始化线程;创建一个SystemStartupFunc 函数的线程和调用MakeRun()函数执行该线程。/PRIVATE/WINCEOS/COREOS/NK/KERNEL/thread.c
7. MapfileInit()函数初始化内存映射文件的支持:/PRIVATE/WINCEOS/COREOS/NK/MAPFILE/mapfile.c
SystemStartupFunc()(/PRIVATE/WINCEOS/COREOS/NK/KERNEL/schedule.c)函数完成以下任务:
1. 调用KernelInit2()函数完成内核的初始化。
2. 调用LoaderInit()函数初始化EXE/DLL的内核loader。/PRIVATE/WINCEOS/COREOS/NK/KERNEL/loader.c
3. 初始化一个cookie保护堆栈:__security_init_cookie()。
4. 初始化一个页池:PagePoolInit(),CELog,profiler,etc。LoggerInit(),系统的debugger-SysDebugInit()。
5. 调用IOCTL — IOCTL_HAL_POSTINIT。开发者可以使用该方法去做一些内核初始化完成后的一下额外的初始化过程。
6. 创建两个立即执行的线程。第一个线程实现PowerHandlerGuardThrd函数,第二个线程实现RunApps函数。
RunApps()(/PRIVATE/WINCEOS/COREOS/NK/KERNEL/kmisc.c)函数完成以下任务:
1. 加载filesys.dll。
2. 创建一个立即执行的线程,它的入口点就是filesys.dll。
3. 如果镜像包括filesys.dll和文件系统,它将等待文件系统被初始化(SYSTEM/FSReady event),然后加载MUI和从注册表中加载系统配置和通知子系统完成必须的任务:(*pSignalStrarted)(0)。
4. 后台运行一个线程作为废弃页的回收。
图7-2显示了一部分系统启动的流程:KernelInit()-SystemStartupFunc() - RunApps().图中还显示了主要完成的一些任务和被调用的函数。
Loading the File System
继续说明加载filesys.dll的流程。与前面的内核部分不同,filesys.dll的源代码不再Shared Sources中提供。因此,filesys.dll的加载只能通过load log和使用和filesys.dll有关的代码来跟踪。
接下来,我们看以下cold boot.在cold boot阶段,filesys.dll完成以下主要功能:
1. 初始化对象存储内存并且自己映射它。
2. 初始化一些文件系统的API和中间层的API(数据库,消息队列,事件log,和注册表)。
3. 初始化注册表数据。
※ 不同类型的注册表有这不同的初始化过程(hive-based 或者 RAM-based).
※ 在该阶段,设备管理器(device.dll)可以加载一些必要的驱动去加载一些hive-base注册表将要存储的media。
※ 如果设备管理器在该阶段加载,在这些必要的驱动加载完毕后,device.dll将被挂起等待filesys.dll初始化的完成。
4. 通知内核filesys.dll完成了基本的初始化(设置SYSTEM/FSReady 事件)并且等待系统通知-(*pSignalStarted)(0)完成下一步的初始化。(参考上面的RunApps()函数)
5. Filesys.dll加载注册表中HKEY_LOCAL_MACHINE/Init的应用程序。
※ 如果注册表键值中包含设备管理器并且它已经被加载了,filesys.dll设置SYSTEM/BootPhase2事件。在收到这个消息之后,设备管理器继续加载驱动(/PRIVATE/WINCEOS/COREOS/DEVICE/DEVCORE/devcore.c)。
Filesys.dll初始化完成后,系统就可以运行了。图7-3显示了filesys.dll的初始化流程和一些主要的完成的任务:
Loading the Device Manager
设备管理器(device.dll)读取HKEY_LOCAL_MACHINE/Drivers的值。接下来,调用ActivateDeviceEx。默认情况下,该值和/Driver/BuiltIn相同。
HKEY_LOCAL_MACHINE/<RootKey>包括bus enumerator(BusEnum.dll)的配置。Bus enumerator驱动读取所有的注册表中的子键,并且每一个Key 都调用ActivateDeviceEx()函数。调用ActivateDeviceEx()函数的顺序是由Order值来决定的。Order值小的先加载。没有Order值的驱动最后加载。
如果设备管理器在注册表初始化完成后加载,它首先加载注册表的boot段。加载顺序和上面相同。
看一下在系统启动阶段自动加载应用程序的HEKY_LOCAL_MACHINE/Init的值。Init Key包括2种类型的值:LaunchXX和DependXX,XX的值可以是00-99。
LaunchXX包括REG_SZ类型的值,这个名字必须是需要被加载的程序,例如 program.exe。XX的值决定加载次序。XX值越小,应用程序越先被加载。
DependXX包括REG_BINARY类型的值,通过LaunchXX Key的值,可以决定应用程序间的依赖关系的加载次序。Indexes of XX applications that the specified application is dependent on are indicated as a list of words string (word – 2 bytes),with the words’byte order reversed。
在Init key中的应用程序必须通知系统它被加载成功并且和它依赖的程序能通过调用SignalStarted()函数加载,在加载阶段系统通过命令行参数传递一个参数给该函数。这就是为什么当从Init Key中加载应用程序时不能指定命令行参数。
以下是一个registry key Init内容的例子:
[HKEY_LOCAL_MACHINE/Init]
"Launch10" = "shell.exe"
"Launch20" = "device.dll"
"Launch20" = "hex:0a,00"
"Launch30" = "gwes.dll"
"Launch30" = "hex:14,00"
"Launch50" = "explore.exe"
"Launch50" = "hex:14,00,1e,00"
在这种情况下,shell.exe程序将被最先加载;接下来-设备管理器(device.dll)依赖于shell.exe(在这个例子中);接下来gwes.dll被加载,依赖于设备管理器;最后explorer.exe被加载,依赖于设备管理器和gwes.dll。
(由于刚开始接触WIndows CE,纯属为了学习而去翻译该文档,必然有翻译错误的地方,有问题请大家直接指出,或者联系luocan1358@126.com)