7

我对这些概念很陌生,但我想问大家一个我认为非常基本的问题,但我很困惑,所以我问了。问题是......操作系统如何确定进程的大小?我先说清楚,假设我写了一个 C 程序,我想知道它要占用多少内存,我该如何确定呢?其次,我知道一个进程有很多部分,如代码部分、数据部分、BSS。现在这些的大小是预先确定的吗?其次是如何确定 Stack 和 heap 的大小。在计算进程的总大小时,堆栈和堆的大小是否也很重要。

我们再次说,当我们加载程序时,会为进程分配一个地址空间(我猜这是由基址和限制寄存器完成并由 MMU 控制),并且当进程试图访问不在它的内存位置时地址空间我们得到分段错误。进程如何访问不在其地址空间中的内存。根据我的理解,当发生一些缓冲区溢出时,地址就会被破坏。现在,当进程想要访问损坏的位置时,我们会遇到分段错误。是否有任何其他违反地址的方式。

第三,为什么堆栈向下增长并向上堆。这个过程是否与所有操作系统相同。它如何影响性能。为什么我们不能以其他方式拥有它?

请纠正我,如果我的任何陈述有误。

谢谢索拉博

4

4 回答 4

3

当一个进程启动时,它会获得自己的虚拟地址空间。虚拟地址空间的大小取决于您的操作系统。通常,32 位进程获得 4 GiB(4 giga 二进制)地址,64 位进程获得 18 EiB(18 exa 二进制)地址。

您不能以任何方式访问未映射到虚拟地址空间的任何内容,因为根据定义,任何未映射到虚拟地址空间的内容都没有适合您的地址。您可能会尝试访问当前未映射到任何内容的虚拟地址空间区域,在这种情况下,您会遇到段错误异常。

在任何给定时间,并非所有地址空间都映射到某物。也不是所有的都可以映射(可以映射多少取决于处理器和操作系统)。在当前一代的英特尔处理器上,最多可以映射 256 TiB 的地址空间。请注意,操作系统可以进一步限制这一点。例如,对于 32 位进程(最多有 4 个 GiB 地址),Windows 默认为系统保留 2 GiB,为应用程序保留 2 GiB(但有一种方法可以为系统保留 1 GiB,为应用程序保留 3 GiB)。

应用程序运行时使用了多少地址空间以及映射了多少变化。操作系统特定的工具可以让您监控当前分配的内存和虚拟地址空间对于正在运行的应用程序是什么。

代码段、数据段、BSS 等是指链接器创建的可执行文件的不同区域的术语。通常,代码与静态不可变数据分开,静态不可变数据与静态分配但可变的数据分开。堆栈和堆与上述所有内容分开。它们的大小由编译器和链接器计算。请注意,每个二进制文件都有自己的部分,因此任何动态链接的库都将分别映射到地址空间中,每个库都将其自己的部分映射到某个地方。然而,堆和堆栈不是二进制映像的一部分,通常每个进程只有一个堆栈和一个堆。

堆栈的大小(至少是初始堆栈)通常是固定的。编译器和/或链接器通常有一些标志,可用于设置运行时所需的堆栈大小。堆栈通常“向后增长”,因为这就是处理器堆栈指令的工作方式。让堆栈沿一个方向增长,而其余的则沿另一个方向增长,这样在您希望两者都不受限制但又不知道每个可以增长多少的情况下,组织内存变得更容易。

通常,堆是指进程启动时未预先分配的任何东西。在最低级别,有几个与堆管理相关的逻辑操作(并非所有操作都像我在此处描述的那样在所有操作系统中实现)。

虽然地址空间是固定的,但一些操作系统会跟踪它的哪些部分当前被进程回收。即使不是这种情况,流程本身也需要对其进行跟踪。因此,最低级别的操作是实际决定要使用地址空间的某个区域。

第二个低级操作是指示操作系统将该区域映射到某物。这个一般可以

  • 一些不可交换的内存

  • 可交换并映射到系统交换文件的内存

  • 可交换并映射到其他文件的内存

  • 可交换并以只读模式映射到其他文件的内存

  • 另一个虚拟地址区域映射到的相同映射

  • 另一个虚拟地址区域映射到的相同映射,但处于只读模式

  • 另一个虚拟地址区域映射到的相同映射,但在写时复制模式下,复制的数据映射到默认交换文件

我可能忘记了其他组合,但这些是主要的。

当然,使用的总空间实际上取决于您如何定义它。当前使用的 RAM 与当前映射的地址空间不同。但正如我在上面所写的,依赖于操作系统的工具应该可以让您了解当前正在发生的事情。

于 2012-09-19T09:09:46.003 回答
2

这些部分由可执行文件预先确定。

除此之外,可能还有任何动态链接库。虽然 DLL 的代码和常量数据应该在使用它的多个进程之间共享并且不会被多次计算,但它的进程特定的非常量数据应该在每个进程中都被考虑在内。

此外,进程中可以动态分配内存。

此外,如果进程中有多个线程,则每个线程都有自己的堆栈。

更重要的是,在进程本身和代表它的内核中将有每个线程、每个进程和每个库的数据结构(线程本地存储、命令行参数、各种资源的句柄、用于这些资源的结构)资源等等等等)。

如果不知道一切是如何实现的,就很难准确地计算出整个进程的大小。不过,您可能会得到一个合理的估计。

WrtAccording to my understanding when some buffer overflows happens then the address gets corrupted.这不一定是真的。首先,地址是什么?这取决于缓冲区附近的内存中发生了什么。如果有地址,它可以在缓冲区溢出期间被覆盖。但是,如果附近有另一个缓冲区包含您的照片,则图片的像素可能会被覆盖。

当您尝试访问您没有必要权限的内存时(例如,已映射或以其他方式存在于进程地址空间中的内核部分),您可能会遇到分段或页面错误。或者它可以是只读位置。或者该位置不能映射到物理内存。

在不知道我们所讨论的性能的情况下,很难判断堆栈和堆的位置和布局将如何影响性能。你可以推测,但推测可能会被证明是错误的。

顺便说一句,您真的应该考虑针对单独的问题就 SO 提出单独的问题。

于 2012-09-19T08:47:18.377 回答
1

“进程如何访问不在其地址空间中的内存?”

考虑到内存保护,这是不可能的。但它可能会被尝试。考虑随机指针或超出缓冲区的访问。如果您将任何指针递增足够长的时间,它几乎肯定会进入未映射的地址范围。简单的例子:

 char *p = "some string";

 while (*p++ != 256)  /* Always true. Keeps incrementing p until segfault. */
     ;

轻描淡写地说,像这样的简单错误并非闻所未闻。

于 2012-09-19T08:55:03.787 回答
0

我可以回答问题#2 和#3。

答案#2

在 C 中使用指针时,您实际上是在使用一个数值,该数值被解释为内存地址(现代操作系统上的逻辑地址,请参见脚注)。您可以随意修改此地址。如果该值指向的地址不在您的地址空间中,则您有分段错误。

例如考虑这种情况:您的操作系统为您的进程提供从 0x01000 到 0x09000 的地址范围。然后

int * ptr = 0x01000;
printf("%d", ptr[0]); // * prints 4 bytes (sizeof(int) bytes) of your address space
int * ptr = 0x09100;
printf("%d", ptr[0]); // * You are accessing out of your space: segfault

正如您所指出的,段错误的主要原因是使用指向 NULL 的指针(主要是 0x00 地址,但取决于实现)或使用损坏的地址。

注意,在 linux i386 上,base 和 limit 寄存器并没有像你想象的那样使用。它们不是每个进程的限制,但它们指向两种段:用户空间或内核空间。

答案#3

堆栈增长取决于硬件而不是操作系统。在 i386 汇编指令上,如 push 和 pop 使堆栈相对于堆栈相关的寄存器向下增长。例如,当您执行 push 时堆栈指针会自动减小,而当您执行 pop 时堆栈指针会增加。操作系统无法处理它。

脚注

在现代操作系统中,进程使用所谓的逻辑地址。该地址由操作系统映射到物理地址。要注意这一点,请自己编译这个简单的程序:

#include <stdio.h>

int main()
{
    int a = 10;
    printf("%p\n", &a);
    return 0;
}

如果您多次(甚至同时)运行该程序,您会看到,即使对于不同的实例,打印出相同的地址。当然这不是真正的内存地址,而是一个逻辑地址,在需要的时候会映射到物理地址。

于 2012-09-19T08:59:41.973 回答