太子爷小说网 > 杂集电子书 > windows环境下32位汇编语言程序设计 >

第73节

windows环境下32位汇编语言程序设计-第73节

小说: windows环境下32位汇编语言程序设计 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!






 
来源:电子工业出版社 作者:罗云彬 上一页         回书目         下一页          
上一页         回书目         下一页          
  


第10章 内存管理和文件操作


10。1 内 存 管 理(7)

    
invoke  VirtualFree,lpAddress,dwSize,dwFreeType

lpAddress和dwSize参数指定地址和地址空间的大小,dwFreeType指定释放地址空间的方式,它可以是以下的数值:

●   MEM_DEMIT——为一个已经提交物理内存的地址空间解除提交。

●   MEM_RELEASE——释放保留的地址空间。

现在来看如何使用它们来保留地址空间和释放保留的地址空间。使用VirtualAlloc函数保留一个地址空间的分配方式使用MEM_RESERVE,由于被保留的地址空间还没有提交给物理内存,是无法访问的,所以保护属性必须使用PAGE_NOACCESS标志,具体的语句是:

    invoke  VirtualAlloc,NULL,10485760,MEM_RESERVE,PAGE_NOACCESS

    。if     eax

            mov lpAddress,eax

    。endif

这一段代码导致系统保留一个10 MB大小的地址空间。当在一个进程中保留地址时,没有物理内存页被提交,也没有在页文件中为它保留空间,而只是阻止了其他内存分配函数对该段地址的请求而已,保留一个地址范围并不保证将来会有可用的物理内存来提交给这些地址。

保留地址的操作是很快的,保留一个小的地址范围和保留一个大范围的地址空间的速度差不多,因为在操作期间,并没有资源分配。

如果要释放保留的地址空间,可以使用MEM_RELEASE方式调用VirtualFree函数:

    invoke  VirtualFree,lpAddress,0,MEM_RELEASE

lpAddress就是上面调用VirtualAlloc返回的指针,dwSize参数在这里必须为0。当使用上面的VirtualAlloc函数保留了一段地址空间以后,接下来还可以继续多次调用同样的函数提交这段地址空间中的不同页面,所以到最后不同的页面可能处在不同的状态中(提交的和没有提交的)。如果用VirtualFree函数释放这个地址空间,所有的页面必须处在相同的状态下(可以是全部提交的或全部没有提交的),否则释放操作会失败。当不同页面的状态不同的时候,最好首先将所有的已提交页面逐一解除提交,最后再使用上面举例的方法释放整个地址空间。

有时候,两次调用VirtualAlloc函数保留了两段连在一起的地址空间,对于这种情况,虽然两段地址空间实际上是连在一起的,但也无法调用VirtualFree函数将它们一次释放,必须调用两次VirtualFree函数将它们分别释放。

2。 使用保留的地址空间

要使用保留的地址,首先必须提交物理内存给该地址。提交内存到地址与保留内存同样使用VirtualAlloc函数,只是调用的方式使用MEM_MIT标志。在已经保留的地址段中,内存可以按一页的大小被分次提交,也可以一次提交所有的保留地址。

当内存被提交时,可能全部被分配为物理内存页,也可能一部分或全部被分配在页文件中,直到它被访问。一旦内存页已提交,系统就会像对待用其他函数分配的内存块一样来对待它们。

使用VirtualAlloc函数提交地址空间的方法是:

invoke  VirtualAlloc,lpAddress,4096,MEM_MIT,PAGE_READWRITE

    。if     eax

            mov lpMemory,eax

。endif

这个语句将一个页面4 096 B的保留地址提交到物理内存。在提交的时候,lpAddress参数不能指定为NULL,而是要指定一个特定的地址来准确地指示被保留地址的哪一页会被提交。而且,页的属性现在要指定是可以访问的,不能再使用PAGE_NOACCESS,可以使用PAGE_READWRITE和PAGE_READONLY等属性。如果函数执行成功,返回的是被提交地址中第一页的起始线程地址,执行失败将返回NULL。

提交内存的时候,系统只能按页面的整数倍大小提交,函数会自动按照lpAddress和dwSize指定的范围把与这个范围同属一个页面的地址全部提交,所以当lpAddress指定的数值不是一个页的整数倍的时候,返回的lpMemory就不会和指定的lpAddress相同,而是被修改为页的边界地址。

如果要一次提交全部保留的地址空间,那么可以把保留和提交的操作合并到同一次对VirtualAlloc函数的调用中:

invoke   VirtualAlloc,NULL,dwSize,MEM_RESERVE or MEM_MIT,PAGE_READWRITE

。if     eax

            mov lpMemory,eax

。endif

这种方法与用GlobalAlloc函数直接分配一块内存没有多大的差别,惟一的好处就是可以自己指定分配的内存块地址。

如果想对已经提交的页面解除提交,让它们从提交状态返回到保留状态,可以使用VirtualFree函数,这时需要使用MEM_DEMIT参数:

    invoke  VirtualFree,lpMemory,dwSize,MEM_DEMIT

同样,函数操作的对象是整个页面,如果指定的内存范围不是整个页面,函数会自动将整个范围同属一个页面的地址全部解除提交。

3。 内存页的保护和锁定

除了用VirtualAlloc函数在提交内存的时候指定不同的保护方式外,也可以在以后用VirtualProtect函数来改变虚拟内存页的保护方式。比如,应用程序可以按PAGE_READWRITE来提交一个页并立即将数据写到该页中,然后马上使用VirtualProtect函数将该页的保护方式改为PAGE_READONLY,这样可以有效地保护数据不被该进程中的任何线程重写。VirtualProtect函数的用法是这样的:

 

    invoke  VirtualProtect,lpAddress,dwSize,flNewProtect,lpflOldProtect

flNewProtect是新的保护方式,取值可以参考VirtualAlloc函数中的flProtect参数,lpflOldProtect 是指向一个双字的指针,函数会在这里返回原来的保护方式,如果不需要知道原来的方式,可以把这个参数设置为NULL。

VirtualProtect函数还可以用在什么地方呢?MSDN中由Randy Kath书写的一篇文章《Managing Virtual Memory in Win32》中的例子很有代表性:

“一个用于缓冲数据的应用程序接收到一组大小变化的数据流,由于其他应用程序对CPU时间的竞争,数据流可能在某些时候超出进程的能力。为了防止这种现象发生,应用程序可以在开始时为一个缓冲区提交一些内存页,然后使用PAGE_NOACCESS保护来保护内存的顶端页,使得任何想要访问该内存的请求都会产生一个异常。应用程序也在该代码的外层代码中使用一个异常处理程序来处理访问冲突。”

“当处理能力不够的时候,缓冲区会满到这个受保护的顶端页,于是会产生一个访问冲突,这时应用程序就知道缓冲区已经到了其极限,该应用程序可以通过将页保护改变为PAGE_READWRITE来响应,允许该缓冲区接收任何附加的数据,并且继续不间断地执行。同时,应用程序加载另一个线程来减缓数据流,直到该缓冲区恢复到一个理想的操作范围。当情况恢复到正常,顶端的页又返回为PAGE_NOACCESS页,附加的线程也结束了。这样可以将页保护和异常处理程序结合使用来提供独一无二的内存管理机会。”

另外,应用程序还可以使用VirtualLock和VirtualUnlock函数,它们的功能分别是将内存页锁定在物理内存中以及解除锁定。这两个函数的语法很简单:

    invoke  VirtualLock,lpAddress,dwSize

    invoke  VirtualUnlock,lpAddress,dwSize

“锁定”的意思是要求系统总是将指定的内存页保留在物理内存中,不许将它交换到磁盘页文件中。如果程序中有些内存被频繁使用,将它们保留在物理内存可以提高访问的速度。由于锁定太多的页面会导致其他页面被频繁交换到页文件中,所以Windows限制每个进程能同时锁定的页数不能超过30个。只有已经被提交的内存页才能被锁定,对一个保留的地址进行锁定操作是不能成功的。

10。1。6  其他内存管理函数

Win32中还有其他的一些内存管理函数,可以用来完成一些辅助的功能,如内存填充、移动以及测试函数等。

1。 填充和移动内存

填充和移动内存本来就可以用几句简单的代码实现,如下面的代码可以将从szSource开始的dwSize大小的内存块移动到szDest处:

mov esi;offset szSource

    mov edi;offset szDest

    mov ecx;dwSize

    cld

rep movsb



 
来源:电子工业出版社 作者:罗云彬 上一页         回书目         下一页          
上一页         回书目         下一页          
  


第10章 内存管理和文件操作


10。1 内 存 管 理(8)

    
而下面的代码可以将szDest处的dwSize字节填充为0:

xor eax;eax

mov edi;offset szDest

mov ecx;dwSize

cld

rep stosb

如果把xor eax;eax换成mov al;xx,那么完成的功能就是将这块内存填充为xx。

虽然填充和移动的功能这么简单,但Win32中还是有对应的API函数:

invoke  RtlMoveMemory;offset szDest;offset szSource;dwSize  ;移动内存

invoke  RtlFillMemory;offset szDest;dwSize;dwFill   ;以dwFill填充内存块

invoke  RtlZeroMemory;offset szDest;dwSize          ;以0填充内存块

可以看到,使用这些函数时,仅传递参数和调用的开销就远远超过了前面举例的两段代码,但是使用它们的可读性比较好,所以在具体的使用中要有所取舍。如果执行速度比较重要,比如是在一个循环中使用,同样的代码要被使用很多遍,还是应该使用嵌入的几句汇编代码;如果为了让程序看上去简洁一些,那就不妨使用这几个API函数。

2。 内存状态测试

有时候在访问一块内存之前,可能想知道这块内存的属性究竟是什么,是可写的?可读的?还是可执行的?这些功能可以用测试函数来完成:

    invoke  IsBadCodePtr,lpMemory

    invoke  IsBadReadPtr,lpMemory,dwSize

    invoke  IsBadWritePtr,lpMemory,dwSize

    invoke  IsBadStringPtr,lpMemory,dwSize

这些函数的功能如下:

●   IsBadCodePtr函数测试某个指针指向的单个字节是否可读,如果可读则返回0,否则返回非0值。

●   IsBadReadPtr函数测试某段内存是否可读,如果这段内存的所有字节都是可读的,则返回0,如果中间包含有不可读的字节则返回非0值。

●   IsBadWritePtr函数测试某段内存是否可写,如果这段内存的所有字节都是可写的,则返回0,如果中间包含有不可写的字节则返回非0值。

●   IsBadStringPtr函数测试的同样是可读性,lpMemory参数指向一个以0结尾的字符串,字符串的最大长度为dwSize,如果整个字符串包含结尾的一个0都是可读的,则函数返回0,否则返回非0值。缓冲区中剩余的字节则不予测试。



 
来源:电子工业出版社 作者:罗云彬 上一页         回书目         下一页          
上一页         回书目         下一页          
  


第10章 内存管理和文件操作


10。2 文 件 操 作(1)

    
10。2。1  Windows的文件I/O

在DOS操作系统下,最早的文件操作方法是使用FCB(文件控制块),FCB是一个数据结构,为了存取一个文件,必须建立一个FCB并在其中填写好驱动器名、文件名和要读写的记录号等,然后调用int 21h中对应的功能。使用FCB方式的缺点很多,如每次只能按记录为单位读取数据,无法随意指定数据块大小,无法直接指定一个全路径的文件名,文件的操作位置不会自动调整,每次操作都必须指定记录号等,归纳起来就是功能简单,操作复杂。

于是在2。0以上的DOS版本中,开始使用更方便的文件句柄方式,这种方式不再需要文件控制块,程序指定一个包含全路径的文件名后,就可以要求操作系统打开这个文件并返回一个文件句柄,以后就可以用这个句柄来读写文件,直到关闭文件为止。操作系统在内部为每个文件句柄维护一个读写指针。读写指针总是指向文件下一次要存取的位置,每次对文件的读写操作完成以后,读写指针会自动调整到本次操作的最后一个字节后面的

返回目录 上一页 下一页 回到顶部 0 0

你可能喜欢的