3

在现代操作系统中,内存可作为抽象资源使用。进程暴露于虚拟地址空间(独立于所有其他进程的地址空间),并且存在将任何虚拟地址映射到某个实际物理地址的完整机制。我的疑问是:

  • 如果每个进程都有自己的地址空间,那么它应该可以自由访问同一地址空间中的任何地址。因此,除了 .data、.bss、.text 等权限受限部分之外,应该可以自由更改任何地址的值。但这通常会产生分段错误,为什么?

  • 为了获取动态内存,我们需要做一个malloc。如果整个虚拟空间都可供一个进程使用,那么为什么它不能直接访问它呢?

  • 程序的不同运行会导致变量的不同地址(在堆栈和堆上)。当每次运行的环境相同时,为什么会这样?它不会影响可供使用的可寻址内存量吗?(它与地址空间随机化有关吗?)

  • 一些关于内存分配的链接(例如在堆中)。

不同地方的可用数据非常混乱,因为他们谈论的是古代和现代,通常没有区分它们。如果有人能在记住现代系统的同时澄清疑虑,比如 Linux,那将会很有帮助。

谢谢。

4

1 回答 1

5

从技术上讲,操作系统能够在访问时分配任何内存页面,但它不应该或不能分配的重要原因是:

不同的内存区域有不同的用途。

  • 代码。它可以被读取和执行,但不应该被写入。
  • 文字(字符串,常量数组)。该内存是只读的,应该是。
  • 堆。它可以读写,但不能执行。
  • 线程堆栈。两个线程没有理由访问彼此的堆栈,因此操作系统可能会禁止这样做。此外,可以在胎面结束时重新分配胎面堆栈。
  • 内存映射文件。对该区域的任何更改都应影响特定文件。如果文件打开以供读取,则可能会在进程之间共享相同的内存页面,因为它是只读的。
  • 内核空间。通常应用程序不应该(或不能)访问该区域 - 只有内核代码可以。它基本上是内核的暂存空间,并且在进程之间共享。网络缓冲区可能驻留在那里,因此无论数据包何时到达,它始终可用于写入。
  • ...

操作系统可能会假设所有无法识别的内存访问都是试图分配更多的堆空间,但是:

  • 如果应用程序从用户代码中触及内核内存,则必须将其杀死。在 32 位 Windows 上,所有高于1<<31(最高位设置)或高于3<<30(最高两位设置)的内存都是内核内存。您不应假设任何未分配的内存区域位于用户空间中。
  • 如果应用程序考虑使用内存区域但没有告诉操作系统,则操作系统可能会为该内存分配其他内容(操作系统:当然,您的文件位于0x12341234;应用程序:但我我的数据存储在那里)。您可以通过触摸阵列的末尾来告诉操作系统(无论如何这都不可靠),但是调用操作系统函数更容易。函数调用是“给我 10MB 的堆”,而不是“给我 10MB 的堆”,这只是一个好0x12345678主意
  • 如果应用程序通过使用它来分配内存,那么它通常根本不会取消分配。这可能是有问题的,因为操作系统仍然必须保留未使用的页面(但 Java 虚拟机也不会取消分配,所以嘿)。

程序的不同运行导致变量的不同地址

这称为内存布局随机化,并与适当的权限(堆栈空间不可执行)一起使用,以使缓冲区溢出攻击更加困难。您仍然可以终止应用程序,但不能执行任意代码。

一些关于内存分配的链接(例如在堆中)。

你的意思是,分配器使用什么算法?最简单的算法是始终在最快的可用位置分配并从每个内存块链接到下一个内存块,如果它是空闲块或已用块,则存储标志。更高级的算法总是以两倍或某个固定大小的倍数的大小分配块,以防止内存碎片(大量小的空闲块)或链接不同结构中的块以更快地找到足够大小的空闲块。

一个更简单的方法是永远不要取消分配,只指向第一个(也是唯一的)空闲块并保持它的大小。如果剩余空间太小,请将其扔掉并向操作系统索要新空间。

内存分配器没有什么神奇之处。他们所做的只是:

  • 向操作系统询问大区域和
  • 将其划分为更小的块
  • 没有
    • 浪费太多空间或
    • 花费太长时间。

无论如何,关于内存分配的维基百科文章是http://en.wikipedia.org/wiki/Memory_management

一种有趣的算法称为“(二进制)伙伴块”。它拥有几个大小为 2 次方的池,并将它们递归地拆分为更小的区域。然后,每个区域要么完全分配、完全免费,要么分成两个并非完全免费的区域(伙伴)。如果它被拆分,则一个字节足以容纳该块中最大空闲块的大小。

于 2012-11-23T05:37:27.900 回答