windows环境下32位汇编语言程序设计-第60章
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
保留地址的操作是很快的,保留一个小的地址范围和保留一个大范围的地址空间的速度差不多,因为在操作期间,并没有资源分配。
如果要释放保留的地址空间,可以使用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版本中,开始使用更方便的文件句柄方式,这种方式不再需要文件控制块,程序指定一个包含全路径的文件名后,就可以要求操作系统打开这个文件并返回一个文件句柄,以后就可以用这个句柄来读写文件,直到关闭文件为止。操作系统在内部为每个文件句柄维护一个读写指针。读写指针总是指向文件下一次要存取的位置,每次对文件的读写操作完成以后,读写指针会自动调整到本次操作的最后一个字节后面的位置,这样顺序读写文件就不必每次重新指定位置。读写指针可以被移动到文件的任意位置,以便满足随机存取的要求。
Windows操作系统中,文件操作沿用了这种句柄方式,保留了文件句柄和读写指针等概念,同时又根据Windows操作系统的新特征对文件I/O进行了很多的扩展,下面列出了Win32中文件函数经过扩展的一些功能:
● 文件函数的操作对象有了很大的扩展,除了普通的文件,对串口、磁盘设备、网络文件、控制台和目录等的操作都可以使用文件函数来完成。
● 支持异步文件操作,文件函数可以不必等待到操作完成才能返回。
● Windows是多用户的操作系统,可能发生多个程序同时对文件操作的现象,文件函数中增强了对共享和锁定的支持。
● 文件操作函数和内存映射文件函数配合可以实现将文件当做内存的一部分来存取的功能。
● 增加了拷贝文件和移动文件等函数来实现常用的功能。
另外,在文件的命名中有长短文件名之分,众所周知,DOS操作系统使用8。3结构的文件命名方式,在这种命名方式下,用文件名来简单地说明文件的用途显得比较困难,因为仅用8个字符是表达不了什么复杂的含义的。
而在长文件名系统中,文件名的长度可以长达255个字符,这样在文件名中就可以清晰地表达出文件的用途,长文件名在磁盘的目录区中占用了多个连续的目录项,其中的一个目录项用做8。3结构的短文件名,其他的目录项存放其他名字字符。在8。3文件名中不合法的一些字符,如小数点与空格等在长文件名中都可以使用,只有/ :*?〃|等9个字符不能用于长文件名。
长文件名需要文件系统的支持,从DOS到Windows,使用过的有FAT,VFAT,FAT32,NTFS与HPFS等多种文件系统,在这些文件系统中,只有FAT系统不支持长文件名。
各种操作系统对文件系统的支持是不同的。Windows 3。x和DOS操作系统一直使用的是文件分配表(FAT)系统;Windows 95开始使用扩展FAT文件系统(VFAT),FAT系统和VFAT系统都是16位的文件系统,也称为FAT16。Windows NT在支持FAT16的同时,还支持两种32位的文件系统:NT文件系统(NTFS)和高性能文件系统(HPFS),NTFS支持文件的安全性,能够指定谁能访问某一文件或目录和对它做什么操作。Windows 98在支持FAT16的同时,也支持32位的FAT文件系统(FAT32),但Windows 9x系列操作系统不支持NTFS和HPFS。Windows 2000则支持上面所列的所有文件系统。
那么在Win32的文件操作函数中,如何处理长、短文件名,又如何处理不同的文件系统呢?答案很简单:就是不要去考虑它们,不管要操作的文件名是长是短,不管文件位于什么样的文件系统中,只要指定了正确的文件名,文件操作函数就能正确地处理它。
10。2。2 创建和读写文件
在开始讨论文件I/O的函数之前,先来看一个例子,这是一个对文本文件中的英文单词进行统计的小程序WordCount,程序将文本文件读入并分析文件内容,最后将统计结果保存到记录文件中,比如对包含下面文本的文件进行统计,就会得到如图10。4所示的记录文件:
“He's one of my best friend; a very very good man and has a very very good job and their relationship used to be so charming and stable; but now changed。”
图10。4 WordCount程序的运行结果
为了实现程序的功能,除了读文本文件和写记录文件外(这部分在本节中逐步介绍),还要涉及如何设计程序的结构。虽然可以用一个比较笨的办法:首先读一个单词,将它保存在缓存区中并为它增加一个计数,以后每读到一个单词就和以前保存的所有单词比较一下,如果存在则增加计数,不存在则保存一个新单词,但这种办法随着数据的增长工作量会急剧增加,所以例子程序用了另外一种方法:就是用树型结构的办法。
如图10。5所示,树的每个结点中有个计数器,还有26个子结点指针,子结点指针根据单词中的下一个字母是A~Z分别指向不同的子结点,假如我们遇到一