Windows编程_Lesson008_内存

来源:互联网 发布:汉诺塔递归算法java 编辑:程序博客网 时间:2024/05/23 00:27

我们接下来将要比较神秘,也是平时接触比较少的一个东西,那就是内存
有人会说我们平时接触的内存不是很多吗?尤其是对于C/C++程序员,好像无时无刻都在于内存打交道啊!怎么会说它比较神秘呢?说的也并不无道理,但是我们这里所说的内存并不是站在程序员的角度来看待内存的。如果站在程序员的角度看内存,内存的机制是透明的,但是这对于我们以后想要开发更好的程序,或者说想要我们的程序运行的更快,这是不利的。下面我们要学习的是在Windows下的内存管理方式。下面我们来深入的了解一下虚拟内存。

虚拟内存

1.支持多进程
首先我们在学习虚拟内存之前,我们必须明白一个最基础的概念,它就是虚拟内存物理内存(逻辑内存)。我们平时只是说内存,但是并没有具体说虚拟内存和物理内存这两个东西。我们在组装电脑的时候,肯定少不了的就是内存条,有的台式机上甚至有好几条内存条,为什么要有内存条呢?因为它就是我们所说的物理内存,而我们的的程序实际运行的地方就是物理内存。但是Windows中的虚拟内存又是怎么回事呢?它是和物理内存相对应的。虚拟内存并不是真真实实存在于物理内存中的,因为Windows操作系统还要支持多进程的运行,而我们的物理内存只有一块,那么其它进程的内存该放到哪儿呢?所以Windows就把它们放到虚拟内存中了,如果这个进程需要执行,那么就把它所对应的虚拟内存加载到物理内存中来运行。这就和我们前面所说的线程(CONTEXT)之间切换是类似的,作用也是差不多一样的,需要注意的是虚拟内存模拟了所有物理内存的功能,这样在加载到物理内存的时候才能得到顺利的运行。
我们在进行应用程序开发的时候,我们所面对的都是虚拟内存,我们是根本不可能接触到物理内存的,这是因为Windows操作系统帮我们完成了把虚拟内存加载到物理内存中,和从物理内存中卸载虚拟内存的操作,所以从某一种意义上来说,我们所操作的都是虚拟内存,这也是Windows考虑到安全问题才这么设计的。但是无论是虚拟内存也好,物理内存也罢,对于我们程序员来说操作的都是内存,包括以后我们所提到的内存也都是虚拟内存,因为我们内有办法去直接操作我们的物理内存。
如果说Windows只是解决多进程的问题,它还有很多其它的解决方案,这就是我们要说的第二个原因。
2.分配额定大小空间
应该说这是一个非常大的创举,Windows给程序员解决了一个非常大的问题。这是因为在很早很早以前(比如486、586甚至386、286)进行软件开发的时候,我们必须专机专用,即使是里面的硬件驱动都是一样的,我们也必须得专机专用,这又是为什么呢?这是因为内存空间大小有可能不一样,这样我们的程序管理内存空间的方式也是不一样的,导致在开发的时候会遇到很多问题。
所以Windows的分配固定大小空间的方法帮程序员们解决了这个大问题。Windows规定,在32位程序下,每个应用程序的虚拟空间有4GB,在64位程序下,每个应用程序的虚拟空间有16EB(总之很大很大)。程序员只管使用Windows规定的这些空间就可以了,而不用去管用户实际的物理内存大小。
虚拟内存给从程序带来的一些特性
每个进程所拥有的内存空间是私有的(别的进程是无法访问的)。假如我们有两个进程:
进程A:0x12345678
进程B:0x12345678
这两个进程不同,它们所指向的内存空间相同吗?肯定是不相同的,因为进程之间的内存空间是隐藏的。尽管这两个指针的地址一样,它们所存放的内容也是不一样的。
需要注意的是:进程空间中有操作系统的代码(空间)进程在进行运行的时候,无论如何都会有操作系统进行参与的,因为所有的操作都是在操作系统中完成的,所以对于32位程序,它所能使用的空间是不够4GB的,剩下的空间是用来存储操作系统的代码,操作系统的代码也是私有的。但是在win98之后的所有程序对于进程来说也都是隐藏的,没有办法直接访问操作系统中的内存的(因为是隐藏的)。进程中的工作单位是线程,一个进程的中的线程无法访问其他进程中的地址空间,进程中的线程无法直接访问系统中的内存空间。

虚拟内存的分区

每个进程的虚拟地址空间都要划分成各个分区。
其实在进行分区的时候,我们大家也并不陌生,比如全局区、静态区、栈区、堆区以及代码区,但是这只是片面的,只是在用户区中进行详细划分的,和我们的下面所说的分区还是不一样的。
这些区域是通过内核的指针范围来进行的划分。

分区 32位Windows 64位Windows Windows 98 NULL指针 0x0-0xFFFF 0x-0xFFFF 0x0-0xFFF DOS 16兼容 无 无 0x1FF-0x3FF 用户 0x10000-0xFFEFFFFF 0x10000-0x3FFFFFEFFFF 0x400000-0x7FFFFFFF 64KB禁止进入去 0x7FFF0000-0x7FFFFFFF 0x3FFFFFF0000-0x3FFFFFFFFFFF 无 共享内存映射 无 无 0x80000000-0xBFFFFFFF 内核 0x80000000-0xFFFFFFFF 0x40000000000-0xFFFFFFFFFFFFFFFF 0xC0000000-0xFFFFFFF2

NULL指针,用来对进程中指针的保护,如果指针指向到了这里,那么就会抛出空指针的异常,方便程序的调试。
DOS 16兼容,在Windows 98中,为了兼容前面dos系统中的程序。到了32位和64位的时候,可以完美的兼容,可以在用户区里面运行。
用户,它是一块非常重要的区域,它是程序中分配的所有空间地址都是在这个区间中分配的。一般情况下,在32位系统下,应用程序可用的内存空间是2GB,但是如果有特殊需要,比如数据库需要更大一些的内存空间,可以通过一些特殊设置来压缩1GB的系统空间给用户,用户空间就变成了3GB。在64位下,可以认为无穷大。
64KB禁止进入去,这相当于一个高压线,在这条线之上的可以访问,但是在高压线之下的就永远也不可能访问,因为下面运行的是操作系统的内核。
共享内存映射,只有在win98下,提供了一块内存共享区域来进行映射,但是到了32位系统以后,都是通过内核对象、网络等来进行交互的。
内核,运行了内核对象,比如线程对象、进程对象、内存管理、文件、网络以及驱动等。

用户地址空间

我们能够操作的内存空间也只有用户地址空间可以操作,剩下的其它的内存地址空间都无法操作。

内存的分配

当进程被创建并被赋予它的地址空间时,该可用地址空间的主体是空闲的,即未分配的。
如果要使用该地址空间的各个部分,必须通过调用VirtualAlloc函数(C语言里面的额malloc函数、C++语言的new函数等到最后调用的都是VirtualAlloc函数来真正的分配空间的)来分配它里面的各个区域。对于一个地址空间的区域进行分配的操作称为保留,当一块内存地址空间使用完成时,我们需要使用VirtualFree函数进行释放。(需要注意的是,我们这里所说的所有内存都是对虚拟地址空间进行操作的!!!
每当你保留地址空间的一个区域时,系统要确保该区域从一个分配粒度的边界开始。
当你保留地址空间的一个区域时,系统还要确保该区域的大小是系统的页面大小的倍数。

分配颗粒

所有CPU平台都会使用相同的分配颗粒(即64KB)。

页面大小

4KB。

页面及提交物理内存

在电脑中之所以能够进行正确有序的运算,那是因为我们有一个类似于人的大脑的东西(CPU),CPU能够从内存中读取数据,并且加以解释、计算,最终得到我们想要的结果并呈献给我们。
CPU只能去物理存储器里面取数据,但是我们写的程序是在虚拟内存地址空间操作的,我们的程序如果想要被CPU执行,就必须要通过物理内存传给CPU,所以虚拟存储器的数据还需要物理存储期进行转存。
所以:
1. 物理存储器和虚拟存储器之间还必须要有一个通道,好让虚拟内存的数据传递给物理存储器。
2. 虚拟存储器中记录了一些状态,而这些状态也是需要存储的,所以虚拟存储器是我们常见的硬盘存储器的一个区域里面(即虚拟存储器是在硬盘存储器中的)。
虚拟存储器存储的数据我们可以称之为页交换文件。
每一次从虚拟存储器将数据提交到物理存储器中的时候,是以页面大小(4KB)为单位来提交的。但是更多的时候,并不是将虚拟存储器中的内容直接提交给物理存储器的,因为在程序运行的时候,一切都是以CPU为主的,CPU又是以线程为单位来进行一块一块的数据访问。CPU进行数据访问的过程大致如下:
首先CPU会检查需要访问的这一数据块是否在物理内存中,如果存在,那么就直接拿来使用;如果不存在,它会产生一个页面错误,这个页面错误会被操作系统捕获到,操作系统会检查所需要的这一数据块是否存在于虚拟存储器中,如果存在,那么还要检查物理存储器中是否还有空间来加载这一数据块,如果有剩余的空间,那么就直接加载过来,如果没有多余的空间,它会尝试的将物理存储器中的一个页面来释放,如果释放成功,那么就加载过来,如果释放失败,就会报内存不足的错误,如果需要的数据块也不在虚拟存储器中,它也会发出一个错误。
这里写图片描述

64位及内存对齐

页面的大小是由CPU来决定的,因为CPU在读取数据的时候,是以页为单位进行读取的。页面大小也将会影响内存对齐,那么我们如何才能获取页面的大小呢?可以通过GetSystemInfo这个函数来获取系统信息,其中就包括了页面大小的属性、CPU的版本号(通过CPU版本号可以知道是哪一些型号的CPU)。

目前我们所使用的CPU它都是4KB,那么我们把它在程序里面写死不就可以了吗?这样肯定是要出问题的。这是因为我们现在所使用的CPU都是Intel或者AMD的X64或者X86的结构,这种结构的CPU的页面大小都是4KB(无论是32位程序还是64位程序),但是如果我们以后写服务器程序的时候,所使用的CPU将会是IA64(也是Intel公司的产品),它的页面大小是8KB。所以,如果我们把页面大小写死的话,我们的程序就会有着潜在的问题。

我们以后也不排除这一种情况:在IA64下面运行32位程序,此时我们就需要记住了!!! 尽管是32位应用程序,它获取的页面大小也是8KB,但是实际上页面大小只有4KB,此时我们的内存对齐就发出现问题。
为什么在IA64上面出现这个问题呢?
微软能够让32位应用程序在64位上面运行,中间加了一个模拟层,这个模拟层会给刚刚接触64位编程人员带来很大的困扰。
什么是模拟层呢?它的全名叫WOW64,它能够让绝大多数32位程序在64位操作系统下正常运行,但是有可能还有极少数的32位应用程序不能正常运行,这些不能正常运行的32位程序就是我们以后需要考虑的。
我们在获取页面大小之前,可以使用IsWow64Process函数先获取当前操作系统是32位还是64位的。
函数原型如下:

BOOL WINAPI IsWow64Process(  _In_  HANDLE hProcess,  _Out_ PBOOL  Wow64Process);

它的返回值只有在32位应用程序运行在64位系统的情况下,结果才是TRUE,剩下的所有情况,结果都是FALSE。这个函数的返回结果可能有点儿绕,所以建议以后每次使用这个函数时都要查文档。

我们还可以使用IsOs函数来判断,所在的头文件是Shlwapi.h。
这里写图片描述