26

执行时,程序将从虚拟地址 0x80482c0 开始运行。这个地址并不指向我们的main()过程,而是指向一个_start由链接器创建的过程。

到目前为止,我的谷歌研究只是让我做出了一些(模糊的)历史推测,比如:

民间传说 0x08048000 曾经是 STACK_TOP(即堆栈从 0x08048000 附近向下增长到 0)在 *NIX 到 i386 的端口上,这是由加利福尼亚州圣克鲁斯的一个团体颁布的。当时 128MB 的 RAM 很昂贵,而 4GB 的 RAM 是不可想象的。

任何人都可以确认/否认这一点吗?

4

2 回答 2

37

正如 Mads 指出的那样,为了通过空指针捕获大多数访问,类 Unix 系统倾向于使地址为零的页面“未映射”。因此,访问会立即触发 CPU 异常,即段错误。这比让应用程序流氓要好得多。但是,异常向量表可以位于任何地址,至少在 x86 处理器上是这样(有一个特殊的寄存器用于加载lidt操作码)。

起始地址是一组描述内存布局方式的约定的一部分。链接器在生成可执行二进制文件时,必须知道这些约定,因此它们不太可能改变。基本上,对于 Linux,内存布局约定是从 90 年代初的 Linux 的第一个版本继承而来的。一个进程必须能够访问多个区域:

  • 代码必须在包含起点的范围内。
  • 一定有栈。
  • 必须有一个堆,其限制随着系统调用brk()sbrk()系统调用而增加。
  • 系统调用必须有一些空间mmap(),包括共享库加载。

如今,堆所在的位置malloc()mmap()调用支持,这些调用在内核认为合适的任何地址获取内存块。但在过去,Linux 就像以前的类 Unix 系统一样,它的堆在一个不间断的块中需要一个很大的区域,这可能会随着地址的增加而增长。因此,无论约定是什么,它都必须将代码和堆栈填充到低地址,并将给定点之后的地址空间的每一块都分配给堆。

但也有堆栈,它通常很小,但在某些情况下可能会显着增长。堆栈向下增长,当堆栈已满时,我们真的希望进程可以预见地崩溃,而不是覆盖某些数据。所以堆栈必须有一个广阔的区域,在该区域的低端,一个未映射的页面。瞧!在地址 0 处有一个未映射的页面,用于捕获空指针取消引用。因此,定义堆栈将获得前 128 MB 的地址空间,但第一页除外。这意味着代码必须在这 128 MB 之后,位于类似于 0x080xxxxx 的地址处。

正如迈克尔指出的那样,“丢失” 128 MB 的地址空间没什么大不了的,因为地址空间对于实际使用的内容来说非常大。当时,Linux内核将单个进程的地址空间限制为1 GB,超过硬件允许的最大4 GB,这被认为不是什么大问题。

于 2010-02-02T21:13:07.967 回答
7

为什么不从地址 0x0 开始?这至少有两个原因:

  • 因为地址 0 被称为 NULL 指针,编程语言使用它来完善检查指针。如果要在那里执行代码,则不能为此使用地址值。
  • 地址 0 处的实际内容通常(但不总是)是异常向量表,因此在非特权模式下无法访问。请查阅您的特定架构的文档。

至于入口点_startvs main:如果您链接到 C 运行时(C 标准库),该库会包装名为 的函数main,因此它可以在调用之前初始化环境main。在 Linux 上,这些是应用程序的argcargv参数、环境变量,可能还有一些同步原语和锁。它还确保从 main 返回传递状态代码,并调用_exit终止进程的函数。

于 2010-02-02T20:49:15.580 回答