• 回炉重造之重读Windows核心编程-013-Windows的内存结构


    第13章Windows的内存结构

    13.1 进程的虚拟地址空间

    每个进程都有自己的虚拟地址空间,32位下是4GB,指针的寻址范围是0x000000000xFFFFFFFF之间的值;64位下是16EB(10的18次方),指针寻址范围是0x00000000000000000xFFFFFFFFFFFFFFFF。每个进程都只能访问自己的私有空间。操作系统的内存也是隐藏的。
    虽然两个不同的进程A和B都可以访问同样的0x12345678,但是它们访问的是各自的数据结构。
    而且,这些都是虚拟地址,不是物理地址空间。

    13.2 虚拟地址空间如何分区

    每个进程的虚拟地址空间都要划分各个分区,根据操作系统的实现方法来划分,不同的Windows内核的分区方式略有不同。

    13.2.1 NuLL指针分配的分区—适用于Windows2000Windows98

    这个分区使用NULL指针的分配。如果试图读写这个分区,就会发生一个访问违规的情况。

    13.2.2 MS-DOS/16位Windows应用程序兼容分区—仅适用于Windows98

    这个分区用于兼容Windows98,大小是4MB。读写它同样会发生访问违规的情况。

    13.2.3 用户方式分区—适用于Windows2000Windows98

    进程私有的地址空间,不能被其他线程读写或访问。这样让整个系统更加健壮。
    实际情况下可以使用的地址空间还不到进程的地址空间的一半。显然另一半是操作系统需要的,用于内核代码、设备驱动程序代码、设备IO高速缓存、非页面内存池的分配和进程页面表等使用。只有在64的Windows2000下,内核才得到了它要的空间。

    1. x86下获取3GB用户方式分区的方式是将/3GB这个开关加到系统的BOOT.INI文件的有关项目中。这还不行,链接程序还要有/LARGEADDRESSAWARE这个开关供 Microsoft 检查。如果链接的时候没有加这个开关,Microsoft将会保留多出来的1GB,防止意外的访问。另外,使用了/3GB之后,内核只能勉强地放入一个1GB的分区,这将减少系统能创建的线程、堆栈和其他资源的数量。而且系统最多使用16GB的RAM,而通常最多可以使用64GB的RAM,因为内核方式没有足够多的虚拟地址空间可以用来管理更多的RAM了。最后,编译DLL是忽略/LARGEADDRESSAWARE这个开关的。
    2. 64位Windows2000中获得2GB用户方式分区
      1. 当从32位移植到64位的时候,指针还是视为32位地址就会造成访问违规。不过,只要对0x000000007FFFFFFF的高33位零,这样就可以截断64位地址位32位而不会造成任何影响了。这样用户地址空间有0x80000000这么多,对于大多数应用程序来说已经足够了。
      2. 若一个64位应用程序想要访问它的全部4TB空间,必须使用/LARGEADDRESSAWARE这个链接开关来创建。

    13.2.4 64KB禁止进入的分区—仅适用于Windows2000

    位于用户方式分区的前64KB是禁止进入的,访问它只会导致访问违规。保留它只是为了更容易地实现操作系统。当内存的地址和长度传递给Windows函数时,该函数将在执行它的操作前使内存块生效,而让它生效的是内核的代码,方式是让这个分区保持禁止进入的状态。

    13.2.5 共享的MMF分区—仅适用于Windows98

    这个1GB分区是系统用来存放所有32位进程共享数据的地方,比如共享的动态连接库Kernel32.dllAdvAPI32.dllUser32.dllGDI32.dll等,便于所有32位进程访问。此外每个进程加载DLL时都加载到相同的地址,这里也是内存映射文件映射到的地方。

    13.2.6 内核方式分区—适用于Windows2000和Windows98

    存放系统代码的地方,用于线程调度内存管理文件系统支持网络支持设备驱动程序的代码都在这里加载,可以被所有进程共享。在Win2000中,这些组件是受到保护的。

    13.3 地址空间中的区域

    在进程被创建并拥有地址空间后,如果想要使用其中的部分,必须通过调用VirtualAlloc函数来分配它里边的各个区域,这个分配的操作就成为保留(reserving)。分配的粒度则是相对固定的64KB

    当保留一个区域时,系统要确保该区域的大小是页面大小的倍数,页面时系统管理内存的一个内存单位,不同的CPU页面的大小不同,x86下的就是4K64位就是8K

    有时候系统能够代表你的进程来保留地址空间的区域,用来放进程环境块(PEB)。系统需要创建一个线程环境块(TEB)以便管理当前进程中存在的线程。

    值得注意的是,PEB和TEB可以不是从64KB这个边界开始的,即便它们是页面大小的倍数。

    释放区块使用函数VirtualAlloc来完成。

    13.4 提交地址空间区域中的物理存储器

    分配物理存储器,然后把它映射到已保留的地址空间区域,这个过程称为提交物理存储器,以页面的形式提交,使用函数VirtualAlloc。提交时,只需要提交到进程部分的地址空间。如果不再需要已提交的物理寄存器,物理寄存器应该被释放,通过VirtualFree完成。

    13.5 物理存储器与页文件

    在较老的操作系统中,物理存储器被视为计算机拥有的RAM的容量。今天的操作系统让磁盘空间看起来就像内存了,磁盘上的文件被称为页文件,它包含可供所有进程使用的虚拟内存。

    通过页文件,CPU可以操作更多的RAM,尽管其中的部分不是真实的RAM,而是页文件。这个过程里,根据运行的应用程序的需要,页文件的内容和RAM的内容不停的交换。

    显然这个过程增加了应用程序可以用的RAM的容量,因此页文件的使用是视情况而定的,但是尽量使用页文件是值得鼓励的,这样可以对更大的数据集进行操作。使用VirtualAlloc将物理存储器提交给地址空间的一个区域时,地址空间实际上是从硬盘上的一个文件中进行分配的。页文件的大小直接决定了有多少物理存储器可供应用程序使用,RAM的影响则相对非常小。

    以下是RAM和页的交换过程:
    RAM和页的交换过程

    从上图可以看出,线程访问的数据有可能是在RAMRAM空闲页面物理地址三个地方,所以需要相对复杂地判断数据不在这三个地方的时候的处理方式。

    系统在内存页面页文件两端交互倒腾得越频繁,系统运行得越慢。因此提高系统运行速度的方式通过增加RAM而不是提高CPU的速度效果更好

    但这也不意味着页文件有变得非常大的趋势,而且只要运行一个程序,系统就会保留一些空间,把物理存储器提交给这些空间,再把代码和数据拷贝过去这样的操作需要花费很大的代价来加载和启动这个进程。系统的做法是打开应用程序后,就确定代码和数据的大小,然后系统保留一个地址空间的区域,并指明该区域相关的物理存储器在exe文件自身。这样就省略了大部分页文件和RAM的交换过程,使得应用程序加载的非常迅速,页文件也可以保持得很小。

    可以运行命令sysdm.cpl打开系统属性,然后选择高级(英文是Advance)选项卡,再单击性能(Performance Option)设置,就可以编辑页文件。

    13.6 保护属性

    已经分配的物理存储器的各 个页面可以被赋予不同的保护属性。x86AlphaCPU不支持“执行”保护属性,由操作系统软件支持。这意味着将PAGE_EXECUTE保护属性赋予内存的话,该内存也有读优先权。不过,这仅仅是x86的情况。

    13.6.1 Copy-On-Write访问

    关于PAGE_WRITECOPYPAGE_EXECUTE_WRITECOPY两个属性。它们多用于节省RAM的使用量和页文件的的空间。例如对于10个Notepad实例在运行,那么所有这些实例都可以共享应用程序的代码和数据页面。这样可以大大提高系统性能,但这也要求所有实例都视这片内存为只读或者只执行的内存

    为了防止造成混乱,操作系统给共享内存块赋予了Copy-On-Write保护属性。当一个exe或者dll被映射到一个内存地址时,系统将计算有多少页面是可以写入的,然后从页文件中分配内存,以适应这些可写如的页面的需要。除非该模块的可写入页面是实际的写入模块,否则这些页文件是不适用的。

    当共享的内存块被其中一个线程试图写入,系统必然要干预:

    1. 查找RAM中的一个空闲页面。这一步不会失败,因为进程在被映射的时候系统,会保证有足够的空闲页面。
    2. 将试图被修改的页面拷贝到第一步找到的页面,并赋予PAGE_EXECUTE_READ_WRITEPAGE_READWRITE两个保护属性,不再变化。
    3. 系统更新进程的页(面)表,使得被访问的虚拟地址被转换成新的RAM页面

    此外,当使用VirtualAlloc函数保留地址空间或者提交物理存储器时,不应该传递PAGE_READWRITEPAGE_EXECUTE_READ_WRITE。否则会导致函数失败,GetLastError的返回值是ERROR_INVALID_PARAMETER。当exe或者dll映射时,这两个属性被操作系统使用。

    13.6.2 特殊的访问保护属性的标志

    还有三个可以属性:

    1. PAGE_CACHE用于停用提交页面的高速缓存,一般情况下最好不用,多是给处理内存缓冲区的硬件设备驱动开发人员使用。
    2. PAGE_WRITECOMBINE也是给硬件设备驱动开发人员使用,它允许把单个设备的多次写入合并在一起,以提高性能。
    3. PAGE_GUARD可以在页面上写入一个字节时使应用程序得到通知(通过一个异常条件)。Windows2000在创建线程堆栈时使用这个标志。

    13.7 综合使用所有的元素

    通过源代码中的VMMap应用程序,对一张表的说明。

    13.7.1 区域的内部情况

    对另一张表的说明。

    13.7.2 与Windows98地址空间的差别

    13.8 数据对齐的重要性

    数据对齐并不是操作系统的一部分,而是CPU结构的一部分。当CPU访问对齐的数据时,效率是最高的。当数据大小的数据魔术的内存地址总是0时,数据是对齐的。如果CPU试图读取的数据值没有正确地对齐,CPU可以产生一个异常,也可以执行多次对齐的内存访问,已读取完整的未对齐数据值。

    数据如果不对齐,就会导致内存的多次访问,放慢应用程序的运行速度。说明数据的对齐是非常重要的。

    x86CPU实现对齐的方式是通过EFLAGS寄存器的AC标志位。CPU首次加电时这个值为0。

    1. 如果这个标志是0,就通过一系列的操作,不耽误CPU顺利访问未对其的数据。
    2. 如果这个标志是1,如果CPU访问未对齐的数据,就发出一个INT 17H中断。这个标志从不被x86的Windows20000改变,因此应用程序根本看不到这个异常。

    Alpha处理器的情况不必关心

  • 相关阅读:
    问题 Duplicate entry '0' for key 'PRIMARY'
    java中转译符用"\"的几种特殊字符
    mysql在查询中常见问题汇总
    linux msql
    shell 简单的比大小脚本
    wordpress的备份与还原
    wordpress的创建
    6、httpd服务的安装、配置
    5、Linux下面桌面的安装
    4、时间同步ntp服务的安装于配置(作为客户端的配置)
  • 原文地址:https://www.cnblogs.com/leoTsou/p/12435424.html
Copyright © 2020-2023  润新知