现代操作系统大部分都采用了分页的方式对物理内存进行管理,Intel 从80386之后也在硬件上支持的分页管理,为操作系统的设计和实现带来了很多便利之处。
由于实际去实现一个操作系统的内存管理内核是一件相对比较困难的事情,因此我们今天将用模拟的方式来设计和实现一个操作系统的分页管理内核。由于我自己也是一个学生,因此有说的不妥甚至错误的地方,请各位看官不要吝啬您的意见建议甚至批评。如无特别说明,下文的物理内存地址一词均指模拟物理内存中的物理地址。
一切开始以前先来确定一些基准数据:
1. 物理页的大小:物理页的大小通常和硬件系统有关,
2. 比如Intel80386及以后的处理器都是按4KB大小来分页。这里我们也选择用4KB来作为分页的尺寸。
3. 逻辑页的大小:逻辑页的大小个人认为和物理页取同4. 样大小最好,5. 因为这样可以不6. 用担心页边界问题,7. 并且在设计分页变换机构时比较容易实现。因此也取为4KB。
8. 系统能有效管理的最大内存:作为一个模拟系统,9. 这里我取32MB作为最大内存管理上限,10. 太大了没什么实际意义,11. 再说模拟系统跑起来也会很占资源。
12. 页号地址范围:依照逻辑页尺寸和支持的最大内存,13. 系统中最多可有8192个逻辑页面,14. 则页面地址为0x0-0x1FFF
15. 页内偏移量范围:页尺寸取为4KB 则页内偏移地址为0x0-0x0fff.。
在分页管理系统中,地址的通常不止一种。在我们现在要做的系统中,地址分为3类,既分页地址,线性地址,虚拟地址。
线性地址:线性地址既存储器的物理地址,在系统内核未初始化和初始化时,都将使用线性地址操作内存。
虚拟地址:虚拟地址是在分页后产生的。其含义和线形地址相同,在系统中存在的各个进程,都使用虚拟地址访问内存,虚拟地址传入内核后,内核利用该进程的页表和自身的页地址变换机构将虚拟地址映射为某一个线性地址。
分页地址:分页地址由2部分组成,按照上文的基准数据设定,其结构如下:
BBBBBBBBBBBBB:BBBBBBBBBBBB
前一部分共13bit用来记录页号地址,后一部分共12bit用来记录页内偏移量。由于我们将物理页和逻辑页的尺寸统一了,因此分页地址所表示的整体意义和线性地址是一样的。
例如:分页地址0000000000001000000000001 是0x1001。作为分页地址,它表示物理内存中第二个物理页(注意,不是第一个物理页,因为0x0000也是有效地址,表示的是第一个物理页),页内偏移量为01。这个分页地址所表示的线性地址为:页号×页尺寸+页内偏移量,即:1×4096+1=4097 (0x1001)。可以看到,从整体上来看,分页地址和线性地址在意义上一样的,但是分页地址和线性地址所表示的自身含义则是完全不同的,这一点在逻辑页和物理页尺寸不统一的情况下尤为明显,并且在这种情况下,分页地址和线性地址的整体含义也就完全不同了。由此看出,逻辑页和物理页尺寸的统一给我们设计和实现内核的地址变换部分提供了很大的便利。
在正式开始设计内核前,先来看看现代操作系统的一个重要特征:硬件无关性。这个特征通常是提供给用户的。但是个人认为,在操作系统的底层设计中,也应该有一个接口部分是直接操作硬件,而其他部分在这个部分之上,通过这个接口去访问和控制硬件。这样做可以使得操作系统的实现可以在某种程度上脱离具体硬件规格,并且可以给移植带来方便。这种设计思路在Microsoft Windows NT家族的中得到了具体的体现,NT中使用了“硬件抽象层”(HAL)的概念来分离操作系统的相对高层部分和底层硬件。各个高层部分通过HAL层来访问和控制硬件,而不是将各种对硬件的访问和控制操作分散在系统的各个角落。这种设计思路将在我们今天即将开始设计的模拟内核中再一次得到体现。这就是“模拟硬件抽象层”(EmuHAL)。
EmuHAL的设计思路完全取自Windows NT的HAL。作为给这个模拟内核搭配的HAL层,EmuHAL将提供一个基本上完全真实的物理内存环境供我们来操作,你可以认为通过EmuHAL层进行操作和访问的是完全的一个物理硬件,而不必要将其想象成别的什么。在这里,EmuHAL层通过利用一个数组,来提供真实的物理内存操作环境。下面是其提供的接口函数:
DWORD APIENTRY Fn_HAL_Init(LPDWORD lpMemoary,
UDWORD NewMemoaryLength);
BYTE APIENTRY Fn_ReadByte(UDWORD Address);
VOID APIENTRY Fn_WriteByte(UDWORD Address,DWORD Value);
WORD APIENTRY Fn_ReadWord(UDWORD Address);
VOID APIENTRY Fn_WriteWord(UDWORD Address,DWORD Value);
DWORD APIENTRY Fn_ReadDword(UDWORD Address);
VOID APIENTRY Fn_WriteDword(UDWORD Address,DWORD Value);
这些函数都是对内存的原子操作的模拟。除去第一初始化函数外,从上到下一共3组,每组对应一个读写服务集,分别为:字节读/写集,字读/写集, 双字读/写集。EmuHAL层的实现源代码如下:
宏定义头文件::Base.h
//=================================================================================
//Name: Base.h
//Funcation: Base Type Define,CPU support,MyUnicode Support.
//Author: kakashi.R
//Last Update: 6.1.2005
//=================================================================================
#ifndef _BASE_H
#define _BASE_H
#define TRUE 1
#define FALSE 0
#define SUCCESS 1
#define FAILURE 0
#define ENABLE 1
#define DISABLE 0
#define HIGH 1
#define LOW 0
#define USED 1
#define UNUSE 0
#define NULL 0
#pragma message("")
#pragma message("This is A Header File Created by kakashir. :)")
#pragma message("")
#if (defined _IOSTREAM) //Incude iostream.h for I/O.
#pragma message("")
#pragma message("Macro _IOSTREAM actived Now.")
#pragma message("Now iostream.h included for I/O.")
#pragma message("")
#include <iostream.h>
#endif //#if (defined _IOSTREAM)
#if ( !(defined APIENTRY) && (defined _STDAPI) ) //__stdcall style support.
#pragma message("")
#pragma message("Macro _STDAPI actived Now.")
#pragma message("Funcations call style KeyWord APIENTRY Defined as __stdcall.")
#pragma message("")
#define APIENTRY __stdcall
#define CENTRY __cdecl
#define API APIENTRY
#define PASCAL API
#define CALLBACK PASCAL
#endif //#if ( !(defined APIENTRY) && (defined _STDAPI) )
#ifdef _I386 //Intel 80386 and later CPU support and typedefs.
#pragma message("")
#pragma message("Macro _I386 actived NOW.")
#pragma message("Data define Keywords defined for Intel 80386 and later Processer Family.")
#pragma message("")
#if !(defined _UNICODE)
typedef char CHAR;
#endif //#if !(defined _UNICODE )
typedef __int8 BYTE;
typedef __int16 WORD;
typedef __int32 DWORD;
typedef __int64 QWORD;
typedef float FLOAT;
typedef double DOUBLE;
typedef unsigned __int8 UBYTE;
typedef unsigned __int16 UWORD;
typedef unsigned __int32 UDWORD;
typedef unsigned __int64 UQWORD;
typedef void* LPVOID;
typedef WORD* LPWORD;
typedef BYTE* LPBYTE;
typedef DWORD* LPDWORD;
typedef QWORD* LPQWORD;
typedef FLOAT* LPFLOAT;
typedef DOUBLE* LPDOUBLE;
typedef UBYTE* LPUBYTE;
typedef UWORD* LPUWORD;
typedef UDWORD* LPUDWORD;
typedef UQWORD* LPUQWORD;
typedef bool BOOL;
typedef void VOID;
typedef union ___uint128 //128bit unsigned data support.
{
unsigned __int64 __128[2];
unsigned __int32 __32[4];
unsigned __int16 __16[8];
unsigned __int8 __8[16];
}uint128;
typedef union ___sint128 //128bit signed data support.
{
signed __int64 __128[2];
signed __int32 __32[4];
signed __int16 __16[8];
signed __int8 __8[16];
}sint128;
typedef UBYTE U8;
typedef short unsigned U16;
typedef long unsigned U32;
typedef UQWORD U64;
typedef uint128 U128;
typedef BYTE S8;
typedef short signed S16;
typedef long signed S32;
typedef QWORD S64;
typedef sint128 S128;
#endif //#ifdef _I386
#if (defined _UNICODE && defined _I386) // MyUNICODE support!! :)
#pragma message("")
#pragma message("Macro _UNICODE actived NOW.")
#pragma message("The Keyword CHAR(upcase) defined 16bit for support UNICODE now.")
#pragma message("Pleas use cout() to display UNICODE char or String.")
#pragma message("use __char to define Old ANSCII-Style Character :)")
#pragma message("")
typedef char __char;
typedef union ___wchar //Define MyUNICODE struct.
{
U16 Unicode16;
U8 Unicode8[2];
}__wchar;
typedef __wchar CHAR;
#include <UnicodeTable.h> //include MyUNICODE Table :)
#define __IOSTREAM
#include <iostream.h>
#if (defined _IOSTREAM)
void APIENTRY _cout(CHAR Word) //MyUnicode I/O Funcation for character Display
{
if(Word.Unicode8[1] == 0)
{
cout<<(__char)Word.Unicode8[0]<<flush;
}
else
{
if(Word.Unicode8[1] >=0 && Word.Unicode8[1] <= HIGH_WORD_MAX &&
Word.Unicode8[0] >=0 && Word.Unicode8[0] <= LOW_WORD_MAX)
{
_Buffer[0] = *(UnicodeTable[(Word.Unicode8[1]-1)] + (2*Word.Unicode8[0]) );
_Buffer[1] = *(UnicodeTable[(Word.Unicode8[1]-1)] + (2*Word.Unicode8[0])+1 );
_Buffer[2] = '\0';
cout<<_Buffer<<flush;
}
}
}
void APIENTRY _cout(CHAR* Word) //MyUnicode I/O Funcation for String display
{
U32 Length = sizeof(Word)/2;
U32 Tmp_a=0;
for(Tmp_a=0;Tmp_a<Length;Tmp_a++)
{
if((Word+Tmp_a)->Unicode8[1] == 0)
{
cout<<(__char)( (Word+Tmp_a)->Unicode8[0])<<flush;
}
else if ( ((Word+Tmp_a)->Unicode8[1]) >=0 && ((Word+Tmp_a)->Unicode8[1]) <= HIGH_WORD_MAX &&
((Word+Tmp_a)->Unicode8[0]) >=0 && ((Word+Tmp_a)->Unicode8[0]) <= LOW_WORD_MAX )
{
_Buffer[0] = *(UnicodeTable[ ( (Word+Tmp_a)->Unicode8[1] )-1 ] + 2*( (Word+Tmp_a)->Unicode8[0] ) );
_Buffer[1] = *(UnicodeTable[ ( (Word+Tmp_a)->Unicode8[1] )-1 ] + 2*( (Word+Tmp_a)->Unicode8[0] )+1);
_Buffer[2] = '\0';
cout<<_Buffer<<flush;
}
}
}
#define lpUnicode(var,value) ((var)->Unicode16) = value
#define Unicode(var,value) ((var).Unicode16) = value
#define cout(UnicodeString) _cout(UnicodeString)
#endif //#if (defined _IOSTREAM)
#endif //#if (define __UNICODE)
#endif //#ifndef _BASE_H
//===================================================================//
EmuHAL层源代码头文件 Hal_Define.h
#ifndef _HAL_DEFINE_H
#define _HAL_DEFINE_H
#define _STDAPI
#define _I386
#include <base.h>
#define MAX_MEMOARY_SUPPORT 0x02000000
DWORD APIENTRY Fn_HAL_Init(LPDWORD lpMemoary,UDWORD NewMemoaryLength);
BYTE APIENTRY Fn_ReadByte(UDWORD Address);
VOID APIENTRY Fn_WriteByte(UDWORD Address,DWORD Value);
WORD APIENTRY Fn_ReadWord(UDWORD Address);
VOID APIENTRY Fn_WriteWord(UDWORD Address,DWORD Value);
DWORD APIENTRY Fn_ReadDword(UDWORD Address);
VOID APIENTRY Fn_WriteDword(UDWORD Address,DWORD Value);
#endif //#ifndef _HAL_DEFINE_H
//===================================================================//
EmuHAL 层源代码实现文件:Hal.cpp
#ifndef _HAL_H
#define _HAL_H
#include "../include/HAL_Define.h"
static LPDWORD lpMemoary=NULL;
static UDWORD MemoarySize=NULL;
DWORD APIENTRY Fn_HAL_Init(LPDWORD Init_lpMemoary,UDWORD NewMemoaryLength)
{
DWORD Resault=SUCCESS;
_asm
{
mov EAX,[Init_lpMemoary]
cmp EAX,NULL
je ERROR
mov [lpMemoary],EAX
jmp EXIT
ERROR:
mov [Resault],FAILURE
EXIT:
}
MemoarySize = NewMemoaryLength;
_asm
{
cmp [MemoarySize],MAX_MEMOARY_SUPPORT
ja OverFlow
jmp OK
OverFlow:
mov [MemoarySize],MAX_MEMOARY_SUPPORT
OK:
}
return Resault;
}
BYTE APIENTRY Fn_ReadByte(UDWORD Address)
{
BYTE Resault=NULL;
_asm
{
push EBX
mov EBX,[Address]
mov EAX,[MemoarySize]
cmp EBX,EAX
jae EXIT
mov EBX,[lpMemoary]
add EBX,[Address]
mov BL ,BYTE ptr[EBX]
mov [Resault],BL
EXIT:
pop EBX
}
return Resault;
}
VOID APIENTRY Fn_WriteByte(UDWORD Address,DWORD Value)
{
_asm
{
push EBX
mov EBX,[Address]
mov EAX,[MemoarySize]
cmp EBX,EAX
jae EXIT
mov EBX,[lpMemoary]
mov EAX,[Value]
add EBX,[Address]
mov BYTE ptr[EBX],AL
EXIT:
pop EBX
}
}
WORD APIENTRY Fn_ReadWord(UDWORD Address)
{
WORD Resault=NULL;
_asm
{
push EBX
mov EBX,[Address]
mov EAX,[MemoarySize]
sub EAX,0x02
cmp EBX,EAX
ja EXIT
mov EBX,[lpMemoary]
add EBX,[Address]
mov BX ,WORD ptr[EBX]
mov [Resault],BX
EXIT:
pop EBX
}
return Resault;
}
VOID APIENTRY Fn_WriteWord(UDWORD Address,DWORD Value)
{
_asm
{
push EBX
mov EBX,[Address]
mov EAX,[MemoarySize]
sub EAX,0x02
cmp EBX,EAX
ja EXIT
mov EBX,[lpMemoary]
mov EAX,[Value]
add EBX,[Address]
mov WORD ptr[EBX],AX
EXIT:
pop EBX
}
}
DWORD APIENTRY Fn_ReadDword(UDWORD Address)
{
DWORD Resault=NULL;
_asm
{
push EBX
mov EBX,[Address]
mov EAX,[MemoarySize]
sub EAX,0x04
cmp EBX,EAX
ja EXIT
mov EBX,[lpMemoary]
add EBX,[Address]
mov EBX ,DWORD ptr[EBX]
mov [Resault],EBX
EXIT:
pop EBX
}
return Resault;
}
VOID APIENTRY Fn_WriteDword(UDWORD Address,DWORD Value)
{
_asm
{
push EBX
mov EBX,[Address]
mov EAX,[MemoarySize]
sub EAX,0x04
cmp EBX,EAX
ja EXIT
mov EBX,[lpMemoary]
mov EAX,[Value]
add EBX,[Address]
mov DWORD ptr[EBX],EAX
EXIT:
pop EBX
}
}
#endif //#ifndef _HAL_H
以上3个文件中,Base.h 是我个人给自己写的类型定义和宏定义文件。在后面设计实现内核时也会用到。从现在开始,我们正式进入内核的设计。
首先,我们要实现的模拟内核是内存分页管理内核,但是由于分页管理时有可能出现页表过长的情况(这在页尺寸较小但需要管理的内存总量很大时尤其明显),这时我们采用的方法是将页表分组,即多级页表的方式。那么我们现在正在设计的模拟内核也需要实现这样的功能吗?我个人给出的答案是否。并不是因为我们管理的最大内存只有32MB所以不需要这样的功能,而是由我们所设计的模拟内核在一个真实的操作系统所处的位置导致的。个人认为,在真实的操作系统中,内存管理内核应该并不是一个单独整体,而是由一个层次结构组成。大致上如下:
系统调用接口层
页表管理层(高级)
页表管理层(低级)
HAL层
其中“页表管理层(高级)”部分实现对二级或者多级页表的管理,而页表管理层(低级)部分则实现对于一级页表中的地址映射,变换操作,同时由于该部分是紧挨HAL层的,因此对物理内存的访问也将通过该层传递到HAL层。可能很多人要问,为什么不把对一级页表的操作独立出来呢?我是这样考虑的,由于无论页表是多级还是一级,最终存放待映射地址的总是一级页表,在访问到一级页表后,紧接着马上就要进行对硬件的操作和访问,因此为了高效,我将从一级页表到传递给HAL操作的实现部分包含在了“页表管理层(低级)”中。现在我们就依照这样的层次结构,来设计这个模拟内核。以下是这个工程的名字和这个模拟内核的代号,在后文中,我可能将频繁使用到这些代号。
工程名: 操作系统中分页管理内核的模拟
工程代号: EmuSolaris
内核代号: Venus
我将采用C++来设计和实现这个模拟内核,并且为了快速和方便,会使用一些asm指令,但是不会太复杂。
作为操作系统中内存管理内核的最底层部分(仅高于HAL层),Venus必须明确知道其将要管理的硬件对象的具体参数,在这里,就是物理内存的尺寸,并且利用这个数据,可以计算出系统中合法物理和逻辑页面号的范围。另外,在整个EmuSolaris中,Venus应该是做为在HAL层被加载后首先被加载的内核。在加载后,整个物理内存的操作和访问都将由Venus去控制。并且在加载完后,分页地址就应该可以开始使用,物理内存也已经被划分为逻辑页和物理页,并且可以开始对物理内存按页为单位进行分配和回收,提供给高层部分的内存访问接口也已经初始化完毕,可以利用指定的页表对内存进行访问了。以上就是我认为应该最先设计并实现的基础功能。相对的,Venus应该有如下的数据结构以用于支持分页变换和地址映射功能。注意,这里所有的数据类型定义符都请参看前文的Base.h。
BOOL IsPage; //是否分页管理 正常初始化时为1,即使用分页管理
UDWORD PageAddressIndex; //虚拟地址中的页号地址
UDWORD InPageOffset; //虚拟地址中的页内偏移地址
UDWORD NowLineAddress; //当前等待变换或者使用的虚拟地址
UDWORD ErrorCode; //错误代码变量
UDWORD MemoarySize; //当前总共内存容量
UDWORD MaxPageIndex; //最大页面号,有初始化时的总计内存容量决定.
为了实现上面所说的基础功能,Venus应该具有如下的操作函数:
VOID APIENTRY Fn_TranslateLineAddress();
VOID APIENTRY Fn_Service_LoadVirtualAddress(UDWORD VirtualAddress);
VOID APIENTRY Fn_Service_Init(UDWORD NewMemoarySize);
Fn_Service_LoadVirtualAddress(UDWORD VirtualAddress)函数实现加载虚拟地址到Venus的操作。在上文中的数据结构中,PageAddressIndex和InPageOffset分别用来保存当前加载的虚拟地址中的页号地址和页内偏移量地址。Fn_Service_LoadVirtualAddress函数就是对这个2个数据对象进行操作。而Fn_TranslateLineAddress();则是执行具体变换动作的函数,在IsPage为真的情况下,这个函数将会按照由Fn_Service_LoadVirtualAddress的参数传递过来的地址进行变换,并将结果分别放在PageAddressIndex和InPageOffset中。而Fn_Service_LoadVirtualAddress中所带的参数则会保存在NowLineAddress中。这个设计可以供以后在扩展系统时使用。到这里,虚拟地址和线性地址转换为分页地址的操作就完成了。
由于分页管理的缘故,EmuSolaris中运行的其他进程都将使用虚拟地址来访问内存,并且必须提供页表以供Venus进行从虚拟地址到物理地址的映射和变换。由此可以得到Venus的运行图:
加载待读取的虚拟地址à利用指定的页表进行地址变换,得到物理地址à将待访问的地址传递给EmuHAL层àEmuHAL层完成硬件操作
按照这个运行图,可以看出,第一部分已经设计完毕,那么我们首先来实现这个部分。代码如下:
首先是Venus启动和初始化函数:
VOID APIENTRY MemoaryKernel::Fn_Service_Init(UDWORD NewMemoarySize)
{
MemoarySize = NewMemoarySize;
InPageOffset = NULL;
PageAddressIndex = NULL;
NowLineAddress = NULL;
ErrorCode = NORMAL;
_asm//这段汇编完成计算出NewMemoarySize对应的最大页号地址
{
push EDX
push ECX
mov EAX,[NewMemoarySize]
mov EDX,EAX
and EAX,0x0000FFFF
shr EDX,0x010
and EDX,0x0000FFFF
mov CX,0x01000
div CX
sub EAX,1
and EAX,0x0000FFFF
mov ECX,this
mov [ECX]this.MaxPageIndex,EAX
pop ECX
pop EDX
}
}
//====================================================================//
//加载虚拟/线性地址,以备进行页面地址变换操作:
VOID APIENTRY MemoaryKernel::Fn_Service_LoadVirtualAddress(UDWORD VirtualAddress)
{
this->NowLineAddress = VirtualAddress;
if (IsPage == TRUE) {Fn_TranslateLineAddress();}
}
//====================================================================//
//执行实际变换动作的变换函数:
VOID APIENTRY MemoaryKernel::Fn_TranslateLineAddress()
{
_asm
{
push EBX
push ECX
mov EBX,this
mov EAX,[EBX]this.NowLineAddress
mov ECX,EAX
and EAX,0x0FFF //清空高24位得到页内偏移地址
mov [EBX]this.InPageOffset,EAX
shr ECX,12 //右移动12位,得到页号地址
and EAX,0x01FFF
cmp ECX,[EBX]this.MaxPageIndex
jbe OK
mov ECX,MAX_PAGE_NUMBER
OK:
mov [EBX]this.PageAddressIndex,ECX
pop ECX
pop EBX
}
}
//====================================================================//
虽然完成了状态图上的第一部分功能,但是Venus目前仍然不具备对内存进行按页分配和回收的功能。那么如何来设计这2个功能,是十分重要的,因为分配和回收是Venus的基础的基础功能。要分配和回收页面,就需要了解整个物理内存页面的使用情况,最简单的办法是采用位映射图(BitMap)的数据结构。BitMap,就是用一个bit位,来表示一个页面的使用状态,只有1和0两种状态,分别对应页面的 “已分配”(USED) 和“空闲”(UNUSE)2种状态。但是,由于用来记录内存使用状态的数据也是保存在内存中的,那么,BitMap的空间效率如何呢?我们来计算一下:
Venus最大支持32MB内存,此时最大会有8192个页面,也就是说,需要8192个bit来记录内存的使用状态,即8192bit/8 = 1024Byte =1KB空间。应该说,BitMap的空间效率还是相当不错的。那么这1KB的连续数据存放在物理内存的什么地方呢?作为操作系统的内核使用的数据,我将它设计为存放在物理内存的最低地址处,即 物理地址0x0-0x03fff。这样的话,Venue中应该新增加一个数据成员以记录BitMap的存放的起始物理地址。我们新增加一个数据成员 _SaveLinerAddress 来记录它:
UDWORD _SaveLinerAddress; //Map位图存放的线性地址
现在我们来设计下BitMap的细节部分,由于这个模拟程序是在Intel 80386以上的机器上编写的,因此为了实现bit操作时方便,我们将BitMap按32bit为一组进行划分。每组从右向左计数。对BitMap的操作集有2个函数:设置函数和查询函数,其具体实现代码将在最后给出。
DWORD APIENTRY Fn_Service_SetBitMap(UDWORD PageIndex,BOOL Flag);
//设置位映射图, 参数为分页地址的页号.
DWORD APIENTRY Fn_Service_GetBitMap(UDWORD PageIndex);
//取得位映射图数值, 参数为分页地址的页号
现在通过BitMap我们已经可以了解到物理内存分页状态下内存的使用状态了,但是仍然不能按页分配和回收内存。那么要如何设计分配和回收内存页呢?我们可以组织一个空闲页链表,每分配一个页,就摘掉一个链表项,每回收一个页,就往链表里加一个项。但是这样有几个缺点,一来链表不稳定,一旦出错就会造成内存丢失或者重复分配进而造成进程间冲突,二来效率相对差些。最后我想到在UNIX中,磁盘块的分配采用的是成组链接法,因为内存空闲页在本质上也是一种块,因此我们可以把这个算法适当的改造一下,用来分配和回收空闲页。我们知道,成组链接分配算法采用一个堆栈来进行分配和回收操作。我们将空闲物理页号按1024个为一组进行遍组,这样最多会有8组(因为Venus最大支持32MB内存),并设计一个指针,在初始化时让它指向第一组的开头,当分配出去一个空闲页面时,令指针指向组中的下一个空闲物理块号,而当回收一个空闲物理页时,则将其页号记入上一个分配出去的页号的位置。简单说,就是当分配空闲物理页时做出栈操作,当回收时做压栈操作。而在每组的末尾,记录着新一组的起始物理地址,当操作指针指向当前组的末尾时,就会跳到新一组的开始继续进行分配。由于内存的分配和回收要求高效率的操作,再加上这是Venus使用的数据,因此对8个组的地址做如下规定:每组和每组之间保持连续,也就是说这8个组必须存放在连续的物理内存空间里。每组之间以4个字节相隔。这4个字节中记录的是下一组的起始物理地址。为了能明确知道当前分配指针是否已经指向了当前组的末尾,在Venus中需要再增加一些数据成员来记录分配的次数。由于Venus最大支持到32MB ,因此采用一个字的长度来记录空闲物理页号。由此得到8组占用的长度:1024×2Byte×8组=16KB=4页。同样做为Venus的数据,我将起存储位置设置为内存低地址区,紧挨BitMap之后,即:0x0000400- 0x000441F。新增加的数据成员如下:
UDWORD StackEsp; //MallocStack操作指针.
UDWORD MallocCounter; //分配次数计数器 用来记录是否需要取下个可用页面组或生成新可用页面组
UDWORD GroupIndex; //当前操作的组号
对应分配和回收操作,分别有2个函数提供支持,具体实现代码在最后给出:
UDWORD APIENTRY Fn_Service_MallocOnePage();
//分配一个页面
UDWORD APIENTRY Fn_Service_FreeOnePage(UDWORD PageIndex);
//回收一个页面,参数为待回收的页面地址号
到目前,Venus具有了虚拟地址变换,按页为单位对内存进行分配/回收操作,记录/查询内存使用状态的功能。
接着,我们来实现第二个功能部分:利用指定的页表进行地址变换,得到物理地址。那么如何利用页表呢,这就要牵扯到页表的数据结构了。那么我们先来设计好页表。在分页系统中,页表的基本功能是将逻辑页地址映射为物理页地址。实现时,通常采用这样的设计:在逻辑页表中的每一个页表项中存放的是物理页号的地址。这样通过读取页表项,可以得到对应逻辑页在物理内存中的物理页号,再和页内偏移量相拼,就得到了要访问的物理地址。如上文所写到的,在分页系统中,进程访问内存使用的都是虚拟地址,也就是说,进程总是认为自己的存储地址是从0x0开始的。进程进行内存访问时,传递给Venus的是一个虚拟的地址,Venus将这个虚拟的地址做分页变换,取得对应这个虚拟地址的分页地址,分页地址中包含了页号和页内偏移量,然后Venus再利用得到的页号,去这个进程的页表中对应的页表项里取得物理页号的地址,最后将取得的页号地址再和页内偏移量相拼,就得到了物理地址。这样就完成了一次由虚拟地址到物理地址的映射。回到刚才的设计上,页表项必须有至少以下2个数据成员来完成地址的映射:
UDWORD PageIndex; //页索引号用于共系统查找(目前未用)
UDWORD PageBlock; //物理页号 保存该页表项映射的真实物理页号地址
而实际上,为了支持虚拟存储技术和共享保护等诸多功能,页表项还需要其他的数据结构支持,比如,为了供换页进程参看,页表项需要提供当前单位时间内自身是否被访问,是否被修改,当前该页是否在内存,单位时间内页面被访问的次数 等等的信息,因此综合了一下,我将页表项设计如下:
UDWORD PageIndex; //页索引号用于共系统查找(目前未用)
UDWORD PageBlock; //物理页号 保存该页表项映射的真实物理页地址
UDWORD AccessCounter; //访问计数器,用来记录该页面被访问的次数
UDWORD PageStatus; //页面状态标志,标志页面是在内存中还是在外存中,共换页进程参考
BOOL IsModify; //页面是否被修改 共换页进程参考
BOOL IsAccess; //页是否被访问,共换页进程参考
UDWORD SwapAddress; //外存地址.页面被存放于外存的地址
这些数据结构将为以后设计换页进程提供有力的支持。Venus作为底层内核,必须能向高层部分提供对页表项内容的查询和修改操作,以及当新生成一个页表项时,应该能按高层的要求,进行页表项初始化操作,初始化主要用来给新页表项分配一个空闲物理页面供其映射。
在页表项的数据成员中,PageBlock和PageIndex只能在初始化时由初始化函数修改,一旦初始化成功完成,以后除非回收页面,否则不能再修改。而其他数据成员,则可以由高层按需要进行修改。基于以上设定,页表项应该具有的操作集有:
UDWORD APIENTRY Fn_Service_Init(UDWORD Init_PageIndex); //页表项对象初始化函数,参数为页索引号.
//该函数将请求内核分配一个空闲物理页面以用来初始化,并将该页面地址记入PageBlock中
UDWORD APIENTRY Fn_Service_Free();//该函数将请求内核释放其所映射的物理页面,并将PageBlock的数值设置为NO_ALLOCATION
//被释放的页面将被放入内核管理的分配堆栈,共其他进程使用
UDWORD APIENTRY Fn_Service_GetPageBlock(); //取得PageBlock的值
UDWORD APIENTRY Fn_Service_GetPageStatu(); //取得PageStatu的值
UDWORD APIENTRY Fn_Service_GetAccessCounter();//取得AccessCounter的值
UDWORD APIENTRY Fn_Service_GetIsModify(); //取得IsModify的值
UDWORD APIENTRY Fn_Service_GetIsAccess(); //取得IsAccess的值
VOID APIENTRY Fn_Service_SetPageStatu(UDWORD NewStatu); //设置PageStatu的值
VOID APIENTRY Fn_Service_IncAccessCounter(); //AccessCounter自增1
VOID APIENTRY Fn_Service_SetIsModidy(BOOL NewFlag); //设置IsModify的值
VOID APIENTRY Fn_Service_SetIsAccess(BOOL NewFlag); //设置IsAccess的值
VOID APIENTRY Fn_Service_SetSwapAddress(UDWORD NewSwapAddress);
//设置SwapAddress的值
当页表项设计好后,就可以实现Venus的第二部分功能,利用页表进行地址变换,映射出要访问的真实物理地址。对内存的访问,无非是读或者写,因此Venus需要提供一个内存访问接口。EmuHAL中提供了对物理内存进行按字节,字,双字读取/写入的接口,因此Venus中也将提供按字节,字,双字读取/写入的接口。到这里有一个问题,就是什么时候进行利用页表和分页地址进行物理地址的映射。可以有2个方案,第一,加载虚拟地址后立刻进行分页地址到物理地址的映射,这时候当然需要页表的支持。第二,加载虚拟地址后并不立刻进行分页地址到物理的映射,而是等到需要执行读/写操作时,才利用指定的页表进行地址映射,随后进行访问操作。我在这里选择了第二个方案。因为我觉得加载虚拟地址后并不一定马上要进行内存操作,但是向Venus请求了读写操作后则必须立刻执行读/写,因此我在这里提供给高层2个地址方面的接口,一个只先加载地址并进行虚拟地址到分页地址的变换,另一个接口就是由上文的6个读写函数中的参数提供的,使用这6个函数时,Venus会一次性完成加载虚拟地址变换为分页地址和利用分页地址及页表完成分页地址到物理地址的转换,并将读写操作传递给EmuHAL层去执行。Venus提供的读写函数接口如下:
BYTE APIENTRY Fn_Service_ReadByte(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress);
//按字节方式(8bit)读取
VOID APIENTRY Fn_Service_WriteByte(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value);
//按字节方式(8bit)写入
WORD APIENTRY Fn_Service_ReadWord(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress);
//按字方式(16bit)读取
VOID APIENTRY Fn_Service_WriteWord(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value);
//按字方式(16bit)写入
DWORD APIENTRY Fn_Service_ReadDword(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress);
//按双字方式(32bit)读取
VOID APIENTRY Fn_Service_WriteDword(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value);
//按双字方式(32bit)写入
到这里,第二和第三部分功能就相继实现了,作为底层来说,现在基本的功能已经实现了,但是还有很多的不足,比如,内核的健壮性问题。上面的各个功能设计都没有考虑到操作数据错误带来的影响,并且对Venus使用的内存部分完全没有进行保护,任何进程都可以轻松访问到系统使用的内存,这是相当危险的。
上面说了这么多,我把具体实现的代码贴在下面,这些代码实现上面说的Venus 的各个基本功能,并且最了相对比较好的安全性检测,并且基本保证了在EmuHAL下系统用内存空间不可访问。
Memoary_Class_Define.h
//================================================================//
#ifndef _MEMOARY_CLASS_DEFINE_H
#define _MEMOARY_CLASS_DEFINE_H
#include "../Include/Memoary_Define.h"
#include "../Include/Memoary_Class_Lv1Pt_Define.h"
class CMemoaryKernel //内存分页管理内核接口类
{
public:
virtual VOID APIENTRY Fn_Service_Init(UDWORD NewMemoarySize)=0;
//内核启动,初始化函数,必须在EmuHAL被初始化后调用,否则会因为没有EmuHAL层支持而出错.
//同时不调用本函数则内核本身也不会初始化,进而导致Halt.
public: //存储器管理内核状态查询操作服务集
virtual UDWORD APIENTRY Fn_Service_GetErrorCode()=0; //取得内核操作错误状态代码
virtual VOID APIENTRY Fn_Service_GetMemoaryStatu()=0;//当前内存使用情况查看接口
public: //单位页面分配/回收服务集
virtual UDWORD APIENTRY Fn_Service_MallocOnePage()=0;
//分配一个页面,返回值为分配的页面地址
virtual UDWORD APIENTRY Fn_Service_FreeOnePage(UDWORD PageIndex)=0;
//回收一个页面,参数为待回收的页号地址.注意,该函数不会回收任何由系统常驻的页面或者非法页面
public: //读写服务集 第一个参数为Lv1页表对象指针 第二个参数为虚拟地址 第3个参数为待写入的数据
virtual BYTE APIENTRY Fn_Service_ReadByte(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress)=0;
//按字节方式(8bit)读取
virtual VOID APIENTRY Fn_Service_WriteByte(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value)=0;
//按字节方式(8bit)写入
virtual WORD APIENTRY Fn_Service_ReadWord(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress)=0;
//按字方式(16bit)读取
virtual VOID APIENTRY Fn_Service_WriteWord(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value)=0;
//按字方式(16bit)写入
virtual DWORD APIENTRY Fn_Service_ReadDword(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress)=0;
//按双字方式(32bit)读取
virtual VOID APIENTRY Fn_Service_WriteDword(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value)=0;
//按双字方式(32bit)写入
};
class MemoaryKernel:public CMemoaryKernel
{
private:
BOOL IsPage; //是否分页管理 正常初始化时为1,即使用分页管理
UDWORD _SaveLinerAddress; //Map位图存放的线性地址
UDWORD PageAddressIndex; //分页地址中的页号地址
UDWORD InPageOffset; //分页地址中的页内偏移地址
UDWORD NowLineAddress; //当前等待变换或者使用的虚拟地址
UDWORD StackEsp; //MallocStack操作指针.
UDWORD MallocCounter; //分配次数计数器 用来记录是否需要取下个可用页面组或生成新可用页面组
UDWORD ErrorCode; //错误代码变量
UDWORD GroupIndex; //当前操作的组号
UDWORD MemoarySize; //当前总共内存容量
UDWORD MaxPageIndex; //最大页面号,有初始化时的总计内存容量决定.
public:
MemoaryKernel();
~MemoaryKernel();
private://内部服务
VOID APIENTRY Fn_TranslateLineAddress(); //虚拟地址分页变换机构
VOID APIENTRY Fn_Service_Init_MallocStack(); //Malloc堆栈初始化函数
VOID APIENTRY Fn_Service_LoadVirtualAddress(UDWORD VirtualAddress);
//加载待读取或者写入的内存地址.如果使用分页模式,则内核会将线性地址做拆分,
//在PageAddressIndex中放入页号地址,在InPageOffset中放入页内偏移量地址
public: //内存使用状况查询/设置/内核初始化服务集
virtual VOID APIENTRY Fn_Service_Init(UDWORD NewMemoarySize);
//内核初始化函数,负责启动内核,设置位映射图和Malloc堆栈
DWORD APIENTRY Fn_Service_SetBitMap(UDWORD PageIndex,BOOL Flag);
//设置位映射图, 参数为分页地址的页号地址.
DWORD APIENTRY Fn_Service_GetBitMap(UDWORD PageIndex);
//取得位映射图数值, 参数为分页地址的页号地址.
public: //单位页面分配/回收服务集
virtual UDWORD APIENTRY Fn_Service_MallocOnePage();
//分配一个页面
virtual UDWORD APIENTRY Fn_Service_FreeOnePage(UDWORD PageIndex);
//回收一个页面
public: //存储器管理内核状态查询操作服务集
virtual UDWORD APIENTRY Fn_Service_GetErrorCode(); //取得内核操作错误状态代码
virtual VOID APIENTRY Fn_Service_GetMemoaryStatu();//当前内存使用情况查看接口
public: //读写服务集 第一个参数为Lv1页表对象指针 第二个参数为虚拟地址 第3个参数为待写入的数据
virtual BYTE APIENTRY Fn_Service_ReadByte(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress);
//按字节方式(8bit)读取
virtual VOID APIENTRY Fn_Service_WriteByte(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value);
//按字节方式(8bit)写入
virtual WORD APIENTRY Fn_Service_ReadWord(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress);
//按字方式(16bit)读取
virtual VOID APIENTRY Fn_Service_WriteWord(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value);
//按字方式(16bit)写入
virtual DWORD APIENTRY Fn_Service_ReadDword(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress);
//按双字方式(32bit)读取
virtual VOID APIENTRY Fn_Service_WriteDword(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value);
//按双字方式(32bit)写入
};
#endif //#ifndef _MEMOARY_CLASS_DEFINE_H
//================================================================//
Memoary_Class_Lv1PT_Define.h
//================================================================//
#ifndef _MEMOARY_CLASS_LV1PT_DEFINE
#define _MEMOARY_CLASS_LV1PT_DEFINE
#include "../Include/Memoary_Define.h"
class Lv1_MemoaryTable
{
private:
UDWORD PageIndex; //页索引号用于共系统查找(目前未用)
UDWORD PageBlock; //物理页号 保存该页表项映射的真实物理页地址
UDWORD AccessCounter; //访问计数器,用来记录该页面被访问的次数
UDWORD PageStatus; //页面状态标志,标志页面是在内存中还是在外存中,共换页进程参考
BOOL IsModify; //页面是否被修改 共换页进程参考
BOOL IsAccess; //页是否被访问,共换页进程参考
UDWORD SwapAddress; //外存地址.页面被存放于外存的地址
public:
Lv1_MemoaryTable();
~Lv1_MemoaryTable();
public: //页表项初始化服务集
UDWORD APIENTRY Fn_Service_Init(UDWORD Init_PageIndex); //页表项对象初始化函数,参数为页索引号.
//该函数将请求内核分配一个空闲物理页面以用来初始化,并将该页面地址记入PageBlock中
UDWORD APIENTRY Fn_Service_Free();//该函数将请求内核释放其所映射的物理页面,并将PageBlock的数值设置为NO_ALLOCATION
//被释放的页面将被放入内核管理的分配堆栈,共其他进程使用
public: //页表项状态读/写服务集
//读
UDWORD APIENTRY Fn_Service_GetPageBlock(); //取得PageBlock的值
UDWORD APIENTRY Fn_Service_GetPageStatu(); //取得PageStatu的值
UDWORD APIENTRY Fn_Service_GetAccessCounter();//取得AccessCounter的值
UDWORD APIENTRY Fn_Service_GetIsModify(); //取得IsModify的值
UDWORD APIENTRY Fn_Service_GetIsAccess(); //取得IsAccess的值
//写
VOID APIENTRY Fn_Service_SetPageStatu(UDWORD NewStatu); //设置PageStatu的值
VOID APIENTRY Fn_Service_IncAccessCounter(); //AccessCounter自增1
VOID APIENTRY Fn_Service_SetIsModidy(BOOL NewFlag); //设置IsModify的值
VOID APIENTRY Fn_Service_SetIsAccess(BOOL NewFlag); //设置IsAccess的值
VOID APIENTRY Fn_Service_SetSwapAddress(UDWORD NewSwapAddress); //设置SwapAddress的值
};
#endif //#ifndef _MEMOARY_CLASS_LV1PT_DEFINE
//====================================================================//
Memoary_Define.h
//====================================================================//
#ifndef _MEMOARY_DEFINE_H
#define _MEMOARY_DEFINE_H
#define MAX_MEMOARY 0x02000000 //支持管理的最大内存 32MB
#define MIN_RUN_MEMOARY 0x00006000 //内核运行需要的最小内存,以Byte计算
#define MAX_PAGE_NUMBER 0x00001FFF //32MB内存按4KB分页的最大页号 0-8191 共8192个
#define PAGE_SIZE 4096 //页尺寸4KB为4096Byte
#define MAX_MEMOARY_STACK 1024 //负责内存分配回收的堆栈的长度
#define NO_ALLOCATION 0x0FFFF
#define _I386 //类型关键字定义 定义为Intel 80386 及以后处理器专用
#define _STDAPI //允许函数调用使用Win32 APIENTRY(__stdcall)方式
#define _IOSTREAM //使用iostream.h作为I/O操作库
#include <Base.h>
#include <EmuHAL.h>
#pragma comment(lib,"EmuHAL.lib")
#define SWAPPER 0x00000000
#define MEMOARY 0x00000001
#define UNINIT 0x00000002
//以下地址基于ExHal层中的内存,可被视为物理内存
#define SYSTEM_USE_START_LINE_ADDRESS 0x0000000
#define SYSTEM_USE_START_PAGE_ADDRESS 0x0000000
#define MEMOARY_MAP_BEGIN_STORE_LINE_ADDRESS 0x0000000
#define MEMOARY_MAP_END_STORE_LINE_ADDRESS 0x00003FF
#define MEMOARY_STACK_BEGIN_STORE_LINE_ADDRESS 0x0000400
#define MEMOARY_STACK_END_STORE_LINE_ADDRESS 0x000441F
#define MEMOARY_STACK_ESP 0x0004420
#define SYSTEM_USE_END_PAGE_ADDRESS 0x0000004
#define SYSTEM_USE_END_LINE_ADDRESS 0x0004423
//错误代码定义:
#define NORMAL 0x00000001
#define GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY 0xF0000000
#define GENERAL_PROTECT_ERROR_PAGE_INDEX_OVERFLOW 0xF0000001
#define MEMOARY_ALLOCATE_ERROR_NO_ENOUGH_MEMOARY 0xF0000002
#define MEMOARY_FREE_ERROR_NO_MEMOARY_NEED_FREE 0xF0000003
#define MEMOARY_KERNEL_HALT_NO_ENOUGH_MEMOARY_FOR_INIT 0xF0000004
#define MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION 0xF0000005
#define MEMOARY_KERNEL_FREE_PAGE_FAILURE_PAGE_UNUSED 0xF0000006
#define MEMOARY_KERNEL_READ_ERROR_ADDRESS_OVERFLOW 0xF0000007
#define MEMOARY_KERNEL_WRITE_ERROR_ADDRESS_OVERFLOW 0xF0000008
#define MEMOARY_KERNEL_INT_READ_PAGE_NOT_IN_MEMOARY 0xF0000009
#define MEMOARY_KERNEL_INT_WRITE_PAGE_NOT_IN_MEMOARY 0xF000000A
#define MALLOC_STACK_END_FLAG 0xFFFFFFFF
#define LPCMEMOAEYKERNEL CMemoaryKernel*
//LPCMEMOAEYKERNEL Fn_iKernel_GetMemoaryManager();
#endif //#ifndef _MEMOARY_DEFINE_H
//====================================================================//
Memoary_Gloable_Define.h
//====================================================================//
#ifndef _MEMOARY_GLOABLE_DEFINE_H
#define _MEMOARY_GLOABLE_DEFINE_H
#include "./Include/Memoary_Define.h"
#include "./Include/Memoary_Class_Define.h"
#include "./Include/Memoary_Class_Lv1PT_Define.h"
//接口对象
MemoaryKernel m_Venus; //存储器管理核心对象
LPCMEMOAEYKERNEL APIENTRY Fn_iKernel_GetMemoaryManager();
#endif
//===================================================================//
MemoaryPageLib.h
//===================================================================//
#ifndef _MEMPAGELIB_H
#define _MEMPAGELIB_H
#define MAX_MEMOARY 0x00800000 //支持管理的最大内存 32MB
#define MAX_PAGE_NUMBER 0x00001FFF //32MB内存按4KB分页的最大页号 0-8191 共8192个
#define PAGE_SIZE 4096 //页尺寸4KB为4096Byte
#define MAX_MEMOARY_STACK 1024 //负责内存分配回收的堆栈的长度
#define _I386 //类型关键字定义 定义为Intel 80386 及以后处理器专用
#define _STDAPI //允许函数调用使用Win32 APIENTRY(__stdcall)方式
#define _IOSTREAM //使用iostream.h作为I/O操作库
#include <Base.h>
#include <EmuHAL.h>
#pragma comment(lib,"EmuHAL.lib")
#define SWAPPER 0x00000000
#define MEMOARY 0x00000001
#define UNINIT 0x00000002
//以下地址基于ExHal层中的内存,可被视为物理内存
#define SYSTEM_USE_START_LINE_ADDRESS 0x0000000
#define SYSTEM_USE_START_PAGE_ADDRESS 0x0000000
#define MEMOARY_MAP_BEGIN_STORE_LINE_ADDRESS 0x0000000
#define MEMOARY_MAP_END_STORE_LINE_ADDRESS 0x00003FF
#define MEMOARY_STACK_BEGIN_STORE_LINE_ADDRESS 0x0000400
#define MEMOARY_STACK_END_STORE_LINE_ADDRESS 0x000441F
#define MEMOARY_STACK_ESP 0x0004420
#define SYSTEM_USE_END_PAGE_ADDRESS 0x0000004
#define SYSTEM_USE_END_LINE_ADDRESS 0x0004423
//页表映射的空闲物理页面的状态标志
#define SWAPPER 0x00000000 //位于交换设备上
#define MEMOARY 0x00000001 //在内存中
#define UNINIT 0x00000002 //尚未初始化的新页表项
//错误代码定义:
#define NORMAL 0x00000001
#define GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY 0xF0000000
#define GENERAL_PROTECT_ERROR_PAGE_INDEX_OVERFLOW 0xF0000001
#define MEMOARY_ALLOCATE_ERROR_NO_ENOUGH_MEMOARY 0xF0000002
#define MEMOARY_FREE_ERROR_NO_MEMOARY_NEED_FREE 0xF0000003
#define MEMOARY_KERNEL_HALT_NO_ENOUGH_MEMOARY_FOR_INIT 0xF0000004
#define MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION 0xF0000005
#define MEMOARY_KERNEL_FREE_PAGE_FAILURE_PAGE_UNUSED 0xF0000006
#define MEMOARY_KERNEL_READ_ERROR_ADDRESS_OVERFLOW 0xF0000007
#define MEMOARY_KERNEL_WRITE_ERROR_ADDRESS_OVERFLOW 0xF0000008
#define MEMOARY_KERNEL_INT_READ_PAGE_NOT_IN_MEMOARY 0xF0000009
#define MEMOARY_KERNEL_INT_WRITE_PAGE_NOT_IN_MEMOARY 0xF000000A
#define MALLOC_STACK_END_FLAG 0xFFFFFFFF
class Lv1_MemoaryTable
{
private:
UDWORD PageIndex; //页索引号用于共系统查找(目前未用)
UDWORD PageBlock; //物理页号 保存该页表项映射的真实物理页地址
UDWORD AccessCounter; //访问计数器,用来记录该页面被访问的次数
UDWORD PageStatus; //页面状态标志,标志页面是在内存中还是在外存中,共换页进程参考
BOOL IsModify; //页面是否被修改 共换页进程参考
BOOL IsAccess; //页是否被访问,共换页进程参考
UDWORD SwapAddress; //外存地址.页面被存放于外存的地址
public:
Lv1_MemoaryTable();
~Lv1_MemoaryTable();
public: //页表项初始化服务集
UDWORD APIENTRY Fn_Service_Init(UDWORD Init_PageIndex); //页表项对象初始化函数,参数为页索引号.
//该函数将请求内核分配一个空闲物理页面以用来初始化,并将该页面地址记入PageBlock中
UDWORD APIENTRY Fn_Service_Free();//该函数将请求内核释放其所映射的物理页面,并将PageBlock的数值设置为NO_ALLOCATION
//被释放的页面将被放入内核管理的分配堆栈,共其他进程使用
public: //页表项状态读/写服务集
//读
UDWORD APIENTRY Fn_Service_GetPageBlock(); //取得PageBlock的值
UDWORD APIENTRY Fn_Service_GetPageStatu(); //取得PageStatu的值
UDWORD APIENTRY Fn_Service_GetAccessCounter();//取得AccessCounter的值
UDWORD APIENTRY Fn_Service_GetIsModify(); //取得IsModify的值
UDWORD APIENTRY Fn_Service_GetIsAccess(); //取得IsAccess的值
//写
VOID APIENTRY Fn_Service_SetPageStatu(UDWORD NewStatu); //设置PageStatu的值
VOID APIENTRY Fn_Service_IncAccessCounter(); //AccessCounter自增1
VOID APIENTRY Fn_Service_SetIsModidy(BOOL NewFlag); //设置IsModify的值
VOID APIENTRY Fn_Service_SetIsAccess(BOOL NewFlag); //设置IsAccess的值
VOID APIENTRY Fn_Service_SetSwapAddress(UDWORD NewSwapAddress); //设置SwapAddress的值
};
class CMemoaryKernel
{
public:
virtual VOID APIENTRY Fn_Service_Init(UDWORD NewMemoarySize)=0;
//内核启动,初始化函数,必须在EmuHAL被初始化后调用,否则会出错
public: //存储器管理内核状态查询操作服务集
virtual UDWORD APIENTRY Fn_Service_GetErrorCode()=0; //取得内核操作错误状态代码
virtual VOID APIENTRY Fn_Service_GetMemoaryStatu()=0;//当前内存使用情况查看接口
public: //单位页面分配/回收服务集
virtual UDWORD APIENTRY Fn_Service_MallocOnePage()=0;
//分配一个页面
virtual UDWORD APIENTRY Fn_Service_FreeOnePage(UDWORD PageIndex)=0;
//回收一个页面
public: //读写服务集 第一个参数为Lv1页表对象指针 第二个参数为虚拟地址 第3个参数为待写入的数据
virtual BYTE APIENTRY Fn_Service_ReadByte(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress)=0;
//按字节方式(8bit)读取
virtual VOID APIENTRY Fn_Service_WriteByte(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value)=0;
//按字节方式(8bit)写入
virtual WORD APIENTRY Fn_Service_ReadWord(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress)=0;
//按字方式(16bit)读取
virtual VOID APIENTRY Fn_Service_WriteWord(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value)=0;
//按字方式(16bit)写入
virtual DWORD APIENTRY Fn_Service_ReadDword(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress)=0;
//按双字方式(32bit)读取
virtual VOID APIENTRY Fn_Service_WriteDword(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value)=0;
//按双字方式(32bit)写入
};
#define LPCMEMOAEYKERNEL CMemoaryKernel*
LPCMEMOAEYKERNEL APIENTRY Fn_iKernel_GetMemoaryManager();
#endif
//====================================================================//
Class_Lv1pt.cpp
//====================================================================//
#ifndef _CLASS_LV1_PT
#define _CLASS_LV1_PT
#include "../Include/Memoary_Class_Lv1Pt_Define.h"
#include "../Include/Memoary_Class_Define.h"
extern MemoaryKernel m_Venus;
Lv1_MemoaryTable::~Lv1_MemoaryTable()
{;}
Lv1_MemoaryTable::Lv1_MemoaryTable()
{
PageBlock = NO_ALLOCATION;
PageStatus = UNINIT;
}
//页表项对象初始化函数,参数为页索引号.
//该函数将请求内核分配一个空闲物理页面以用来初始化,并将该页面地址记入PageBlock中
UDWORD APIENTRY Lv1_MemoaryTable::Fn_Service_Init(UDWORD Init_PageIndex)
{
PageIndex = Init_PageIndex;
if ((PageBlock == NO_ALLOCATION) && (PageStatus == UNINIT))
{
PageBlock = m_Venus.Fn_Service_MallocOnePage();
if (this->PageBlock != MEMOARY_ALLOCATE_ERROR_NO_ENOUGH_MEMOARY)
{
AccessCounter = 0;
IsAccess = FALSE;
IsModify = FALSE;
PageStatus = MEMOARY;
SwapAddress = 0x0;
return SUCCESS;
}
else
{
PageBlock = NO_ALLOCATION;
return FAILURE;
}
}
else
{
return FAILURE;
}
}
//该函数将请求内核释放其所映射的物理页面,并将PageBlock的数值设置为NO_ALLOCATION
//被释放的页面将被放入内核管理的分配堆栈,共其他进程使用
UDWORD APIENTRY Lv1_MemoaryTable::Fn_Service_Free()
{
UDWORD Resault = MEMOARY_KERNEL_FREE_PAGE_FAILURE_PAGE_UNUSED;
_asm
{
mov EAX,this
cmp [EAX]this.PageStatus,UNINIT
je Exit
cmp [EAX]this.PageBlock,NO_ALLOCATION
je Exit
}
Resault = m_Venus.Fn_Service_FreeOnePage(PageBlock);
_asm
{
cmp EAX,SUCCESS
jne Exit
}
PageBlock = NO_ALLOCATION;
PageStatus = UNINIT;
Exit:
return Resault;
}
UDWORD APIENTRY Lv1_MemoaryTable::Fn_Service_GetPageBlock()
{
return PageBlock;
}
UDWORD APIENTRY Lv1_MemoaryTable::Fn_Service_GetPageStatu()
{
return PageStatus;
}
UDWORD APIENTRY Lv1_MemoaryTable::Fn_Service_GetAccessCounter()
{
return AccessCounter;
}
UDWORD APIENTRY Lv1_MemoaryTable::Fn_Service_GetIsModify()
{
return IsModify;
}
UDWORD APIENTRY Lv1_MemoaryTable::Fn_Service_GetIsAccess()
{
return IsAccess;
}
VOID APIENTRY Lv1_MemoaryTable::Fn_Service_SetPageStatu(UDWORD NewStatu)
{
PageStatus = NewStatu;
}
VOID APIENTRY Lv1_MemoaryTable::Fn_Service_IncAccessCounter()
{
AccessCounter++;
}
VOID APIENTRY Lv1_MemoaryTable::Fn_Service_SetIsModidy(BOOL NewFlag)
{
IsModify = NewFlag;
}
VOID APIENTRY Lv1_MemoaryTable::Fn_Service_SetIsAccess(BOOL NewFlag)
{
IsAccess = NewFlag;
}
VOID APIENTRY Lv1_MemoaryTable::Fn_Service_SetSwapAddress(UDWORD NewSwapAddress)
{
SwapAddress = NewSwapAddress;
}
#endif
//===================================================================//
Main_Source.cpp
//===================================================================//
#include "./Include/Memoary_Gloable_Define.h"
//接口函数
LPCMEMOAEYKERNEL APIENTRY Fn_iKernel_GetMemoaryManager()
{
return (LPCMEMOAEYKERNEL)(&m_Venus);
}
//====================================================================//
MemoaryKernel.cpp
//====================================================================//
#include "../Include/Memoary_Class_Define.h"
MemoaryKernel::MemoaryKernel()
{;}
MemoaryKernel::~MemoaryKernel()
{;}
//内核初始化函数 参数为物理内存容量
VOID APIENTRY MemoaryKernel::Fn_Service_Init(UDWORD NewMemoarySize)
{
_asm //物理内存容量是否够内核启动运行
{
mov EAX,[NewMemoarySize]
cmp EAX,MIN_RUN_MEMOARY
jae KernelStart
mov EAX,this
mov [EAX]this.ErrorCode,MEMOARY_KERNEL_HALT_NO_ENOUGH_MEMOARY_FOR_INIT
}
cout<<"No Enough Memoary for MemoaryKernel(Venus) Init itself."<<endl;
cout<<"EmuSolaris SYSTEM HALTTED Now."<<endl;
_asm
{
pop edi
pop esi
mov esp,ebp
pop ebp
ret 8
}
if(NewMemoarySize > MAX_MEMOARY) {NewMemoarySize = MAX_MEMOARY;}
//如果物理内存容量超过了能管理的最大容量,则按能支持的最大容量处理,更大的地址看不见……=.=
KernelStart:
MemoarySize = NewMemoarySize;
UDWORD SystemUsed = SYSTEM_USE_START_PAGE_ADDRESS;
_SaveLinerAddress = MEMOARY_MAP_BEGIN_STORE_LINE_ADDRESS;
InPageOffset = NULL;
PageAddressIndex = NULL;
NowLineAddress = NULL;
MallocCounter = 0;
ErrorCode = NORMAL;
GroupIndex = 1;
_asm//这段汇编完成计算出NewMemoarySize对应的最大页号地址
{
push EAX
push EDX
push ECX
mov EAX,[NewMemoarySize]
mov EDX,EAX
and EAX,0x0000FFFF
shr EDX,0x010
and EDX,0x0000FFFF
mov CX,0x01000
div CX
sub EAX,1
and EAX,0x0000FFFF
mov ECX,this
mov [ECX]this.MaxPageIndex,EAX
pop ECX
pop EDX
pop EAX
}
for(SystemUsed;SystemUsed<=SYSTEM_USE_END_PAGE_ADDRESS;SystemUsed++) //为系统使用的内存做保留操作 :)
{ //这些地址将被用来保存 内存分页使用状态映射图和用于内存分配的Malloc堆栈
Fn_Service_SetBitMap(SystemUsed,USED);
}
StackEsp = MEMOARY_STACK_BEGIN_STORE_LINE_ADDRESS;//Malloc堆栈指针指向栈顶
Fn_Service_Init_MallocStack(); //Malloc堆栈初始化
IsPage = TRUE; //设置系统使用分页模式管理内存 至此内核初始化完毕.
}
//内存描述Map的设置函数.
DWORD APIENTRY MemoaryKernel::Fn_Service_SetBitMap(UDWORD PageIndex,BOOL Flag)
{
UDWORD OffsetAddress;
DWORD Content;
BYTE Shift;
if (PageIndex>MaxPageIndex) {return FAILURE;}
_asm
{
//保存寄存器
push ECX
push EDX
push EBX
mov EAX,[PageIndex] //比较页号是否合法,32MB内存按4KB分页支持的最大页号为0-8192
cmp EAX,MAX_PAGE_NUMBER
ja EXIT
mov CL,0x020 //映射常数因子 由于x86寄存器是32bit长,所以取为32方便对齐运算
div CL //取得页号对应的位映射在Map中的段偏移量,段长以4Byte为单位。
mov BX,AX //取得页号对应的位映射在段内的偏移量,并保存在BX中
and EAX,0x000000FF
mov CL,0x04 //段长常数因子,取4表示4Byte,因为4Byte = 32bit.
mul CL //取得自Map图开始到对应段的具体地址,以Byte为单位.
mov [Shift],BH //将段内偏移量放Shift
and EAX,0x0000FFFF //EAX高16位清空
mov [OffsetAddress],EAX //保存段偏移地址
}
//读取Map中对应的段
Content = Fn_ReadDword(_SaveLinerAddress+OffsetAddress);
_asm
{
mov EAX,[Content] //取得map图中的对应段入EAX
xor EDX,EDX
mov DL,[Flag]
cmp DL,0
mov EDX,0x01
mov CL,[Shift]
je SetUnuse
//SetUse: //设置对应位为1的操作
shl EDX,CL //移动shift位
or EAX,EDX //设置对应位
mov [Content],EAX
jmp Exit
SetUnuse: //设置对应位为0的操作
shl EDX,CL
not EDX
and EAX,EDX
mov [Content],EAX
EXit: //恢复寄存器,退出。
pop EBX
pop EDX
pop ECX
}
Fn_WriteDword(_SaveLinerAddress+OffsetAddress,Content);//将对应位写回Map
return SUCCESS;
}
//读取内存使用状态Map图的函数
DWORD APIENTRY MemoaryKernel::Fn_Service_GetBitMap(UDWORD PageIndex)
{
UDWORD OffsetAddress;
DWORD Content=GENERAL_PROTECT_ERROR_PAGE_INDEX_OVERFLOW; //如果出错,
//则这里放的就是出错代码
BYTE Shift;
if (PageIndex > MaxPageIndex) {return FAILURE;}
_asm
{
//保存寄存器
push ECX
push EDX
push EBX
mov EAX,[PageIndex] //比较页号是否合法,32MB内存按4KB分页支持的最大页号为0-8192
cmp EAX,MAX_PAGE_NUMBER
ja EXIT
mov CL,0x020 //映射常数因子 由于x86寄存器是32bit长,所以取为32方便对齐运算
div CL //取得页号对应的位映射在Map中的段偏移量,段长以4Byte为单位。
mov BX,AX //取得页号对应的位映射在段内的偏移量,并保存在BX中
and EAX,0x000000FF
mov CL,0x04 //段长常数因子,取4表示4Byte,因为4Byte = 32bit.
mul CL //取得自Map图开始到对应段的具体地址,以Byte为单位.
mov [Shift],BH //将段内偏移量放Shift
and EAX,0x0000FFFF //EAX高16位清空
mov [OffsetAddress],EAX //将总线性偏移量放对应变量
}
Content = Fn_ReadDword(_SaveLinerAddress+OffsetAddress);
_asm
{
mov EAX,[Content] //取得map图中的对应段入EAX
mov CL,[Shift]
shr EAX,CL //将EAX中对应的位移动到最低位
and EAX,0x00000001 //其他位清0
mov [Content],EAX //保存在Content中
EXit: //恢复寄存器,退出。
pop EBX
pop EDX
pop ECX
}
return Content;
}
//加载虚拟/线性地址到分页变换机构,以备进行页面地址变换操作
VOID APIENTRY MemoaryKernel::Fn_Service_LoadVirtualAddress(UDWORD VirtualAddress)
{
this->NowLineAddress = VirtualAddress;
if (IsPage == TRUE) {Fn_TranslateLineAddress();}
}
VOID APIENTRY MemoaryKernel::Fn_TranslateLineAddress()
{
_asm
{
push EBX
push ECX
mov EBX,this
mov EAX,[EBX]this.NowLineAddress
mov ECX,EAX
and EAX,0x0FFF //清空高24位得到页内偏移地址
mov [EBX]this.InPageOffset,EAX
shr ECX,12 //右移动12位,得到页号地址
and EAX,0x01FFF
cmp ECX,[EBX]this.MaxPageIndex
jbe OK
mov ECX,MAX_PAGE_NUMBER
OK:
mov [EBX]this.PageAddressIndex,ECX
pop ECX
pop EBX
}
}
VOID APIENTRY MemoaryKernel::Fn_Service_Init_MallocStack()
{
UDWORD LoopCounter=0;
UDWORD PageAddressCounter = 0x0;
UDWORD OpAddress = StackEsp; //设置分配堆栈的开始存储地址
DWORD IsUsed;
for(;PageAddressCounter<=MaxPageIndex;PageAddressCounter++) //这个for循环构造一个组长为1024,
{ //共N组的成组链表用于分配和回收内存页
IsUsed = Fn_Service_GetBitMap(PageAddressCounter);
if(IsUsed == FALSE && LoopCounter != 0x0400) //0x0400是1024 LoopCounter负责记录是否分配了1024个,是的话则设置新组
{
Fn_WriteWord(OpAddress,PageAddressCounter); //ExHAL函数,以字方式写写模拟物理内存
LoopCounter++;
OpAddress=OpAddress+2; //每个堆栈项占2个字节,因此这里+2
}
else
{
if(LoopCounter == 0x0400) //等于1024 表示一个组已经分配完了,该设置新组了
{
Fn_WriteDword(OpAddress,OpAddress+4); //ExHAL函数,以双字(32bit)方式写模拟物理内存
//系统中存放的组是连续的,因此新组的开始地址就是当前组完毕后的地址
PageAddressCounter--; //页面地址减1,否则会在所有loopCounter=0x0400时丢失1Byte内存... T_T
LoopCounter=0;
OpAddress=OpAddress+4;//因为堆栈的最后一项是指向新组的开始地址,因此是双字32bit 所以+4
}
}
}
Fn_WriteDword(OpAddress,MALLOC_STACK_END_FLAG); //for循环完毕,写入堆栈结束标志
}
UDWORD APIENTRY MemoaryKernel::Fn_Service_MallocOnePage()
{
UDWORD Resault_Address;
Resault_Address = (UDWORD)Fn_ReadWord(StackEsp);
if ( Resault_Address == MALLOC_STACK_END_FLAG) //如果堆栈指针已经指向了堆栈结尾标志,说明没有可用物理内存页共分配了
{
ErrorCode=MEMOARY_ALLOCATE_ERROR_NO_ENOUGH_MEMOARY;
return MEMOARY_ALLOCATE_ERROR_NO_ENOUGH_MEMOARY;
}
if (this->MallocCounter != 0x0400)
{
StackEsp=StackEsp+2;
Fn_Service_SetBitMap(Resault_Address,USED);
MallocCounter++; //分配次数计数器 每分配一次加1,1024次则读入一个新堆栈
}
else
{ //以下代码重新取得新一组空闲页面组的地址
StackEsp = Fn_ReadDword(StackEsp); //将新组的开始地址读入堆栈操作指针
if (StackEsp == MALLOC_STACK_END_FLAG) ////如果堆栈指针已经指向了堆栈结尾标志,
{ //说明没有可用物理内存页共分配了
ErrorCode=MEMOARY_ALLOCATE_ERROR_NO_ENOUGH_MEMOARY;
return MEMOARY_ALLOCATE_ERROR_NO_ENOUGH_MEMOARY;
}
Resault_Address = (UDWORD)(Fn_ReadWord(StackEsp)); //读出新的空闲物理页面号
Fn_Service_SetBitMap(Resault_Address,USED); //设置该页的分配状态位
StackEsp = StackEsp+2; //已经分配了一个页面 因此地址+2
MallocCounter = 1; //已经分配了一个页面,因此为1而不是0
GroupIndex++; //读取了新组,因此+1
}
return Resault_Address;
}
UDWORD APIENTRY MemoaryKernel::Fn_Service_FreeOnePage(UDWORD PageIndex) //回收一个页面
{
if (PageIndex <= SYSTEM_USE_END_PAGE_ADDRESS) //检测即将释放的地址的合法性
{
ErrorCode = GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY;
return GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY;
}
if (PageIndex > MaxPageIndex) //检测地址是否overflow
{
ErrorCode = GENERAL_PROTECT_ERROR_PAGE_INDEX_OVERFLOW;
return GENERAL_PROTECT_ERROR_PAGE_INDEX_OVERFLOW;
}
if (StackEsp==MEMOARY_STACK_BEGIN_STORE_LINE_ADDRESS) //简单的操作合法性检测
{
ErrorCode = MEMOARY_FREE_ERROR_NO_MEMOARY_NEED_FREE;
return MEMOARY_FREE_ERROR_NO_MEMOARY_NEED_FREE;
}
Fn_Service_GetBitMap(PageIndex); //进一步在内存使用状态图中查看该页的当前状态
_asm
{
cmp EAX,USED //如果该页已经是空闲页,则不再对Malloc堆栈进行操作
jne ErrorFree //以避免发生不同步的问题
}
if (MallocCounter != 0) //!=0说明当前的组还未用完
{ //先移动指针,再读出空闲物理页面号
StackEsp = StackEsp-2;
Fn_WriteWord(StackEsp,PageIndex);
Fn_Service_SetBitMap(PageIndex,UNUSE);
MallocCounter--;
}
else
{
StackEsp = StackEsp-6;
Fn_WriteWord(StackEsp,PageIndex);
Fn_Service_SetBitMap(PageIndex,UNUSE);
MallocCounter = 1023; //=1023是因为已经分配了一个页面 因此不是1024!
GroupIndex--;
}
return SUCCESS;
ErrorFree:
ErrorCode = MEMOARY_KERNEL_FREE_PAGE_FAILURE_PAGE_UNUSED;
return MEMOARY_KERNEL_FREE_PAGE_FAILURE_PAGE_UNUSED;
}
UDWORD APIENTRY MemoaryKernel::Fn_Service_GetErrorCode()
{
return ErrorCode;
}
VOID APIENTRY MemoaryKernel::Fn_Service_GetMemoaryStatu()
{
U32 OpAddress=0x0;
U32 LineAddress=0x0;
U32 DisplayOpAddress=OpAddress+0x0F;
CHAR Pause=NULL;
cout<<"This is Memoary Use Statu Viewer. :)"<<endl;
cout<<"Command list:"<<endl;
cout<<"A: Display All Memoary Address Use Statu."<<endl;
cout<<"p: Display Memoary Address Use Statu on Group Mode."<<endl;
cout<<"q: Exit."<<endl;
cout.setf(ios::hex);
do{
if (Pause != 'a' && Pause !='A')
{
cout<<"Input Command Here: "<<flush;
cin>>Pause;
}
if(Pause == 'A'||
Pause == 'a'||
Pause == 'p'||
Pause == 'P')
{
if(IsPage == TRUE)
{
cout<<"SYSTEM working in PageMode."<<endl;
for(OpAddress;OpAddress<=SYSTEM_USE_END_PAGE_ADDRESS;OpAddress++) //因为以下部分是循环为了加速用了asm 其实就是算个地址
{
_asm mov EAX,[OpAddress]
_asm shl EAX,0x0C
_asm mov [LineAddress],EAX
cout<<"PageIndexAddress: 0x0"<<OpAddress<<" (LineAddress: 0x0"<<LineAddress<<"-"<<flush;
_asm add DWORD ptr[LineAddress],0x0FFF
cout<<"0x0"<<LineAddress<<")"<<" is Used By SYSTEM."<<endl;
}
for(OpAddress;OpAddress<=(DisplayOpAddress);OpAddress++)
{
_asm
{
push ECX
mov EAX,[OpAddress]
mov ECX,this
mov ECX,[ECX]this.MaxPageIndex
cmp EAX,ECX
ja DisplayFinish //检测到最大地址已经显示完毕则跳出显示循环
pop ECX
}
if (Fn_Service_GetBitMap(OpAddress) == USED)
{
_asm mov EAX,[OpAddress]
_asm shl EAX,0x0C
_asm mov [LineAddress],EAX
cout<<"PageIndexAddress: 0x0"<<OpAddress<<" (LineAddress: 0x0"<<LineAddress<<"-"<<flush;
_asm add DWORD ptr[LineAddress],0x0FFF
cout<<"0x0"<<LineAddress<<")"<<" is Used By User Programme"<<endl;
}
else
{
_asm mov EAX,[OpAddress]
_asm shl EAX,0x0C
_asm mov [LineAddress],EAX
cout<<"PageIndexAddress: 0x0"<<OpAddress<<" (LineAddress: 0x0"<<LineAddress<<"-"<<flush;
_asm add DWORD ptr[LineAddress],0x0FFF
cout<<"0x0"<<LineAddress<<")"<<" is Free."<<endl;
}
}
DisplayFinish:
if (OpAddress < MaxPageIndex)
{
DisplayOpAddress = DisplayOpAddress+0x10;
}
else
{
Pause='q';
}
}
else
{
cout<<"Now EmuSoloris SYSTEM Haltted."<<endl;
Pause = 'q';
}
}
}while(Pause != 'q' && Pause != 'Q');
cout.setf(ios::dec);
}
BYTE APIENTRY MemoaryKernel::Fn_Service_ReadByte(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress)
{
UDWORD CurrentPageBlock;
BYTE Resault=0;
Fn_Service_LoadVirtualAddress(NewVirtualAddress);//加载虚拟地址到内核
lpPageTableAddress = lpPageTableAddress+PageAddressIndex; //将地址调整到对应的页表项上
CurrentPageBlock = lpPageTableAddress->Fn_Service_GetPageBlock(); //取得对应页表项映射的物理空闲页号
_asm
{
cmp [CurrentPageBlock],SYSTEM_USE_END_PAGE_ADDRESS //对系统地址做保护
jbe AccessDeny
}
lpPageTableAddress->Fn_Service_GetPageStatu();
_asm
{ //这一段代码对页表项映射的合法性做进一步检测
cmp EAX,MEMOARY //标志位是MEMOAY则合法
jne AddressError
cmp EAX,SWAPPER
je INT_PageSwap
mov EAX,this
mov EAX,[EAX]this.MaxPageIndex
cmp [CurrentPageBlock],EAX //映射的地址没有overflow则完全合法
ja AddressError
//AddressOK:
push ECX
mov EAX,[CurrentPageBlock]
shl EAX,0x0C
mov ECX,this
mov ECX,[ECX]this.InPageOffset
and ECX,0x0FFF
add EAX,ECX //EAX中放的就是转换后的线性地址
push EAX
call Fn_ReadByte //读取一个字节
mov BYTE ptr[Resault],AL
pop ECX
}
lpPageTableAddress->Fn_Service_SetIsAccess(TRUE); //页是否被访问属性设置为真
lpPageTableAddress->Fn_Service_IncAccessCounter(); //页访问计数器增加1
return Resault;
INT_PageSwap:
ErrorCode = MEMOARY_KERNEL_INT_READ_PAGE_NOT_IN_MEMOARY;
_asm mov EAX,MEMOARY_KERNEL_INT_READ_PAGE_NOT_IN_MEMOARY
_asm jmp Exit
AccessDeny: //企图访问系统使用的空间的出错处理
ErrorCode = GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY;
_asm mov EAX,GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY
_asm jmp Exit
AddressError:
ErrorCode = MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION;
_asm mov EAX,MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION
Exit: //出错处理出口
_asm
{
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 0Ch
}
}
//实现思路同MemoaryKernel::Fn_Service_ReadByte函数
VOID APIENTRY MemoaryKernel::Fn_Service_WriteByte(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value)
{
UDWORD CurrentPageBlock;
Fn_Service_LoadVirtualAddress(NewVirtualAddress);
lpPageTableAddress = lpPageTableAddress+PageAddressIndex;
CurrentPageBlock = lpPageTableAddress->Fn_Service_GetPageBlock();
_asm
{
cmp [CurrentPageBlock],SYSTEM_USE_END_PAGE_ADDRESS
jbe AccessDeny
}
lpPageTableAddress->Fn_Service_GetPageStatu();
_asm
{
cmp EAX,MEMOARY
jne AddressError
cmp EAX,SWAPPER
je INT_PageSwap
mov EAX,this
mov EAX,[EAX]this.MaxPageIndex
cmp [CurrentPageBlock],EAX
ja AddressError
push ECX
mov EAX,[CurrentPageBlock]
shl EAX,0x0C
mov ECX,this
mov ECX,[ECX]this.InPageOffset
and ECX,0x0FFF
add ECX,EAX
mov EAX,[Value]
and EAX,0x0FF
push EAX
push ECX
call Fn_WriteByte
pop ECX
}
lpPageTableAddress->Fn_Service_SetIsAccess(TRUE); //页是否被访问属性设置为真
lpPageTableAddress->Fn_Service_IncAccessCounter(); //页访问计数器增加1
lpPageTableAddress->Fn_Service_SetIsModidy(TRUE); //页表对应的物理页是否被修改标志为真
_asm
{
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 10h
}
INT_PageSwap:
ErrorCode = MEMOARY_KERNEL_INT_WRITE_PAGE_NOT_IN_MEMOARY;
_asm mov EAX,MEMOARY_KERNEL_INT_WRITE_PAGE_NOT_IN_MEMOARY
_asm jmp Exit
AccessDeny:
ErrorCode = GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY;
_asm mov EAX,GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY
_asm jmp Exit
AddressError:
ErrorCode = MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION;
_asm mov EAX,MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION
Exit:
_asm
{
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 10h
}
}
//实现思路同MemoaryKernel::Fn_Service_ReadByte函数
WORD APIENTRY MemoaryKernel::Fn_Service_ReadWord(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress)
{
UDWORD CurrentPageBlock;
WORD Resault;
Fn_Service_LoadVirtualAddress(NewVirtualAddress);
lpPageTableAddress = lpPageTableAddress+PageAddressIndex;
CurrentPageBlock = lpPageTableAddress->Fn_Service_GetPageBlock();
_asm
{
cmp [CurrentPageBlock],SYSTEM_USE_END_PAGE_ADDRESS
jbe AccessDeny
}
lpPageTableAddress->Fn_Service_GetPageStatu();
_asm
{
cmp EAX,MEMOARY
jne AddressError
cmp EAX,SWAPPER
je INT_PageSwap
mov EAX,this
mov EAX,[EAX]this.MaxPageIndex
cmp [CurrentPageBlock],EAX
ja AddressError
push ECX
mov EAX,[CurrentPageBlock]
shl EAX,0x0C
mov ECX,this
mov ECX,[ECX]this.InPageOffset
and ECX,0x0FFF
add EAX,ECX
push EAX
call Fn_ReadWord
mov WORD ptr[Resault],AX
pop ECX
}
lpPageTableAddress->Fn_Service_SetIsAccess(TRUE); //页是否被访问属性设置为真
lpPageTableAddress->Fn_Service_IncAccessCounter(); //页访问计数器增加1
return Resault;
INT_PageSwap:
ErrorCode = MEMOARY_KERNEL_INT_READ_PAGE_NOT_IN_MEMOARY;
_asm mov EAX,MEMOARY_KERNEL_INT_READ_PAGE_NOT_IN_MEMOARY
_asm jmp Exit
AccessDeny:
ErrorCode = GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY;
_asm mov EAX,GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY
_asm jmp Exit
AddressError:
ErrorCode = MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION;
_asm mov EAX,MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION
Exit:
_asm
{
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 0Ch
}
}
//实现思路同MemoaryKernel::Fn_Service_ReadByte函数
VOID APIENTRY MemoaryKernel::Fn_Service_WriteWord(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value)
{
UDWORD CurrentPageBlock;
Fn_Service_LoadVirtualAddress(NewVirtualAddress);
lpPageTableAddress = lpPageTableAddress+PageAddressIndex;
CurrentPageBlock = lpPageTableAddress->Fn_Service_GetPageBlock();
_asm
{
cmp [CurrentPageBlock],SYSTEM_USE_END_PAGE_ADDRESS
jbe AccessDeny
}
lpPageTableAddress->Fn_Service_GetPageStatu();
_asm
{
cmp EAX,MEMOARY
jne AddressError
cmp EAX,SWAPPER
je INT_PageSwap
mov EAX,this
mov EAX,[EAX]this.MaxPageIndex
cmp [CurrentPageBlock],EAX
ja AddressError
push ECX
mov EAX,[CurrentPageBlock]
shl EAX,0x0C
mov ECX,this
mov ECX,[ECX]this.InPageOffset
and ECX,0x0FFF
add ECX,EAX
mov EAX,[Value]
and EAX,0x0FFFF
push EAX
push ECX
call Fn_WriteWord
}
lpPageTableAddress->Fn_Service_SetIsAccess(TRUE); //页是否被访问属性设置为真
lpPageTableAddress->Fn_Service_IncAccessCounter(); //页访问计数器增加1
lpPageTableAddress->Fn_Service_SetIsModidy(TRUE); //设置是否修改标志为真
_asm
{
pop ECX
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 10h
}
INT_PageSwap:
ErrorCode = MEMOARY_KERNEL_INT_WRITE_PAGE_NOT_IN_MEMOARY;
_asm mov EAX,MEMOARY_KERNEL_INT_WRITE_PAGE_NOT_IN_MEMOARY
_asm jmp Exit
AccessDeny:
ErrorCode = GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY;
_asm mov EAX,GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY
_asm jmp Exit
AddressError:
ErrorCode = MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION;
_asm mov EAX,MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION
Exit:
_asm
{
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 10h
}
}
//实现思路同MemoaryKernel::Fn_Service_ReadByte函数
DWORD APIENTRY MemoaryKernel::Fn_Service_ReadDword(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress)
{
UDWORD CurrentPageBlock;
DWORD Resault;
Fn_Service_LoadVirtualAddress(NewVirtualAddress);
lpPageTableAddress = lpPageTableAddress+PageAddressIndex;
CurrentPageBlock = lpPageTableAddress->Fn_Service_GetPageBlock();
_asm
{
cmp [CurrentPageBlock],SYSTEM_USE_END_PAGE_ADDRESS
jbe AccessDeny
}
lpPageTableAddress->Fn_Service_GetPageStatu();
_asm
{
cmp EAX,MEMOARY
jne AddressError
cmp EAX,SWAPPER
je INT_PageSwap
mov EAX,this
mov EAX,[EAX]this.MaxPageIndex
cmp [CurrentPageBlock],EAX
ja AddressError
push ECX
mov EAX,[CurrentPageBlock]
shl EAX,0x0C
mov ECX,this
mov ECX,[ECX]this.InPageOffset
and ECX,0x0FFF
add EAX,ECX
push EAX
call Fn_ReadDword
mov DWORD ptr[Resault],EAX
pop ECX
}
lpPageTableAddress->Fn_Service_SetIsAccess(TRUE); //页是否被访问属性设置为真
lpPageTableAddress->Fn_Service_IncAccessCounter(); //页访问计数器增加1
return Resault;
INT_PageSwap:
ErrorCode = MEMOARY_KERNEL_INT_READ_PAGE_NOT_IN_MEMOARY;
_asm mov EAX,MEMOARY_KERNEL_INT_READ_PAGE_NOT_IN_MEMOARY
_asm jmp Exit
AccessDeny:
ErrorCode = GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY;
_asm mov EAX,GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY
_asm jmp Exit
AddressError:
ErrorCode = MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION;
_asm mov EAX,MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION
Exit:
_asm
{
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 0Ch
}
}
//实现思路同MemoaryKernel::Fn_Service_ReadByte函数
VOID APIENTRY MemoaryKernel::Fn_Service_WriteDword(Lv1_MemoaryTable* lpPageTableAddress,
UDWORD NewVirtualAddress,DWORD Value)
{
UDWORD CurrentPageBlock;
Fn_Service_LoadVirtualAddress(NewVirtualAddress);
lpPageTableAddress = lpPageTableAddress+PageAddressIndex;
CurrentPageBlock = lpPageTableAddress->Fn_Service_GetPageBlock();
_asm
{
cmp [CurrentPageBlock],SYSTEM_USE_END_PAGE_ADDRESS
jbe AccessDeny
}
lpPageTableAddress->Fn_Service_GetPageStatu();
_asm
{
cmp EAX,MEMOARY
jne AddressError
cmp EAX,SWAPPER
je INT_PageSwap
mov EAX,this
mov EAX,[EAX]this.MaxPageIndex
cmp [CurrentPageBlock],EAX
ja AddressError
push ECX
mov EAX,[CurrentPageBlock]
shl EAX,0x0C
mov ECX,this
mov ECX,[ECX]this.InPageOffset
and ECX,0x0FFF
add ECX,EAX
mov EAX,[Value]
push EAX
push ECX
call Fn_WriteDword
}
lpPageTableAddress->Fn_Service_SetIsAccess(TRUE); //页是否被访问属性设置为真
lpPageTableAddress->Fn_Service_IncAccessCounter(); //页访问计数器增加1
lpPageTableAddress->Fn_Service_SetIsModidy(TRUE); //设置是否修改标志为真
_asm
{
pop ECX
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 10h
}
INT_PageSwap:
ErrorCode = MEMOARY_KERNEL_INT_WRITE_PAGE_NOT_IN_MEMOARY;
_asm mov EAX,MEMOARY_KERNEL_INT_WRITE_PAGE_NOT_IN_MEMOARY
_asm jmp Exit
AccessDeny:
ErrorCode = GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY;
_asm mov EAX,GENERAL_PROTECT_ERROR_ACCESS_SYSTEM_MEMOARY
_asm jmp Exit
AddressError:
ErrorCode = MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION;
_asm mov EAX,MEMOARY_KERNEL_READ_FAILURE_PAGE_NOT_ALLOCATION
Exit:
_asm
{
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 10h
}
}
上面的代码如果编译不正常,欢迎大家连续我。 :)