[笔记]Windows核心编程《十三》windows内存体系结构

简介: Windows核心编程《十三》windows内存体系结构

系列文章目录



[笔记]Windows核心编程《一》错误处理、字符编码

[笔记]Windows核心编程《二》内核对象

[笔记]Windows核心编程《三》进程

[笔记]Windows核心编程《四》作业

[笔记]快乐的LInux命令行《五》什么是shell

[笔记]Windows核心编程《五》线程基础

[笔记]Windows核心编程《六》线程调度、优先级和关联性

[笔记]Windows核心编程《七》用户模式下的线程同步

[笔记]Windows核心编程《八》用内核对象进行线程同步

[笔记]Windows核心编程《九》同步设备I/O和异步设备I/O

[笔记]Windows核心编程《十一》Windows线程池

[笔记]Windows核心编程《十二》纤程

[笔记]Windows核心编程《十三》windows内存体系结构

[笔记]Windows核心编程《十四》探索虚拟内存

[笔记]Windows核心编程《十五》在应用程序中使用虚拟内存

[笔记]Windows核心编程《十六》线程栈

[笔记]Windows核心编程《十七》内存映射文件

[笔记]Windows核心编程《十八》堆栈

[笔记]Windows核心编程《十九》DLL基础

[笔记]Windows核心编程《二十》DLL的高级操作技术

[笔记]Windows核心编程《二十一》线程本地存储器TLS

[笔记]Windows核心编程《二十二》注入DLL和拦截API

[笔记]Windows核心编程《二十三》结构化异常处理


相关:

参考

参考


文章目录



   系列文章目录

   前言

   13.1 进程的虚拟地址空间

       进程的虚拟地址空间

           32位进程

           64位进程

   13.2 虚拟地址空间的分区

       13.2.1 空指针赋值区

           地址范围

           目的

       13.2.2 用户模式分区

           1.在x86 Windows下得到更大的用户模式分区

           2.在64位windows下得到2GB用户模式分区

       13.2.3 64KB禁入分区

       13.2.4 内核模式分区

   13.3 地址空间中区域

       预定地址空间中的一块区域

   13.4 给区域调拨物理储存器

       13.4.1 调拨/撤销 物理存储器

   13.5 物理存储器和页交换文件

       13.5.1 页交换文件

           作用

       13.5.2 虚拟地址转为物理存储器地址

       13.5.3 不在页交换文件中维护得物理存储器

   13.6 页面保护属性

       13.6.1 写时复制

           写时复制属性以及作用

           写入共享页面时,系统介入的操作

           在预订地址空间或提交物理存储器时

       13.6.2 一些特殊得访问保护属性标志

   13.7 实例分析

   13.8 数据对齐的重要性

       数据没有对齐的两种情况

           x86CPU对错位数据的处理

           AMDx86-64CPU 对错位数据处理

           IA-64CPU对错位数据处理

           编译器对错位数据的处理

   总结


前言


Windows内存分为:


   虚拟内存:虚拟内存表示逻辑地址,在物理内存并非真正存在的,但是跟物理内存有映射对应关系

   物理内存:物理内存条上能找到实际地址的内存。


   每个进程都有自己独立的虚拟内存,在32系统中,每个进程是4G的虚拟内存

   而每个进程的虚拟内存只是预定的,而非实际提交的,不然系统这么多进程,系统不得需要4*n G 大小的内存条了


13.1 进程的虚拟地址空间


进程的虚拟地址空间

32位进程

地址范围:0x00000000 ~0xFFFFFFFF 任一值

地址空间大小:4GB


64位进程

地址范围:0x00000000·00000000 ~0xFFFFFFFF·FFFFFFFF 任一值

地址空间大小:16EB1


       在windows中,正在运行的线程看不到属于操作系统操作本身的内存,这意味着它不能无意间访问到操作系统的数据

       每个进程都有自己的私有的地址空间

       同属于一个进程的线程们共享地址空间

       虚拟地址空间有这么大,但不是物理存储器。仍然需要物理存储器分配或映射相应的地址空间,否则将会导致访问违规。


13.2 虚拟地址空间的分区


32位Windows内核和64位Windows内核的分区基本一致,唯一不同在于大小和分区的位置。

图片.png


13.2.1 空指针赋值区

地址范围

0x00000000 ~ 0x0000FFFF

目的

目的:帮助程序员捕获对空指针的赋值,如果进程中的线程视图读取或写入位于这一分区内的内存地址,就会引发访问违规。

例如下面代码就没有执行错误检查。

int * pnSomeInteger = (int*) malloc(sizeof(int));
*pnSomeInteger = 5;

如果malloc无法分配足够的内存,那么它会返回NULL,但是代码没有检查这种可能,想当然认为分配一定会非常成功。

地址空间中空指针赋值区是禁止访问的,所以会引发内存访问违规并导致进程被终止。


13.2.2 用户模式分区

这一分区时进程地址空间的驻地,可用的地址空间和用户模式分区的大小取决于CPU体系。

图片.png

32位下,默认为2GB大小。打开/3GB开关时,可扩大到3GB空间,但同时内核空间缩小为1GB)

exe/Dll 都载入到这一区域,每个进程都可能将这些dll载入到这一分区内的不同地址。系统同时会把该进程可以访问的所有内存映射文件映射到这一分区


1.在x86 Windows下得到更大的用户模式分区

修改Windows启动配置数据(Boot Configuration Data,BCD)

运行B.CDEdit.exe

bcdedit /set IncreaseUserVa 3072,就可以为进程保留3GB用户模式地址空间,IncreaseUserVa可接受的最小值为2048,即默认的2GB。取消的话:bcdedit /deletevalue IncreaseUserVa。

为了让应用程序可以访问2GB以上的地址空间(特别地,早期的应用程序是不允许这样做的)。在链接时,可以打开/LARGEADDRESSAWARE链接开关。


2.在64位windows下得到2GB用户模式分区

1.因大量使用32位指针开发程序,仅重新编译程序会导致指针截断错误和不正确的内存访问。但可以让应用程序在地址空间沙箱(Address space sandbox)中运行,这也是默认的情况,系统能够保证高33位都为0的64地址截断为32位,这样进程可用的地址空间就被限制在最底部的2GB中。

2.当运行64位应用程序时,默认下系统会保留用户模式地址空间中在2GB以下(即最底部的2GB),这就是所谓的地址空间沙箱。这空间对于大多数的应用程序来说是足够的。

3.为了让64位应用程序能够访问整个用户地址空间,必须指定/LARGEADDRESSAWARE链接器开关来链接应用程序。


13.2.3 64KB禁入分区

64K禁入区的作用很明显是隔离了用户和内核空间;防止用户程序跨越到内核空间中。与内核交互会涉及到SSDT表,后续破解驱动保护部分会讲到。

因为分配粒度目前暂定 64KB 为了保持兼容。所以null和禁入区是最小64KB。分配粒度64KB是为了考虑以后cpu发展到64KB页面大小的时候可以和现在的程序兼容。


13.2.4 内核模式分区

  • 操作系统代码的驻地。
  • 线程调度、内存管理、文件系统支持、网络支持以及设备驱动程序相关代码都载入到该分区。
  • 应用程序试图读取和写入这一分区内存中的内存地址会引发访问违规。


13.3 地址空间中区域


预定地址空间中的一块区域

预订:为了使用可用地址空间,我们一般通过VirtualAlloc来分配其中的区域,分配区域的操作被称为 预定

释放:VirtualFree

分配粒度:当前所有CPU平台都使用相同的分配粒度 大小为64KB

系统页面:

起始地址: 分配粒度(一般是64K)的整数倍。(分配粒度,会随不同CPU平台而有所不同,目前都是64KB,即系统会把分配请求取整到64KB的整数倍)

预定空间的区域的大小: 系统页面大小的整数倍(x86和x64的页面大小为4KB,I64系统使用的页面大小为8KB),即例如当应用程序预定一块大小为10KB的地址空间,系统自动取为页面大小整数倍,然后预定取整的大小区域。( x862/x643系统中会预定一块12KB,IA-64系统4会预定16KB.)


13.4 给区域调拨物理储存器


13.4.1 调拨/撤销 物理存储器

调拨物理存储器:为了使用所预定的地址空间区域,我们还必须分配物理存储器,并将存储器映射到所预定的区域。

撤销调拨物理存储器:当程序不在需要访问所预定区域中已调拨的物理存储器时,应该释放物理存储器。


13.5 物理存储器和页交换文件


13.5.1 页交换文件

页交换文件:磁盘上的文件一般被称为页交换文件,其中包含虚拟内存,可供任何进程使用。

注: 一般Windows页交换文件名为pagefile.sys


作用

页交换文件以一种透明的方式增大了应用程序的可用内存的总量,例如一台机器装备了1GB的内存,硬盘上还有1GB的页交换文件,那么应用程序会认为可用内存的总量为2GB


13.5.2 虚拟地址转为物理存储器地址

当一个线程试图访问所属进程地址空间中的一块数据时,可能出现两种情况:


  • 访问数据就在内存中。在这时,CPU会先把虚拟内存地址映射到内存的物理地址,接下来就可以访问内存数据了。
  • 访问数据不在内存中,而是在交换内存中的某处。这种情况下,这次不成功的访问称为页面错误, 具体情况如下图:
  • 图片.png

注:系统需要在内存和页交换文件之间复制页面的频率越高,硬盘颠簸得越厉害,系统运行得也越缓慢。

(颠簸是指操作系统把所有时间都花在页面文件和内存之间交换数据上,导致没有时间运行程序)


13.5.3 不在页交换文件中维护得物理存储器

内存映射文件:把硬盘上的文件映像(如一个.exe或DLL文件)作为虚拟内存的一部分(注意是文件映射,而不是页交换文件)。


当用户要执行一个可执行文件时,系统会打开应用程序对应的.exe文件并计算出应用程序的代码和数据的大小。然后预订一块地址空间,并注明与该区域相关的存储场所是.exe文件本身,而不是页交换文件。这样做可以将.exe的实际内容用作程序预订的地址空间区域,不仅载入程序速度快,而且可避免将为每个程序文件的代码和数据复制到页交换文件而造成页交换文件过于庞大和臃肿。


13.6 页面保护属性


我们可以给每个已分配物理存储页指定不同的页面保护属性。

图片.png


13.6.1 写时复制

写时复制属性以及作用

写时复制属性:PAGE_WRITECOPY

写时复制属性的作用:节省内存和页交换文件的使用


   Windows提供一种机制,允许两个或两个以上的进程共享一块存储器。如10个记事本进程正在运行,所有的进程会共享应用程序的代码页和数据页。当只读或执行时,这种共享存储页的方式极大地提高了性能。但当某个实例写入一个存储页时,就要求给共享的存储页指定写时复制属性,这样在映射地址空间时,系统会计算有多少可写页面,然后从页交换文件中分配空间来容纳这些可写页面,在程序真正写入的时候,就存储在页交换文件中。


写入共享页面时,系统介入的操作

系统在内存中找到一个空闲页面。注意,该空闲页的后备页面来自页交换文件。它是系统最初将模块映射到进程的地址空间时分配的。由于是第1次映射时就分配了所需的页交换文件空间。所以这步不可能失败。


系统将要修改的页面内容复制到第1步找到的空闲页面,然后给这些空闲页面指定PAGE_READWRITE或PAGE_EXECUTE_READWRITE属性。(注意系统不会修改原始页面的保护属性和数据)


然后系统更新进程的页面表,这样,原来的虚拟地址现在就对应到内存中一个新的页面了。以后进程就可以访问它自己的副本了。


在预订地址空间或提交物理存储器时

在预订地址空间或提交物理存储器时,不能使用PAGE_WRITECOPY或PAGE_EXECUTE_WRITECOPY保护属性,否则VirtualAlloc会失败,GetLastError将返回ERROR_INVALID_PARAMETER。


13.6.2 一些特殊得访问保护属性标志

图片.png


13.7 实例分析



13.8 数据对齐的重要性


数据对齐:数据地址%数据大小 = 0时 数据是对齐的


数据没有对齐的两种情况

  1. CPU会引发一个异常
  2. CPU会通过多次访问已对齐的内存,来取得整个错位数据即未对齐数据。


x86CPU对错位数据的处理

   EFLAGS寄存器的AC标志位(AlignmentCheck)为0时,CPU自动执行必要的操作来访问错位数据)


   AC标志位为1时,如果试图访问错位数据,CPU会触发INT 17H中断。


(对于x86版本的Windows从来不变为AC标志位(即永远为0),因此x86处理器上运行应用程序,绝对不会发生数据错位的异常,


AMDx86-64CPU 对错位数据处理

会得到和x86的相同的结果,这是因为在默认情况下CPU处理了数据错位的错误。


IA-64CPU对错位数据处理

IA-64CPU处理器不能自己处理数据错误的错误,因此当访问错位数据时,会抛出一个EXECPTION_DATATYPE_MISALIGNMENT异常,我们通用SetErrorMode函数并传SEM_NOALIGNMENTFAULTEXCEPT 标志,让系统自动修正数据错位的错误。(注意传入这个标志会影响进程中所有的线程,而且这个错误模式会被进程的子进程继承)


编译器对错位数据的处理

IA-64版本的VC/C++编译器支持__unaligned关键字

如DWORD dw = (__unaligned DWORD)pvDataBuffer;

x86版本的VC/C++编译器:不支持__nnaligned关键字,所以这个关键字在x86版本的编译器下会报错。

鉴于编译器对__unaligned有不同的支持,为代码的通用性,建议用UNALIGNED和UNLIGNED64宏来替换__unaligned。


总结


1.exabytes,百亿亿字节


2.x86:从1978年来的8086处理器开始,就已经出现了x86架构CPU,即32位处理器。


3.x86-64:又简称为x64,最初开发为1999年AMD,为了扩充IA64。当时的x86-64架构诞生颇有时代意义,处理器的发展遇到了瓶颈,内存寻址空间由于受到32位CPU的限制而只能最大到约4G。于是就有了x86-64。后被INTEL所采用。


4.ia- 64:其实ia64的历史早于x86-64x,最初由INTEL和惠普联合推出。由于ia-64不与32位兼容,所以没有受到重视。直到INTEL采用了 AMD的x86-64架构,才正式的批量生产。而后为了日益扩张的计算需求,INTEL重新将IA-64拿出来,发布了安腾系列服务器CPU。




相关文章
|
1月前
|
存储 Linux 编译器
Linux C/C++ 编程 内存管理之道:探寻编程世界中的思维乐趣
Linux C/C++ 编程 内存管理之道:探寻编程世界中的思维乐趣
50 0
|
3月前
|
缓存 网络协议 数据安全/隐私保护
[运维笔记] - (命令).Windows server常用网络相关命令总结
[运维笔记] - (命令).Windows server常用网络相关命令总结
191 0
|
19天前
|
缓存 安全 Java
Java并发编程进阶:深入理解Java内存模型
【4月更文挑战第6天】Java内存模型(JMM)是多线程编程的关键,定义了线程间共享变量读写的规则,确保数据一致性和可见性。主要包括原子性、可见性和有序性三大特性。Happens-Before原则规定操作顺序,内存屏障和锁则保障这些原则的实施。理解JMM和相关机制对于编写线程安全、高性能的Java并发程序至关重要。
|
3月前
|
存储 缓存 Java
释放C盘空间:释放Windows休眠文件和关闭虚拟内存
在 Windows 11 专业版中,可以通过以下步骤来释放休眠文件(Hibernate File),以释放磁盘空间。休眠文件是系统休眠(Hibernate)功能所需要的文件,它保存了系统的当前状态,以便在休眠状态下恢复。如果你不使用休眠功能,如果因为C盘空间不足,可以考虑释放这个文件来腾出磁盘空间。
3646 0
|
1月前
|
存储 编解码 Linux
深入解析Linux C/C++ 编程中的内存泄漏问题
深入解析Linux C/C++ 编程中的内存泄漏问题
112 1
|
1月前
|
消息中间件 Linux
Linux进程间通信(IPC)教程 Linux共享内存介绍:介绍POSIX共享内存的基本概念、用途和编程实践
Linux进程间通信(IPC)教程 Linux共享内存介绍:介绍POSIX共享内存的基本概念、用途和编程实践
24 2
|
2月前
|
存储 编译器 程序员
近4w字吐血整理!只要你认真看完【C++编程核心知识】分分钟吊打面试官(包含:内存、函数、引用、类与对象、文件操作)
近4w字吐血整理!只要你认真看完【C++编程核心知识】分分钟吊打面试官(包含:内存、函数、引用、类与对象、文件操作)
106 0
|
2月前
|
程序员 编译器 C++
C++核心编程一:内存分区模型(持续更新)
C++核心编程一:内存分区模型(持续更新)
|
2月前
|
Windows
火山中文编程 -- 第一个windows程序
火山中文编程 -- 第一个windows程序
12 0
|
2月前
|
编译器 API Windows
windows编程基础
windows编程基础
13 0

热门文章

最新文章