执行时,程序将从虚拟地址0x80482c0开始运行。此地址不指向我们的main()
过程,而是指向由链接器创建的名为_start
的过程。
到目前为止,我的谷歌研究只是让我得到了一些(含糊的)历史猜测:
有民间传说,0x08048000曾经是由加利福尼亚州圣克鲁斯市的一个团体颁布的* NIX到i386的端口上的STACK_TOP(也就是说,堆栈从接近0x08048000向下增长到0)。这是因为128MB的RAM很昂贵,4GB的RAM是不可想象的。
任何人都可以确认/否认这个吗?
答案 0 :(得分:33)
正如Mads指出的那样,为了通过空指针捕获大多数访问,类Unix系统倾向于使地址为零的页面“未映射”。因此,访问会立即触发CPU异常,换句话说就是段错误。这比让应用程序变得流氓要好得多。但是,异常向量表可以位于任何地址,至少在x86处理器上(有一个特殊的寄存器,加载了lidt
操作码)。
起点地址是描述内存布局方式的一组约定的一部分。链接器在生成可执行二进制文件时必须知道这些约定,因此它们不太可能更改。基本上,对于Linux来说,内存布局约定是从90年代早期的Linux的第一个版本继承而来的。流程必须能够访问多个区域:
brk()
和sbrk()
系统调用而增加。mmap()
系统调用必须有一些空间,包括共享库加载。如今,malloc()
所在的堆由mmap()
调用支持,这些调用在内核认为合适的任何地址获取内存块。但是在较老的时代,Linux就像以前的类Unix系统一样,它的堆需要一个不间断的大块区域,这可能会增加地址。因此,无论是什么约定,它都必须将代码和堆栈填充到低地址,并在给定点之后将每个地址空间块分配给堆。
但是还有堆栈,它通常很小但在某些情况下可能会非常显着。堆栈增长,当堆栈已满时,我们真的希望流程可以预测崩溃而不是覆盖一些数据。因此,堆栈必须有一个广泛的区域,在该区域的低端,是一个未映射的页面。瞧!在地址零处有一个未映射的页面,用于捕获空指针解引用。因此,定义了堆栈将获得第一个128 MB的地址空间,第一页除外。这意味着代码必须在128 MB之后,地址类似于0x080xxxxx。
迈克尔指出,“丢失”128 MB的地址空间并不是什么大问题,因为地址空间非常大,可以实际使用。那时,Linux内核将单个进程的地址空间限制为1 GB,硬件允许的最大值为4 GB,这并不是一个大问题。
答案 1 :(得分:6)
为什么不从地址0x0开始?至少有两个原因:
关于入口点_start
vs main
:
如果链接C运行时(C标准库),则库包装名为main
的函数,因此它可以在调用main
之前初始化环境。在Linux上,这些是应用程序的 argc 和 argv 参数, env 变量,可能还有一些同步原语和锁。它还确保从主要传递返回状态代码,并调用_exit
函数,这将终止进程。