Windows 内存机制说明

1.内存分布概述

       每个进程都有自己的虚拟地址空间,对于32位进程来说,这个地址空间的大小为4GB,这是因为32位指针可以表示从0x00000000到0xFFFFFFFF之间的任意一值。对于64位进程来说,由于64位指针可以表示从0x00000000’00000000到0xFFFFFFFF’FFFFFFFF之间的任一值,因此这个地址空间大小为16EB。因为每个进程都有自己专用的地址空间,当进程中的各线程运行时,它们只能访问属于该进程的内存。线程既看不到属于其他进程的内存,也无法访问它们。

2.虚拟地址空间分区

       应用程序虽然有这么大的地址空间可用,但是这只是虚拟地址空间,不是物理存储器。这个地址空间只不过是一个内存的地址区间。

       每个进虚拟地址空间被划分为许多分区(partion)。由于地址空间的分区依赖于操作系统的底层实现。因此会随着Windows内核的不同而略有变化。

分区

x86 32

Windows

3G用户模式下的

x86 32位Windows

x64 64位Windows

IA-64 64位的 Windows

空指针

赋值分区

0x00000000

0xFFFFFFFF

0x00000000

0xFFFFFFFF

0x00000000’00000000

0x00000000’0000FFFF

0x00000000’00000000

0x00000000’0000FFFF

用户模式

分区

0x00010000

0x7FFEFFFF

0x00010000

0xBFFEFFFF

0x00000000’00010000

0x000007FF’FFFFFFFF

0x00000000’00010000

0x000006FB’FFFEFFFF

64K禁入

分区

0x7FFF0000

0x7FFFFFFF

0xBFFF0000

0xBFFFFFFF

0x000007FF’FFFF0000

0x000007FF’FFFFFFFF

0x000006FB’FFFF0000

0x000006FB’FFFFFFFF

内核模式

分区

0x80000000

0xFFFFFFFF

0xC0000000

0xFFFFFFFF

0x00000800’00000000

0xFFFFFFFF’FFFFFFFF

0x000006FC’00000000

0xFFFFFFFF’FFFFFFFF

      从上表中可能看到,32位Winows内核和64位Windows内核的分区基本一致,唯一的不同在于分区的大小和分区的位置。

内存使用

 

2.1.空指针赋值分区

       这一分区是进程地址空间中从0x00000000到0x0000FFFF的闭区间,保留该分区的目的是为了帮助程序捕获对于空指针的赋值。没有任何办法可以让我们分配到位于这一地址空间的虚拟内存,即使是使用Win32的应用程序编程接口(appliction programming interface,通常简称API)也不例外。如果进程中的线程试图读取或写入于这一分区内的内存地址,就会引发访问违规。

        如果malloc无法分配足够的内在,那么它会返回NULL。地址空间中的这一分区是禁止访问的,所以会引发内存访问违规并导致进程被终止。这一特性可以帮助开发人员发现应用程序中的缺陷。

2.2.用户模式分区

        这一分区是进程地址空间的驻地,可用的地址空间和用户模式分区的大小取决于CPU体系结构。进程无法通过指针来读取、写入或以任何方式,访问驻留在这一分区中的其他进程的数据。对所有应用程序来说,进程的大部分数据都保存在这一分区。由于每个进程都有自己的数据分区。因此一个应用程序破另一个应用程序的可能性就非常小,从而使得整个系统更加坚固。

       在早期版本的Windows中,Microsoft不允许用户程序访问2GB以上的地址空间,为了让此类应用程序即使在用模式分区大于2GB的环境下仍能正常运行,Microsoft提供代了一种模式来增大用户模式分区,最多不超过3GB。

       当前系统即将运行一个应用程序时,它会检查应用程序在链接时是否使用了/LARGEADDRESSAWARE链接器开关。如果是,则相应于应用程序在声明它会充分利用大用户模式地址空间,而不会对内在地址进行任何不当的操作。反之,如果应用程序在链接时没有使用/LARGEADDRESSAWARE开关,那么操作系统会保留用户模式分区中2GB以上到内核模式开始处的整个部分。

2.2.1.程序内存使用分布

栈

可参考https://en.wikipedia.org/wiki/Data_segment#Program_memory

2.2.1.1.Text

【原文】

The code segment, also known as a text segment or simply as text, is where a portion of an object file or the corresponding section of the program's virtual address space that contains executable instructions is stored and is generally read-only and fixed size.

       代码段,通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读。

      在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

       代码段,也称为文本段,或简称为文本,是指存储有可执行指令的对象文件或程序的虚拟地址空间的相应部分的一部分,并且通常是只读的和固定大小的。

2.2.1.2.Data

【原文】

The .data segment contains any global or static variables which have a pre-defined value and can be modified. That is any variables that are not defined within a function (and thus can be accessed from anywhere) or are defined in a function but are defined as static so they retain their address across subsequent calls. Examples, in C, include:

int val = 3;

char string[] = "Hello World";

The values for these variables are initially stored within the read-only memory (typically within .text) and are copied into the .data segment during the start-up routine of the program.

       数据段包含任何具有预定义值并可被修改的全局变量或静态变量。这是任何函数中未定义的变量(因此可以从任何地方访问)或在函数中定义,但被定义为静态的,因此它们在随后的调用中保留它们的地址。C中的例子包括:

int val = 3;

char string[] = "Hello World";

       这些变量的值最初存储在只读存储器(通常在.text中),并在程序的启动例程期间复制到.DATA段。

2.2.1.3.BSS

【原文】

The BSS segment, also known as uninitialized data, is usually adjacent to the data segment. The BSS segment contains all global variables and static variables that are initialized to zero or do not have explicit initialization in source code. For instance, a variable defined as static int i; would be contained in the BSS segment.

        BBS段,也称为未初始化数据段,通常与数据段相邻,包含初始化为或在源码中未进行显式初始化的全局变量和静态变量。例如,定义为静态整型变量i将被包含在BSS段中。

2.2.1.4.Heap

【原文】

The heap area commonly begins at the end of the .bss and .data segments and grows to larger addresses from there. The heap area is managed by malloc, calloc, realloc, and free, which may use the brk and sbrk system calls to adjust its size (note that the use of brk/sbrk and a single "heap area" is not required to fulfill the contract of malloc/calloc/realloc/free; they may also be implemented using mmap/munmap to reserve/unreserve potentially non-contiguous regions of virtual memory into the process' virtual address space). The heap area is shared by all threads, shared libraries, and dynamically loaded modules in a process.

        堆区域通常在.BSS和.DATA段的末尾开始,并从那里扩展到更大的地址。堆区域由Maloc、CaloC、ReLoLc和空闲管理,可以使用BRK和SBRK系统调用来调整其大小(注意使用BRK/SBRK和单个堆区)不需要履行MalC/CaloC/ReAlOLC/FILE的合同;也可以使用MMAP/MunMMAP来实现。将虚拟内存的潜在非相邻区域保留/不保留到进程的“虚拟地址空间”中。堆区域由进程中的所有线程、共享库和动态加载模块共享。

2.2.1.5.Stack

【原文】

Main article: Call stack

The stack area contains the program stack, a LIFO structure, typically located in the higher parts of memory. A "stack pointer" register tracks the top of the stack; it is adjusted each time a value is "pushed" onto the stack. The set of values pushed for one function call is termed a "stack frame". A stack frame consists at minimum of a return address. Automatic variables are also allocated on the stack.

The stack area traditionally adjoined the heap area and they grew towards each other; when the stack pointer met the heap pointer, free memory was exhausted. With large address spaces and virtual memory techniques they tend to be placed more freely, but they still typically grow in a converging direction. On the standard PC x86 architecture the stack grows toward address zero, meaning that more recent items, deeper in the call chain, are at numerically lower addresses and closer to the heap. On some other architectures it grows the opposite direction.

 主要文章:调用堆栈

          堆栈区域包含程序栈,即LIFO结构,通常位于内存的较高部分。一个“堆栈指针”寄存器跟踪堆栈的顶部;每当一个值被“推”到堆栈时,它就被调整。一个函数调用所推的值集称为“堆栈框架”。堆栈帧包括返回地址的最小值。自动变量也被分配到堆栈上。

       堆栈区域传统上毗邻堆区域,它们彼此生长;当堆栈指针遇到堆指针时,空闲内存被耗尽。随着大的地址空间和虚拟内存技术,它们倾向于更自由地放置,但它们仍然通常在收敛的方向上生长。在标准的PC x86架构上,堆栈向地址零增长,这意味着在调用链中较近的项在数值较低的地址上更接近堆。在其他一些架构上,它生长了相反的方向。

2.3.内核模式分区

        这一分区是操作系统代码的驻地,与线程调度、内存管理、文件系统支持、网络支持以及设备驱动程序相关的代码都载入到该分区。在这一分区内的任何东西为所有进程共有。虽然这一分区就在每个进程中用户模式分区的上方,但该分区中的所有代码和数据都被完全保护起来。如果一个应用程序试图读取或写入这一分区中的内存地址,会引发违规。在默认情况下,访问违规会导致系统先向用户显示一个消息框,然后结束应用程序。

3.堆栈段说明
3.1.栈(Stack)
       当系统创建线程时,会为线程栈预订一块空间区域(每个线程都有自己的栈),并给区域调拨一些物理存储器。默认情况下,系统会预订1MB的地址空间并调拨两个页面的存储器。但是,在构建应用程序时开发人员可以通过两种方法来改变该默认值,一种方法是使用Microsoft C++编译器的/F选项,另一种方法是使用Microsoft C++链接器的/STACK选项。在构建应用程序时,链接器会把想要的栈的大小写入到.exe或.dll文件的PE文件头中。当前系统创线栈的时候,会根据PE文件头中的大小来预订地址空间的区域。但是在调用CreateThread或_beginthreadex函数时,开发人员也可以另外指定需要在一开始就调拨的存储器数量。这两个函数都有一个参数,可以用来指定一开始要调拨给线程栈的地址空间区域的存储器的大小。如果该参数设为0,那么系统会使用PE文件头中指定的大小,所使用的都是默认值(即区域大小为1MB),每次调拨一个存储页面。
3.2.堆(Heap)
       堆非常适用分配大量的小型数据。堆是用来管理链表和树的最佳方式。堆的优点是它能让我们专心解决手头上的问题,而不必理会分配粒度和页面边界这类事情。堆的缺点是分配和释放内存块的速度比其他方式慢,而且也无法对物理存储器的调拨和摊销进行直接控制。
       进程初始化时候,系统会在进程的地址空间中创建一个堆,这个堆被称为进程的默认堆(Default heep)。在默认情况下,这个堆的地址空间区域的大小是1MB。但是系统可以增大进程的默认堆,使它大于1MB。我们也可以在创建应用程序的时候用/HEAP链接器开关来改变默认区域大小。由于动态链接库(.DLL)没有与之关联的堆,因此在创建DLL的时候不应使用/HEAP开关。

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Windows内核通过虚拟内存管理来实现内存的分配和管理。其中,拟内存换页是一种重要的机制,用于将内存中的数据从物理内存中换出到磁盘上的页面文件,以释放物理内存空间供其他程序使用。下面是一个例子来说明Windows内核如何防止虚拟内存换页的: 假设有一个运行在Windows系统上的应用程序,它需要频繁地访问某个数据结构,但是由于物理内存有限,该数据结构可能被换出到页面文件中。为了防止频繁的虚拟内存换页,Windows内核采用了以下策略: 1. 预读取:Windows内核会根据应用程序的访问模式和历史访问模式来预测应用程序可能会访问的页面,并提前将这些页面加载到物理内存中。这样可以减少虚拟内存换页的次数。 2. 页面优先级:Windows内核为每个页面设置了优先级,根据页面的重要性和访问频率来确定优先级。优先级高的页面会更倾向于留在物理内存中,而优先级低的页面则更容易被换出到页面文件中。 3. 页面置换算法:Windows内核使用了一些高效的页面置换算法,如最近最少使用(LRU)算法和时钟算法等。这些算法会根据页面的访问情况来选择最适合被换出的页面,以最大程度地减少对物理内存的占用。 4. 内存压缩:当物理内存不足时,Windows内核还可以通过内存压缩技术来减少内存的占用。内存压缩会将一部分内存中的数据进行压缩,从而释放更多的物理内存空间。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值