嵌入式操作系统
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.2.5 操作系统核心功能的初始化

在MINIKER.BIN模块初始化完成之后,通过一条跳转指令,直接跳转到MASTER.BIN开始处继续执行。需要注意的是,MASTER.BIN是采用Windows操作系统下的C++编译器编译的,编译结果为PE文件格式。这种文件格式的最开始部分,是一个PE文件头,并不是可执行的二进制代码。因此,我们通过一个特殊的工具(也是采用C语言编写的PE文件处理工具),把PE文件的开头部分采用可执行的指令替换,并从PE头中,提取出文件的入口地址,然后采用一条跳转指令,跳转到入口处执行。下面是MASTER.BIN文件的开始部分(MASTER.BIN文件已经经过处理,下面是对MASTER.BIN进行反汇编所得结果的开头部分)。

00000000  90              nop
00000001  90              nop
00000002  90              nop
00000003  E9E8500000       jmp 0x50f0
00000008  0400            add al,0x0
0000000A  0000            add [eax],al
0000000C  FF              db 0xFF
0000000D  FF00            inc dword [eax]
0000000F  00B800000000     add [eax+0x0],bh

上述代码中,关键的一条JMP指令,跳转到了MASTER.BIN的入口处。

下面是MASTER.BIN入口函数的实现代码,为了便于阅读,我们分段解释。

void __init()
{
    __KERNEL_THREAD_OBJECT*      lpIdleThread   =NULL;
    __KERNEL_THREAD_OBJECT*      lpShellThread  =NULL;
    __KERNEL_THREAD_OBJECT*      lpKeeperThread =NULL;
    DWORD                      dwKThreadID   =0;
    DisableInterrupt();   //The following code is executed in
                          //no-interruptable envrionment.

DisableInterrupt函数禁止了外部可屏蔽中断。实际上,在MINIKER.BIN的初始化过程中,已经禁止了中断,在此又重新做一个禁止中断操作,是为了编码上的统一。因为在该函数的尾部,会调用EnableInterrupt函数启用中断。

ClearScreen();        //Print out welcome message.
PrintStr(pszStartMsg1);
PrintStr(pszStartMsg2);
ChangeLine();
GotoHome();

上述几个函数做了一个清屏操作,然后打印出了两行提示信息,以指示用户目前系统引导状态。

g_keyHandler=SetKeyHandler(_KeyHandler);  //Set key board handler.

SetKeyHandler函数用于设置键盘中断处理程序。在Hello China目前版本的实现中,对于键盘驱动程序,是在MINIKER.BIN模块里实现的,这样用户的按键消息最初会被MINIKER.BIN模块捕获。为了把按键消息传递给MASTER.BIN模块,设计了一个回调机制,即在MASTER.BIN中实现一个处理函数(该函数就是_KeyHandler),把该函数的地址,传递给MINIKER.BIN模块中的一个变量(该变量位于MINIKER.BIN末尾的特定位置),这样一旦发生键盘中断事件,MINIKER.BIN模块就以适当的参数调用该函数,MASTER.BIN就可以接收到这个按键事件,从而做进一步处理。

*(__PDE*)PD_START=NULL_PDE;

上述代码用于完成页索引对象的初始化工作,详细信息,请参考第5章。

    #ifdef  __ENABLE_VIRTUAL_MEMORY     //Should  enable  virtual  memory
model.
    lpVirtualMemoryMgr=(__VIRTUAL_MEMORY_MANAGER*)ObjectManager.
CreateObject(&ObjectManager, NULL, OBJECT_TYPE_VIRTUAL_MEMORY_MANAGER);
                              //Create virtual memory manager object.
    if(NULL==lpVirtualMemoryMgr)   //Failed to create this object.
        goto __TERMINAL;
    if(!lpVirtualMemoryMgr->Initialize((__COMMON_OBJECT*)lpVirtualM
                                  emoryMgr))
        goto __TERMINAL;
    #endif

上述代码创建针对整个系统的虚拟内存管理器,并对之进行初始化。在Hello China的实现中,为了对虚拟内存进行管理,实现了一个虚拟内存管理器(Virtual Memory Manager)的对象,用于对系统或单个进程的地址空间进行管理。目前,尚没有实现进程机制,因此整个系统只有一个虚拟内存管理器。但在未来的实现中,可能会引入进程模型,这样系统中就可能存在多个虚拟内存管理器对象(每进程一个),因此,没有把虚拟内存管理器对象作为全局对象实现,而是作为一个核心对象来实现,尽管目前情况下,整个系统只有一个虚拟内存管理器对象。另外需要注意的是,虚拟内存功能(在IA32构架CPU的实现中,表现为分页机制)是一个可选择的实现模块,通过预先定义的一个宏__ENABLE_VIRTUAL_MEMORY来控制。若在代码中定义了该宏,在编译操作系统核心的时候,虚拟内存管理功能就会被包含,若没有定义该宏,则不会包含虚拟内存管理功能。

if(!KernelThreadManager.Initialize((__COMMON_OBJECT*)&KernelThread Manager))
    goto __TERMINAL;
if(!System.Initialize((__COMMON_OBJECT*)&System))
    goto __TERMINAL;
if(!PageFrameManager.Initialize((__COMMON_OBJECT*)&PageFrameManager,
    (LPVOID)0x02000000,
    (LPVOID)0x09FFFFFF))
    goto __TERMINAL;
if(!IOManager.Initialize((__COMMON_OBJECT*)&IOManager))
    goto __TERMINAL;
if(!DeviceManager.Initialize(&DeviceManager))
    goto __TERMINAL;
lpIdleThread=KernelThreadManager.CreateKernelThread(
                                      //Create system idle thread.
    (__COMMON_OBJECT*)&KernelThreadManager,
    0L,
    KERNEL_THREAD_STATUS_READY,
    PRIORITY_LEVEL_LOWEST,
                                      //Lowest priority level.
    SystemIdle,
    (LPVOID)(&dwIdleCounter),
    NULL);
if(NULL==lpIdleThread)
{
    //PrintLine("Can not create idle kernel thread,please restart the
              system.");
    __ERROR_HANDLER(ERROR_LEVEL_FATAL,0L,NULL);
    goto __TERMINAL;
}
lpShellThread=KernelThreadManager.CreateKernelThread(
                                      //Create shell thread.
    (__COMMON_OBJECT*)&KernelThreadManager,
    0L,
    KERNEL_THREAD_STATUS_READY,
    PRIORITY_LEVEL_NORMAL,
    SystemShell,
    NULL,
    NULL);
if(NULL==lpShellThread)
{
    //PrintLine("Can not create system shell thread,please restart the
system.");
    __ERROR_HANDLER(ERROR_LEVEL_FATAL,0L,NULL);
    goto __TERMINAL;
}
g_lpShellThread=lpShellThread;
                                    //Initialize the shell thread
                                  global variable.
if(!DeviceInputManager.Initialize((__COMMON_OBJECT*)&DeviceInputManager,
                              NULL,
                              (__COMMON_OBJECT*)lpShellThread))
                                //Initialize the DeviceInput
                                  Manager object.
{
    __ERROR_HANDLER(ERROR_LEVEL_FATAL,0L,NULL);
    goto __TERMINAL;
}

上述代码完成了下列两项初始化功能。

(1)创建了空闲线程(Idle Thread)和用户交互线程(Shell Thread)。空闲线程在CPU空闲的时候被调度,用户线程用于完成用户界面功能。其中,Idle线程必须被创建,以完成CPU空闲时的处理,而Shell线程则根据需要创建。在基于PC环境的Hello China中,Shell用于完成用户输入/输出功能,若移植Hello China到其他硬件环境,Shell线程则可根据需要决定是否创建。

(2)完成全局对象的初始化。所谓全局对象,就是整个系统运行环境只存在一个的对象,这个对象一般用于对整个系统中特定部分资源的统一管理。任何一个全局对象初始化失败,都将会导致系统停止启动,进入死循环。表2-4列举了上述初始化的全局对象,以及这些对象的功能。

表2-4 核心设备管理对象

这些对象的详细功能以及其实现方式等,将会在后面章节进行详细介绍,这也是本书的重点内容。需要注意的是,DeviceInputManager对象是在Shell线程创建之后才初始化的,因为该对象的初始化函数需要有一个具体的线程作为当前焦点线程(也可以不指定焦点线程),这样后续的任何主动输入(键盘、鼠标等用户交互设备的输入),都可以被定向到当前焦点线程。在当前的实现中,Shell线程被作为当前焦点线程,即任何用户输入,首先被Shell感知,然后由Shell做进一步处理,这符合Shell线程的功能。当然,可以根据需要,采用其他线程来替代Shell线程,以作为当前焦点线程。比如,可以把Hello China移植到一个手持设备上,这样需要实现一个交互式的图形界面。这时候,可以把这个交互式的界面,以一个线程的形式实现,并把该线程作为当前焦点线程,任何输入,都可以定向到该线程,从而完成用户和设备的交互。

#ifdef __ENABLE_VIRTUAL_MEMORY
    //
    //Now,we enable the page mechanism.
    //
    __asm{
        push eax
        mov eax,PD_START
        mov cr3,eax
        mov eax,cr0
        or eax,0x80000000
        mov cr0,eax
        pop eax
    }
#endif

上述代码完成了IA32 CPU环境下,分页机制的使能。在此之前,所有对内存的访问都是把线性地址直接映射到物理地址的,在使能分页机制之后,对内存的访问将经过分页机制的映射。在当前的实现中,把线性地址空间的前20MB依然映射到物理内存的前20MB,这样可实现分页机制对操作系统代码的透明程度。当然,分页机制是否使能,是可以通过定义宏__ENABLE_VIRTUAL_MEMORY来进行控制的。

SetTimerHandler(GeneralIntHandler);

上述代码用于连接通用中断处理程序和中断。在当前的实现中,对所有的中断处理,都是采用同一个函数GeneralIntHandler作为入口的,然后通过GeneralIntHandler再调用System对象的相应函数,完成中断的进一步分发。在Hello China的当前实现中,GeneralIntHandler是在MASTER.BIN模块中实现的,而所有的中断描述表(IDT),则是定义在MINIKER.BIN中的。SetTimerHandler函数完成连接GeneralIntHandler和MINIKER.BIN模块中的中断处理程序的功能。从名字上看,该函数似乎是完成时钟中断的连接的,这是由于历史原因造成的,目前情况下,SetTimerHandler可以完成任何中断和通用中断处理程序的连接。对于中断的详细信息,请参考第8章。

    StrCpy("[system-view]",&HostName[0]);
    EnableInterrupt();
    DeadLoop();
    //The following code will never be executed if corrected.
__TERMINAL:
    ChangeLine();
    GotoHome();
    //PrintStr("STOP : An error occured,please power off your computer
and restart it.");
    __ERROR_HANDLER(ERROR_LEVEL_FATAL,0L,"Initializing      process
failed!");
    DeadLoop();
}

上述代码打印出提示符(机器名),并启用中断,然后进入一个死循环。这个死循环的作用,是为了等待一个时钟中断发生后,开始正式调用线程。实际上,系统初始化过程的代码,包括REALINIT.BIN、MINIKER.BIN等模块,不属于任何线程,或者可以看作是一个初始化线程,系统一旦初始化完毕,这个初始化线程就算运行完毕,这时候,如果不进入一个死循环,则__init函数返回后,可能会导致CPU进入一个不确定的状态,从而导致系统崩溃。需要注意的是,这个死循环,并不会真正导致系统死循环,一旦时钟中断发生,线程调度程序会选择一个状态为“就绪”的线程(Idel或Shell),重新投入运行,这样初始化线程就算正式结束了。

最后部分的代码是出错处理部分。在初始化过程中,遇到任何一个错误都可能导致初始化失败,__init函数跳转到__TERMINAL标号处,打印出一个出错信息,然后进入死循环。这时候必须采用关闭电源的方式,对计算机进行重新引导。

到此为止,Hello China的启动就算完成了,这之后,Shell线程将得到调度,从而完成用户和计算机之间的交互。