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线程将得到调度,从而完成用户和计算机之间的交互。