from:http://blog.sina.com.cn/s/blog_61d65e360100glqy.html
驱动程序9--实模式,保护模式,虚拟8086模式
(2010-01-07 00:23:04)1,先来稍微说下历史,好多好多年前一开始Intel公司还不是那么的强大,能力有限,导致开发了一个初级阶段的处理器:8086处理器。这个处理器是16位的,寄存器是16位,物理总线是20位,那么只能寻址2^20的地址空间。之后由于公司不断的壮大,就开发出来一个比较重要的处理器:80386.为什么说他重要呢?因为它已经是个32位的处理器,并且内部机制已经很完善,比如支持任务切换,内存分页管理,优先级。以致到现在的奔腾4,酷睿双核几乎都和80386一样,唯一的区别无非就是频率高点缓存高点(因为我们现在研究的是编程,并没有研究处理器如何去造,因此我们忽略处理器架构的什么的东西)
2,从上面的描述可以得到我们要研究的对象:8086和80386。80386处理器出来之前,大家都用的是DOS,DOS操作系统的特点是单任务,无安全保护,无分页,1M寻址,用户可以随意使用任何中断,使用任何特权指令,直接使用物理地址等等。而80386出来之后就开始支持多任务,分页管理,优先级,环0,环3,4G的寻址能力等等。以上说明一个道理,现在所谓的安全措施,内核模式还是用户模式,环0和环3的划分等等概念都是和CPU有关的。稍微举例:CPU有个CR0寄存器,大小32位,其中第16位就来控制内存的保护机制,如果第16位为“0”,那么就关闭这个保护机制,如果为“1”就打开这个保护机制。那也就是说,如果有个内存地址是写保护的,如果我必须要往里面写数据,我们只要关闭内存保护机制就OK了,很简单!不懂的人还以为很高深或者美其名曰:“某某黑客绕过了内存保护机制”,哇,听起来就好像牛逼举动,其实就几行代码。搞技术的人都信奉一句话:“技术在于你知道还是不知道,你现在知道了,OK,简单,之前你不知道,那么就感觉它很深奥”
3,还在8086时代的时候,是没有实模式这个说法的,虽然大家现在都知道8086模式就是实模式!直到80386出来之后才出现实模式这个定义。大伙仔细比较下8086和80386之间的区别再回顾下上面举的例子,你或许应该有所感悟!既然内存保护机制可以通过某个寄存器的某个位来屏蔽掉,那么内存的分页功能等等能不能关闭呢?能!!继续回到主题上来,其实研究来研究去,我们只关心这2个模式之间的特征,特征的不同带来名字的不同!顺便说下80386处理器可以来回切换这2个模式的
4,虚拟8086模式,当时80386出来的时候,DOS程序已经主宰了这个世界。从上面的介绍可知保护模式和实模式的差异十分巨大,势必导致一旦80386带来的保护模式下无法运行DOS程序的局面。这是微软和Intel公司不愿意看到的。从此虚拟8086模式诞生。你可以在windows操作系统中运行多个DOS程序并且同时运行多个真正的32位程序,比如说此时此刻你运行了3个DOS程序和5个32位程序,这8个程序支持任务切换和内存分页,其中3个DOS程序的寻址还是1M。由于分页的存在,这3个DOS程序所对应的物理内存肯定是不同的。
5,以前MSDOS系统是不分内核还是用户模式,也不会分特权指令还是非特权指令。现在问题来了,如果包含特权指令的DOS运行在windows下肯定不行。windows是这样处理的,一旦运行的时候执行特权指令就会引起一个保护异常,这个时候系统就会分析引起这个异常的指令,如果是中断指令,那么就从虚拟86任务的中断向量表中取出中断处理程序的入口地址,之后跳转;如果是像cli之类的能够危及操作系统的指令则简单的忽略。这样DOS程序就可以运行起来。
接下来介绍内存。 这节你只要了解,如果对分页模式不理解,那么下节开始介绍,笔者会把知识点联系起来!这点读者请放心
***************************************************
{
} MADDRESS_SPACE, *PMADDRESS_SPACE;
红色标注的PMEMORY_AREA也是个结构
{
}MEMORY_AREA,*PMEMORY_AREA
先看MEMORY_AREA结构的土黄色部分,你会意识到这个和二叉树有关,并且还会感觉到这个结构很大可能作为一个节点而存在。红色部分再明确不过了,一个开始,一个结束,指明了一个区间。就如上面描述,一个进程为它的EXE文件保留了一个区间用来映射,这个MEMORY_AREA结构正好可以记录这样的区间。同理,进程还会为其需要的DLL文件保留区间,那么系统会生成多个MEMORY_AREA结构来分别记录每个区间的起始地址,结束地址和其他一些信息。
************************以上就是理论*****************下面是函数的介绍**********
函数的代码我就不写了,太多了,写要写半天,但是分析是少不了的。我会写明第几页的什么函数。另外,用土黄色标志的函数说明它只是过渡函数。
就目前而言我们先看看第49页 MmLocateMemoryAreaByAddr
locate:查找,那么这个函数的意思是:通过地址查找MemoryArea结构。这个函数很简单,而是基本的二叉树查找方式,最后返回指向包含Address的区间所对应的MEMORYAREA结构。
第50页 MmFindGap(PMADDRESS_SPACE AddressSpace,ULONG_PTR length,ULONG_PTR Granularity,BOOLEAN TopDown)
这个函数的作用是从已经被瓜分的地址空间里找到一个“空隙”,这个空隙的至少比length大,Granularity是粒度的意思,最后一个参数表明是从高地址向低地址找还是从低向高找呢。此函数回看情况调用下面2个函数
MmFindGapBottomUp(PMADDRESS_SPACE AddressSpace,ULONG_PTR length,ULONG_PTR Granularity)
MmFindGapTopDown(PMADDRESS_SPACE AddressSpace,ULONG_PTR length,ULONG_PTR Granularity)
下一节介绍MmFindGapBottomUp()
**************************************************************************************************
MmFindGapBottomUp(PMADDRESS_SPACE AddressSpace,ULONG_PTR length,ULONG_PTR Granularity)
MmFindGapTopDown(PMADDRESS_SPACE AddressSpace,ULONG_PTR length,ULONG_PTR Granularity)
这两个函数原理是一样,因此拿MmFindGapBottomUp开刀~~~
先介绍几个常量:MmSystemRangeStart
几个宏:MM_ROUND_UP
其中CONTAINING_RECORD最为重要,在驱动编程中想不遇到都难。分别解释
MmSystemRangeStart:此常量的值就是0x80000000,也就是说内核空间的起始地址
MAXULONG_PTR :说实在,我还真不确定它是什么,但是从代码上分析来看,八成为0xffffffff
MM_VIRTMEM_GRANULARITY:此常量表示当前系统规定的内存粒度。默认是64KB。粒度的讲解一会就说
PAGE_SIZE:页的大小。默认是4KB
MM_ROUND_UP:此为宏,有2个参数,第一个参数是个地址值,第二个参数是粒度大小。此宏的作用是根据地址值以粒度为标准,向高地址方向对齐。靠!!!有点云里雾里了。别急,一会详细讲解
MM_ROUND_DOWN:此为宏,有2个参数,第一个参数是个地址值,第二个参数是粒度大小。此宏的作用是根据地址值以粒度为标准,向低地址方向对其。
CONTAINING_RECORD:此为宏,有3个参数。第一个参数是个地址值,第二个参数为一个结构的类型名,第三个参数为此结构里一个元素的名字。此宏的作用是得到某个结构的首地址。
1,先通过Address->LowestAddress的值判断是用户空间还是内核空间,如果是内核空间,那么得到其地址上限值为0xffffffff.如果是用户空间,那么上限值就是0x800000000-1。
2,之后就来查看上节讲的那棵二叉树。如果为空,那么就说明进程刚刚建立,此时虚拟地址空间还为处女地,直接返回Address->LowestAddress这个值(不过先得按粒度值换算下)
3,如果虚拟地址空间已经被瓜分过,那么就取得第一个节点,和第二个节点,分别得到节点中标注的起始地址和结束地址。之后就加加减减的算吧。如果第二个节点中取出的起始地址减去第一个节点取出的结束地址的值小于length那么就不满足要求。说明夹在这2个区间之间的空间不够大。那么继续分析第2个节点和第3个节点。。。。就这样循环下去
4,如果中途找到了,那么返回其首地址
5,当然也别忘了第一个节点前的空间,和最后一个节点后的空间。
好了。这个函数就说到这。继续下个函数
************************************************************
MmLocateMemoryAreaByRegi
*****************************下一节我们来分析下LIST_ENTRY*******************************
} MYDATASTRUCT, *PMYDATASTRUCT;
LIST_ENTRY linkListHead;
LIST_ENTRY使用:
VOID
{
}
遍历:
PLIST_ENTRY pLink=NULL;
首先要感谢这位作者!!!!!并且在此申明此代码的所有权归其作者所有,我只是引用!!!
不过以上程序有几个地方是错误的。首先linkListHead不应该是局部定义,而是全局定义。其次就是加上我用蓝色写的代码
还有(9)这句有点莫名其妙。我已经改正
源代码地址:http://blog.csdn.net/bobohack/archive/2010/02/13/5308397.aspx做个比较!
具体讲解:
(1)前面笔者已经说过,如要想让一个结构有机会被串起来,那么必须包含LIST_ENTRY结构
(2)先定义一个结构实例,并用InitializeListHead初始化,此时此刻的这个结构实例作为链表头而存在
(3)初始化一个“自旋锁”。自旋锁也是驱动编程中用的最为广泛的对象。他的作用类似于应用程序编程中的线程同步对象EVENT。起到同步的作用。为了防止多个线程同时操作这个链表
(4)请求这个“锁”。一旦成功,那么其他线程再次请求的话就会导致失败。
(5)生成MYDATASTRUCT大小的空间,并且使用InsertHeadList这个函数把刚生成的MYDATASTRUCT结构挂入到LIST_ENTRY链表中。需要非常注意这个函数的2个参数!参数理解了,那么链表的结构也就理解了。
(6)操作好链表之后,进行解锁。解锁之后,其他线程就可以请求这个锁了。
(7)节点的移除
(8)此函数为 ExAllocatePoolWithTag的逆操作
(9)是个FOR语句。这句话有个非常重要的信息告诉我们。链表中最后一个节点的FLINK指向的是头节点!
*************************LIST_ENTRY相关代码比较多,可以百度搜索********************************
上面链表的遍历用的是FOR语句,你还可以用WHILE语句
pLink = linkListHead.Flink
While(PLink!=linkListHead)
{
}
探讨好了之后,函数代码也就结束了。返回值是新的区块MM_REGION结构的首地址!!!!!
**********************************虚拟地址空间的“保留”到此结束**************给点掌声,谢谢*****