Windows核心编程第五版学习笔记(四)
第十四章 探索虚拟内存
1、 操作系统中的许多值是系统该所运行的主机决定的,如页面大小和分配粒度,我们决不能在程序中硬编码这些值,而是应该在进程初始化时得到这些值,然后在程序中使用它们。GetSystemInfo函数。为了让32位程序也能在64位版本的windows上运行,Microsoft提供了一个Windows 32-bit on windows 64-bit的模拟层,又称WOW64,想要知道程序是否在WOW64上运行,可以调用IsWow64Process。如果返回TRUE,我们应该调用GetNativeSystemInfo来取得原来的SYSTEM_INFO结构。我们也可以通过调用ShlWApi.h中定义的IsOs函数,并以OS_WOW6432为参数来判断。
第十五章 在应用程序中使用虚拟内存
1、 VirtualAlloc(PVOID pvAddress, SIZE_T dwSize, DWORD fdwAllocationType, DWORD fdwProtect);
参数dwSize是我们想要预定的虚拟内存的大小,但是系统总是返回CPU页面大小的整数倍来预定区域。
fdwAllocationType告诉系统我们是要预定虚拟内存(MEM_RESERVE)还是要调拨物理存储器(MEM_COMMIT)
最后一个参数是页面保护属性
例:PVOID pv =
VirtualAlloc(NULL, 99*1024, MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE).
2、 windows还提供了大页面的支持。只要在调用VirtualAlloc并将MEM_LARGE_PAGE标志与fdwAllocationType参数按位或起来即可,此外还必须满足一下三个条件
要分配的内存块的大小必须是GetLargePageMinimum函数返回值的整数倍;掉用VirtualAlloc时必须把MEM_RESERVE和MEM_COMMIT标志按位或起来;在用VirtualAlloc分配内存时必须传PAGE_READWRITE保护属性给fdwProtect参数。
Windows认为用MEM_LARGE_PAGE标志分配得到的内存是不可换页的,也就是说必须驻留在内存中,但是内存是稀缺资源,用该标志掉哟娜gVirtualAlloc需要调用方具有内存中锁定页面的权限,否则函数调用会失败。
3、 同样可以通过VirtualFree来只撤销调拨的物理存储器而不释放区域,撤销也是以页面为粒度撤销的。
4、 当若干连续的物理存储页跨越了不同的区域时,VirtualProtect是不能该变他们的保护属性的,如果有相邻的区域,又想改变跨区域的连续页面的保护属性,那么必须调用VirtualProtect多次。
5、 重置物理存储器的内容:我们告诉系统一个或几个物理存储页中的数据没有被修改过,这样系统在满足最近的载入请求时,就可以直接把这个或这几个物理存储页拿来用,儿不必先把其中的内容写回到页交换文件中。VirtualAlloc,第三个参数传MEM_RESET。
当调用VirtualAlloc时,基地址会被向下取整到页面大小的整数倍,儿大小则会被向上取证到页面大小的整数倍,重置存储器时,这样是很危险的,有鉴于此,当传入的是MEM_RESET时,VirtualAlloc会从相反的方向进行取整操作。
MEM_RESET必须单独使用,不能和其他标志按位或起来。
在用MEM_RESET调用VirtualAlloc时,必须传一个有效的保护属性值,即使函数实际上并没有使用它。
第十七章 内存映射文件
1、 内存映射文件允许开发人员预定一块地址空间区域并给区域调拨物理存储器,不同之处在于,内存映射文件的物理存储器来自磁盘上的已有文件,而不是来自系统的页交换文件,一旦把文件映射到地址空间,我们就可以对它进行访问,就好象整个文件都已经被载入内存一样。
2、 内存映射文件主要用于三种情况:
- 系统是使用内存映射文件来载入并运行.exe和dll文件,这样大量节省了页交换文件的空间以及应用程序启动时间。
- 开发人员使用内存映射文件来访问磁盘上的数据文件,这使得我们可以避免直接对文件进行IO操作和对文集那内容进行缓存。
- 通过使用内存映射文件,我们可以在同一台机器的不同进程之间共享数据,Windows的确提供了其他一些方法来在进程间传送数据,但这些方法都是通过内存映射文件来实现的,因此,如果要在同一台机器的不同进程间共享数据,内存映射文件是最高效的。
3、 使用编译器指示符创建自己的段
#pragma data_seg(“sectionname”)
例:#pragma data_seg(“Shared”) Long g_lInstanceCount = 0; #pragma data_seg()
当编译器编译这段代码时,会创建一个名为Shared的段,并把pragma指示符之后的所有带有初始值的变量放到这个新的段中,变量后面的那行#pragma data_seg()告诉编译器停止把已初始化的变量放到Shared段中,而是重新开始把它们放到默认的段中,需要记住的一点是,编译器只会把已经初始化的变量放到这个段中。
另一种方法:
#pragma data_seg(“shared”)
Int a = 0 //Initialized, in shared section
Int b; //UnInitiallized, in other section
#pragma data_seg()
__declspec(allocate(“shared”)) int c = 0; //Initialized, in shared section
__declspec(allocate(“shared”)) int d; //Uninitialized, in shared section
Int e = 0; //Initialized, not in shared section
Int f; //UnInitialized, not in shared section
之所以将变量放到一个段中,最常见的原因也许是为了在同一个.exe或.dll的多个实例间共享变量。一旦把变量放到一个单独的段中,系统就不会再在同一个可执行或Dll的每个实例中为它们创建新的实例了。
要共享变量,仅仅这样做还不够,还需要告诉连接器要共享这个段中的变量,在连接器的命令行中使用/SECTION开关实现
/SECTION:name, attributes
例如我们想要改变shared段的属性
/SECTION:shared, RWS
这个开关告诉连接器,shared中的变量是可读可写可共享的。
另一种直接写到代码中的写法: #pragma comment(linker, “/SECTION:shared,RWS”);这行代码告诉编译器把其中的字符串嵌入到生成的.obj文件的一个特殊的段中,段名是.drectve,当链接器把所有的.obj模块合并到一起的时候,连接器会检查每个模块的.drectve段,并将所有的字符串当作是传给连接器的命令行参数。
4、 UnmapViewOfFile有一个特性需要牢记,如果视图最初是用FILE_MAP_COPY标志映射的,那么对文件数据的修改实际上是对保存在页交换文件中的文件数据的副本的修改,如果在这种情况下调用UnmapViewOfFile,函数不需要对磁盘文件进行任何更新,但它会释放页交换文件中的页面,从而导致数据丢失。
当我们把文件映射到进程的地址空间中的时候(MapViewOfFile函数),不必一下子映射整个文件,可以每次只把文件的一小部分映射到地址空间中,文件中被映射到进程的地址空间中的部分被称为视图。把文件的视图映射到进程的地址空间中的时候,我们必须告诉系统两件事,一是告诉系统应该把数据文件中的哪个字节映射到视图中的第一个字节,二是告诉系统要把数据文件的多少映射到地址空间中去。
PVOID MapViewOfFile(HANDLE hFileMappingObject, DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap);中,dwFileOffsetHigh和dwFileOffsetLow组成的偏移量必须是系统分配粒度(当前所有平台为64KB)的整数倍,最后一个参数为0,系统会把从偏移量开始到文件末尾的所有部分映射到视图中,无论整个文件映射对象多大,该API只需要找到一块足够大的地址空间区域来容纳制定的视图。
5、
第十八章 堆
1、 在分配大块内存(大于1MB)的时候因该避免使用堆函数,建议使用VirtualAlloc函数。
2、 在进程完全终止前,系统不允许销毁进程的默认堆,如果把进程的默认堆的句柄传给HeapDestroy,系统将直接忽略该调用并返回FALSE。
浙公网安备 33010602011771号