WINCE6.0+S3C2443的启动过程---内核启动
发布日期:2021-06-30 21:55:18 浏览次数:2 分类:技术文章

本文共 6583 字,大约阅读时间需要 21 分钟。

********************************LoongEmbedded********************************

作者:LoongEmbedded(kandi)

时间:2010.11.07

类别:WINCE操作系统

********************************LoongEmbedded********************************

 

WINCE6.0+S3C2443系统启动过程

 

我们知道是NBOOT用来引导EBOOT,继而EBOOT加载并引导WinCE操作系统(NK)。那么,WinCE6.0是如何启动的呢?在前面文章我们知道ebootOEMLaunch函数通过调用下面函数来跳转到dwPhysLaunchAddr处执行

Launch(dwPhysLaunchAddr)

我们先来看看NK.binimage start等一些信息吧

1

上图的start address=0x80245c50,这个地址经过OALVAtoPA()函数转换之后获得的对应的物理地址0x30245C50就是dwPhysLaunchAddr地址,那么这个地址对应的是NK.bin中哪个文件的哪个函数呢?通过命令viewbin –o nk.bin >output.txt输出output.txtrelease目录下,下面看这个文件的下面内容:

 

 

 

2

也可以通过PB6.0中浏览NK.bin的功能来查看这个地址(file->open->file,选择Windows Embedded CE Run-Time Image类型,然后选择NK.bin)

 

3

从图2或图3  start address=0x80245c50对应的是NK.exe入口地址,而NK.exe就是OAL.exe,为什么呢?我们看看release目录下ce.bib的如下内容

 

4

由图4可知,NK.exe就是OAL.exe,那么OAL.exe的入口地址是哪个函数呢?看/Src/Oal/Oalexe下面的source文件

 

5

这样我们就知道eboot跳转到OAL.exestartup函数中执行了。

 

1.       Statup函数的主要部分

Startup入口

 

6

因为EBOOT中已经初始化了相关硬件,所以OAL的启动代码就可以省去这部分工作,接着执行下面部分

 

7

7 tst指令先用r0来和0x8进行与运算,结果为0,所以设置标志位Zero=1,后面的beq指定在zero=1的时候,跳转到下面标号4的地方,下面的链接是ARM汇编beqbne的介绍

 

 

8

8的是将g_oalAddressTable保存到r0,作为参数传递给kernelStart函数,以便KernelStart函数中初始化MMU地址映射(g_oalAddressTable就是地址映射表的地址)。我们怎么来理解

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

这条指令呢?我们知道PC总是指向取指的指令,而不是指向正在执行的指令或是正在译码的指令,一般情况下,人们总是习惯把正在执行的指令作为参考点,称为当前第一条指令,因此,PC总是指向第3条指令。对于ARM指令,有:PC的值=当前指令所在的存储地址+8;对于Thumb指令,则有:PC的值=当前指令所在的存储地址+4,下图是ARM系列的流水线图

 

9

(.+8)这个表达式的. (+之前的点) 是在MSARM编译器在预处理时决定的,其代表它所在的指令的地址(也即这条指令的存储地址)。这里还要知道一点,就是ARM架构的特点,一条指令从加载到执行主要分为预取、译码、执行三个阶段。结合上面提到的“PC的值=当前指令所在的存储地址+8”,因此有恒等式.+8=pc,上面语句可以理解为

r0 = pc+g_oalAddressTable-(.+8) = g_oalAddressTable+ (pc-(.+8)) = g_oalAddressTable

也可以这样理解

r0 = pc+g_oalAddressTable-(.+8)=pc-(.+8)+ g_oalAddressTable=当前指令所在的存储地址+8-(.+8)+ g_oalAddressTable=.+8--(.+8)+ g_oalAddressTable= g_oalAddressTable

为什么要采用这样的方式给r0赋值,而不采用下面的语句呢

 

Ldr r0,g_oalAddressTable (我试过用这条语句,就不能成功跳转,系统无法正常启动)

 

为什么startup函数中不能直接把g_oalAddressTable符号的值赋值给r0来作为KernelStart函数的参数呢?因为在调用KernelStart函数的时候MMU单元还处于关闭的状态,CPU访问外部物理存储器仍然使用的是系统物理地址,而g_oalAddressTable符号记录的是g_oalAddressTable数组的起始存储位置在0x80000000~0x9FFFFFFF范围内的虚拟内存地址。之所以g_oalAddressTable符号记录的是虚拟内存地址而不是物理地址,并且落在了0x80000000~0x9FFFFFFF虚拟内存地址范围内,因为在汇编源代码中使用的用来表达程序地址的字符串在程序链接阶段要接受地址重定位的处理,而地址重定位的依据是config.bib,其中对系统物理存储的使用分配作出安排所使用的全部是0x80000000~0x9FFFFFFF范围内的虚拟内存地址

我们接下来看看从startup函数跳转到KernelStart函数的语句

bl               KernelStart       ; Call the WinCE kernel.

首先我们要知道arm指令集与X86不同,不是通过栈空间来传递参数,而是通过寄存R0,R1,R2,R3来传递,而前面的语句就是把g_oalAddressTable的物理存储地址赋值给r0后传递给KernelStart函数的,到此,硬件平台初始化完成后,oal.exe的启动任务基本完成,余下的启动工作由内核相关且独立于内核的OAL层实现体kernel.dll接管

 

2.       KernelStart函数

这个函数在/WINCE600/PRIVATE/WINCEOS/COREOS/NK/LDR/ARM/armstart.s中定义,这个函数的主要任务就是为WINCE操作系统设置页表并且启用MMU,先看KernelStart入口处

 

10

设置页表的部分就不介绍了,下面看启动MMUcaches部分

 

11

11启用了MMUcaches部分之后,WINCE操作系统后面就使用虚拟内存地址而不是物理地址来访问了。接着KernelStart函数为ABORTIRQFIQUNDEFsupervisor5CPU的特权级状态设置堆栈

 

12

这些工作完成之后KernelStart函数就会调用ARMInit函数,下面看是如何调用ARMIint函数的

 

13

这样在调用ARMIint之前,r0就保存了KDataSrtuct这块数据区域的指针,也即r0指向了KDataSrtuct这块数据区域,这就是传递给ARMIint函数的参数,这个函数这个函数在/WINCE600/PRIVATE/WINCEOS/COREOS/NK/LDR/ARM/armint.c下面定义

 

14

 

ARMIint函数有三个主要的任务:

是调用KernelRelocate()函数进行内核全局变量重定位。

 

15

ARMInit函数执行之前,系统仍无法使用全局变量,因为系统的全局还在ROM区域,对于操作系统而言,出于安全的考虑,只有XIP程序才有读取ROM区域数据的权利,对于大部分WINCE操作系统,只有将数据拷贝到RAM区域才能进行读写,ARMInit函数通过调用KernelRelocate函数对pTOC全局变量重新定位之后,才能读写。对内核启动所需要的

 

全局变量重新定位之后,对内核启动所需要的KDataStruct结构体(它是内核数据结构, 简单理解成共享内存好了, oal kernel都可访问)进行初始化,是将位于OALOEMinitGlobals()函数指针赋值到pKdata的成员变量dwOEMInitGlobalsAddr,这个在后面的内核NKStartup函数将会用到,OEMInitGlobals便是交换oal.exekernel.dll之间的全局指针,下图是OEMInitGlobals函数体

 

16

是调用函数FindKernelEntry通过TOC找到kernel.dll的入口点函数地址,是如何找到的呢?先看FindKernelEntry函数体

 

17

17具体是如何找到kernel.dll的入口函数的呢?我们通过viewbin –o nk.bin >output.txt输出NK.bin的相关信息中的kernel.dll部分信息如下

 

18

我们看kernel.map文件

 

19

再来看下图

 

20

结合图18,图19和图20我们可以知道kernel.dll的入口函数是NKStartup,这样通过调用函数ARMInit函数就返回NKStartup,那么我们回到KernelStart函数来看看是如何跳转到NKStartup函数执行的

 

21

就这样跳转到NKStartup函数执行了。

 

3.       NKStartup函数

这个函数在/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/mdram.c中定义,我们先看看NKStartup函数头的注释吧

 

22

NKstartup函数的主要工作如下:

NK Loader传递过来的KDataStruct结构体数据中获取到供自身启动所需要的数据信息,如下图所示,其中介绍了KDataStruct结构体结构体

 

23

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

 

24

这里涉及到一个很重要的全局变量g_pOemGlobal,它指向结构体OEMGLOBAL全局变量OemGlobal,而OemGlobaloemglobal.c定义并对其成员函数赋值,下图是其中一部分

 

25

在这里,我们看到了一个很重要的函数OEMInit,这些OEM函数就是被内核用来和OAL部分通信的。

 

调用函数ARMSetup为操作系统内核进程填充虚拟内存0XA0000000~0XBFFFFFFF范围的uncachable静态映射区域,在前面的KernelStartup函数中,依据g_oalAddressTable数组的数据内容将所有的系统物理地址单元(包括所有类型的CPU可直接寻址访问的物理存储设备和各类外设的控制与状态寄存器、数据缓冲区而后I/O端口)映射到虚拟内存的0x80000000~0x9FFFFFFF地址范围。这是WINCE操作系统的一个约束,正是由于这个缘故,在基于WINCE操作系统中所有的系统物理地址单元总容量不得超过512MB,在ARMSetup函数中再一次将全部的物理存储映射到虚拟地址的0xA0000000~0xBFFFFFFF地址范围。

 

26

另外ARMSetup函数还设置中断向量表,如下

 

27

 

如果系统启用了KITL调试功能,则加载KITL模块

 

28

至于kitl.dll的入口函数值如何确定的呢?这个办法可以参考上面介绍到如何定位kernel.dll的入口函数一样,我们可以找到kitl.dll的入口函数是KitlDllMain()。当在编译选项(Build Options)中选中Enable KITL时,编译器就会将kitl.dll动态链接库链接到到内核中,在OEMGLOBAL结构体定义了利用KITLOEM宏将kitl.dll的入口函数KitlDllMain编译到系统中条件,这是系统链接的库为oemmain_statickitl.lib,下面为OEMGLOBAL结构体中KITL的定义部分,我们看OemGlobal全局数组中的pfnKITLGlobalInit成员的初始化

 

29

 

调用OAL函数执行对目标平台板级硬件的初始化动作。

 

30

30注释中的OEMInitOAL中用来实现对板级硬件进行初始化的一个很重要的OEM函数,其主要的任务是初始化cache全局变量,初始化LCD控制器,初始化中断,初始化系统时钟和初始化KITL等,其他OAL函数也是通过这样的方式被内核调用来和OAL层通信的。

 

清楚TLB,分隔RAM区域和跳转到KernelStartup函数执行。

 

31

KernelFindMemory()是查找系统可用内存并且分隔,在WINCE操作系统中,RAM空间主要分为存储内存和程序内存,存储内存主要为文件的存储空间,包括内核文件和复制到系统中所有目标文件,程序内存为运行程序时所需要的存储空间,见相关的博文

 

NKStartup函数完成自己的工作之后跳转到第二个KernelStart函数,这里的KernelStart函数与前面的KernelStart函数的属于两个完全不同的函数,NKStartup函数中调用的KernelStart 函数为/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/armtrap.s文件中的KernelStart 函数,主要完成调用内核初始化函数KernelInit,并跳转到操作系统的第一个启动的任务,这个KernelStartup函数与NKstartup函数同属一个静态库及同一个kernel.dll模块,而上一个KernelStartup函数所属的nkldr.lib库被链接进了WINCE系统模块oal.exe中。

 

4.       第二个KernelStart函数

 

32

这个函数的主要工作有两部分:

调用KernelInit函数来初始化内核

 

33

在目标设备上的硬件初始化结束后,下一步就是调用KernelInit内核初始化函数来初始化WINCE操作系统本身,WINCE6.0的内核初始化函数同其他版本的内核初始化函数基本相近,主要完成在启动第一个线程前对内核进行初始化,主要包括API函数集初始化、堆的初始化、初始化内存池、进程初始化、线程初始化和文件映射初始化等操作。下面来看THRDIint函数体最重要的一部分,THRDInit()初始化了第一个线程是SystemStartupFunc

 

34

FirstSchedule

FirstSchedule函数为Windows CE操作系统启动过程中最后无条件跳转的一个函数,FirstSchedule调用HandleException函数来让系统进行重新调度,这样就选择了第一个就绪的线程(也就是SystemStartupFunc)来执行,下面接着看线程处理函数SystemStartupFunc

 

35

接着看RunApps线程

 

36

RunApps线程处理函数主要是启动filesys.dll,启动后等待其执行的情况,在完成了文件系统的相应的初始化之后,这里继续初始化MUI和系统设置,完成后再通知filesys这边的工作已经完成,filesys继续启动,filesys会完成WinCE的最后启动过程,包括设备管理device.dll,窗体图像子系统gews.dllshell程序 explore.exe,这样,WINCE6.0的启动就完成了。

 

备注:对于fiesys文件系统的启动过程,可以看MSDN的描述

 

参考文档

 

 

转载地址:https://loongembedded.blog.csdn.net/article/details/5994499 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:volatile 使用说明
下一篇:NKStartup的参数KData

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月24日 18时38分14秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章