windows 内存管理总结

前言

关于windows内存管理 官方文档

一、内存体系结构

1. 虚拟地址空间


1.1 概述

windows 每个进程都有自己的地址空间。对于32位进程,32位指针可以表示的地址空间为4G。对于64位进程,64位指针可以表示的地址空间为16EB(非常大)。这个地址空间是虚拟的,也就意味着具有如下特性:

  • 每个进程只能访问属于自己的内存。
  • 这个地址空间仅仅是内存地址区间,不是物理存储器,并不代表正真的有这么大的内存可用。(实际上,进程的内存可使用量会有诸多限制,比如你机器只有2G物理内存)

1.2 虚拟地址空间的分区

windows每个进程的地址空间被划分成许多的分区,分别为 空指针赋值区、用户模式分区、64K禁入分区、内核模式分区。

  • 空指针赋值区
    这一个区域是专门帮助程序猿定位空指针异常的。任何情况下,操作系统都不会分配这个区域的虚拟内存。如果访问这个区域的内存,程序会引发异常。(C++ 程序猿正在抱怨,是谁访问指针不判空 …)

  • 用户模式分区
    进程的大部分数据都保存在这一分区(包括可执行代码、程序运行中的数据等)。每个进程都无法以任何方式访问其他进程的这一区域,这保证了整个系统和应用程序的稳定性。

    注 :经常在各种文章中看到 32位程序只有2G的可用空间,其实就是指的32位进程用户模式分区大小为2G;其实x86windows是可以通过设置,获得更大用户模式分区。

  • 内核模式分区
    这一部分分区存放这操作系统的代码和数据,包括线程调度、内存管理、文件系统、驱动等。这一分区的东西为所有进程共有的,但是这一分区的代码和数据都是保护起来的,应用程序是没法直接读取写入这一分区的地址。(单向共享,内核可以访问用户进程,反之则不行)。

注:用户模式分区、内核模式分区,其实不仅仅的内存划分,更深入涉及OS用户模式与内核模式相关的知识。可参看 User mode and kernel mode

2. 虚拟内存


2.1 物理存储器

物理存储器除了内存外,还可以是硬盘。OS可以把硬盘空间变的像内存一样。硬盘上的文件一般被称为页交换文件

进程访问内存
数据是否在内存中
将虚拟地址映射成硬件地址
访问
是否在交换页中
载入内存
异常

2.2 页面保护属性

我们可以为分配的物理存储页指定不同的访问属性,具体可参看 Memory Protection
CPU在执行指令的时候是不会区分所谓的代码和数据的,只要Jump 到一个地址,CPU就会把这个地址的数据作为指令无脑执行,这会导致很多问题,尤其是恶意的代码攻击。Windows 引入 DEP(数据执行保护)机制,内存中不同意义的数据,赋予不同的保护属性。比如如果执行的内存不是 PAGE_EXECUTE_* 系列的属性,则会抛出异常。

2.3 写时复制

windows支持共享存储器,比如开10个vscode,这些进程会共享代码页和数据页。那么问题来了,如果有一个这些进程完全共享存储,势必会混乱不堪,A改B的数据,B改C的数据 …。所以windows引入写时复制机制。被标记为 可写属性的内存页 如果真的被进程写入数据,那么系统会自动将这块页 做一个副本,程序修改的其实时副本,这样既不会进程将相互影响,也可以最大程度的共享资源。

二、操控使用内存

windows 提供了3种机制来操控内存。

机制特点
虚拟内存最适合管理大型的对象结构,这是一套很底层管理的方法
内存文件映射主要用来实现进程将的数据共享,这里数据包括进程代码段
用来管理小的对象,分配方便,但是速度会略慢

1. 虚拟内存


windows 提供了一组用来操控虚拟内存的函数,我们可以通过他们直接预定地址空间区域,给区域调拨物理存储器,设置保护属性等。

具体的API可以参看 官方虚拟内存接口文档

2. 内存映射文件


内存映射文件与虚拟内存的不同之处在于给预定的地址空间区域调拨的物理存储器。
虚拟内存调拨的物理存储器是内存或者来至系统的页交换文件
内存映射文件调拨的是磁盘上的一个文件
内存映射文件主要用在一下3个方面,即加载可执行文件、访问数据文件、多进程共享数据。

2.1 映射到内存的可执行文件

很多资料都提到 DLL 技术的优点,其中有一条是可以节省内存、促进资源共享,那么为什么会有这种特性呢?究其原因是windows支持内存映射文件。

系统加载可执行文件/DLL的流程如下:

查找可执行文件路径
创建进程内核对象
创建私有地址空间
预定地址空间来容纳exe文件
标注预定区域的物理存储为exe文件
访问exe中的特定段加载DLL
递归加载DLL 流程类似EXE的加载

如果一个应用程序已经在运行,那么当我们在运行一个该应用程序的实例时,系统只是打开了另一个内存映射试图。即多个实例将共享内存中的代码和数据。
下面具体讨论一下同一个exe/dll 多个实例的情况

2.1.1 静态数据不共享。

这里的不共享指但一个程序在修改数据时,操作系统会利用写时复制特性,保证多个实例不会互相篡改数据。数据页的保护属性默认是可读可写的。

2.1.2 在多个实例将实现数据共享

有时,我们会有多份实例需要共享一些变量的需求,比如实现禁止程序多开。
这里又涉及到exe/dll文件的格式的相关知识。简单的说文件映像被分为多个段,比如 .text .data .bss等。每个段其实都有自己的属性:可读、可写、可执行、可共享。只要把要共享的数据放在可共享的段中即可实现数据多实例共享。VC++中可以使用如下示例实现:

// 新增加一个段
#pragma data_seg("TestData")
bool g_isRunning = false
#pragma data_seg()

// 添加链接命令 设置段的属性   R read; W write; E execute; 
#pragma comment(linker, "/SECTION:TestData,RWS") 

2.2 映射到内存的数据文件

windows能够使我们将大型的数据文件映射到进程的地址空间,这样对数据流操作就容易了。
使用内存映射文件的步骤如下:

创建/打开文件内核对象
创建一个文件映射内核对象, 告诉系统我们打算如何使用访问这个文件
告诉系统把文件的部分也可以是全部映射到地址空间
使用
取消映射
关闭内核映射对象
关闭文件对象

对应的API 可参看 https://docs.microsoft.com/en-us/windows/win32/memory/using-file-mapping

2.3 多进程共享数据

内存映射文件,对于同一个文件映射对象,系统会保证其各个视图数据的一致性。通过 句柄继承、命名、句柄复制等技术,可以实现多个进程中使用同一个文件映射对象。所以很容易实现多进程共享数据。
https://docs.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory

3. 堆


堆是对虚拟内存的封装,可以使我们专注于解决实际业务问题,而不是考虑分配粒度、页面边界等问题。在系统内部,堆就是一块预定好的地址区域,随着不断的分配堆内存,堆管理器不断的调拨物理存储设备,释放堆内存也就释放已经调拨的存储器。

三 、扩展

1. C/C++运行时的内存管理与虚拟内存

C标准库malloc/free 是最经典的内存管理接口。这些接口和上文中的虚拟内存有什么关系呢?
虚拟内存是OS提供的机制,C/C++是编写可在OS上运行的程序的语言。对应的C/C++运行时中的内存管理库是基于OS提供的机制实现的,即 malloc 底层使用 windwos 中的堆接口 来实现 堆内存的分配释放。

2. 多个进程使用同一个DLL库,如果有一个进程是恶意进程,修改了DLL库的代码段,那么会影响其他加载了该库的进程吗?

结论是,不会影响。系统加载DLL库 默认代码段是 不可读写可执行的 保护属性,是多个进程共享的。但是根据规则如果要修改DLL 的代码段,必须要修改保护属性为可读写。根据写时复制机制,可写的保护属性在写入时会发生复制,所以修改的只是当前进程中的副本。不会影响其他进程。

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值