为什么通用程序通常从0x8000开始?

43

我对引导程序和系统软件并不陌生,但是我不知道一般程序为什么要从地址0x8000开始运行。我已经知道在普通的C/C++程序中地址0x8000被用作起始地址。

引导程序对于一般程序的最小大小是否需要占用0x8000?或者引导程序应该分配到32KB的最小ROM块大小?或者有其他的原因吗?

我想从历史或逻辑的角度以及从虚拟地址的角度了解这个问题。


非常感谢你花时间来帮助回答这个问题。 为了更清晰地提出问题,这个问题与虚拟地址有关,而不是与物理地址有关。

从物理内存地址的角度,我基本上同意R的观点。

不说一个多样化的特定系统,例如Linux(即使在Android中),一般的RTOS(Nucleus和其他特别是ARM链接器部分)都使用地址0x8000作为一般程序的起始地址。如crt_begin.o、crt.o等位于0x0的文件存在于此区域,并由加载程序进行处理。

因此,我猜测,如果引导程序位于BootROM中进行启动(冷启动),则一般程序的引导程序的最小大小考虑到块大小应为32KB。

嗯,但我不确定……


17
你在这里指的是哪个系统? - Jerry Coffin
2
我没有任何可靠的来源,但我可以做出一个合格的猜测。历史上许多处理器,特别是8位处理器,都有一个称为“零页”的特性,这意味着地址0x00 - 0xFF处的内存单元具有指令支持,以实现更快的执行。我相信这是由Motorola在旧的MCU(如6800)上引入的,因为他们有内存映射的I/O寄存器。 - Lundin
2
因此,您希望内存的第一个区域被RAM单元或特殊寄存器占用。接下来的地址空间部分应该是相同性质的:RAM和/或寄存器。这将占用大量kb,可能高达0x6000或类似的值。然后我假设将ROM(程序存储器)放在偶数地址0x8000处是方便的。我相当确定这个问题的答案可以在早期的Motorola处理器设计中找到。 - Lundin
这可能是相关的:http://en.wikipedia.org/wiki/ELF_binary。也许大小要与先前的格式兼容? - Lethargy
5个回答

19

通常情况下,在除了最小的嵌入式系统之外的所有平台ABI设计中,设计者都希望避免使用最低地址,以便可以捕获空指针解引用。具有几KB的永远无效的地址可以在将空指针与数组或结构成员偏移量一起解引用时为您提供额外的安全性,例如:null_ptr->some_member


3
我不相信这是原因。我曾经使用多个嵌入式系统,其中地址0是有效的可寻址内存,而同时非易失性存储器从8000开始。 - Lundin
5
特别是由于地址0x8000在C语言和空指针变得流行之前就已经存在,甚至可能在C语言发明之前就存在了? - Lundin
5
如果malloc()失败,它会返回一个空指针。这里没有要求空指针必须是地址0(只要一个等于0的整数常量可以隐式地转换成它,不管它是什么)。而系统把中断处理程序放在哪个位置取决于硬件——Intel始终将它们放在最开始位置,但其他系统则有所不同。 - James Kanze
2
@JamesKanze:当然,但是“嵌入式系统”中,“HW可以接受”的是没有VM的简单系统(如果您有VM,则不会在进程空间中映射(void*)NULL)。在这些系统上,您非常接近金属,因此希望NULL为比特0。是的,中断处理程序就是一个例子;其他CPU在地址0处具有内存映射的I/O寄存器。 - MSalters
2
@MSalters 对于虚拟机而言,最简单的解决方案是不映射地址0,并将0用作空指针常量。在没有虚拟机的情况下,系统使用各种各样的方法,肯定有一些系统中空指针不是地址0。 - James Kanze
显示剩余6条评论

6
它取决于系统,不同系统上的程序在不同地址上启动。在Unix下,通常(或者可能甚至被Posix要求)使用地址0作为空指针,并且不映射虚拟内存的第一页,以便对空指针进行解引用将导致段错误。我怀疑其他使用地址0作为空指针的系统也会表现类似(但它们保留多少可能会有所不同)。 (从历史上看,通常将第一页映射为只读,并用零填充,使空指针的行为类似于空字符串,即指向“”。然而,这已经过去了约25年。 )我预计,即使在今天,一些嵌入式系统也会将程序加载到地址0处。

3

这是一些主观判断,至少在Linux上由链接程序来决定。总体思路是为了预留一些空间以捕获NULL指针异常。为了防止内核空间的NULL指针解引用执行任意用户代码而进入内核模式,Linux会阻止您映射到内存的最底部。

/proc/sys/vm/mmap_min_addr控制您可以映射的最低地址(如果您愿意,可以将其更改为0并在0处映射页面)。

在Linux上,可以在/proc中查看内存映射,例如:

genwitt ~> cat /proc/self/maps 
00400000-0040c000 可读可执行 00000000 08:01 354804                             /bin/cat
0060b000-0060c000 只读         0000b000 08:01 354804                             /bin/cat
0060c000-0060d000 可读可写     0000c000 08:01 354804                             /bin/cat
01dda000-01dfb000 可读可写     00000000 00:00 0                                  [堆]
7f5b25913000-7f5b25a97000 可读可执行 00000000 08:01 435953                     /lib64/libc-2.14.1.so
7f5b25a97000-7f5b25c97000 不可读不可写 00184000 08:01 435953                     /lib64/libc-2.14.1.so
7f5b25c97000-7f5b25c9b000 只读         00184000 08:01 435953                     /lib64/libc-2.14.1.so
7f5b25c9b000-7f5b25c9c000 可读可写     00188000 08:01 435953                     /lib64/libc-2.14.1.so
7f5b25c9c000-7f5b25ca1000 可读可写     00000000 00:00 0 
7f5b25ca1000-7f5b25cc2000 可读可执行 00000000 08:01 436061                     /lib64/ld-2.14.1.so
7f5b25cd2000-7f5b25e97000 只读         00000000 08:01 126248                     /usr/lib64/locale/locale-archive
7f5b25e97000-7f5b25e9a000 可读可写     00000000 00:00 0 
7f5b25ec0000-7f5b25ec1000 可读可写     00000000 00:00 0 
7f5b25ec1000-7f5b25ec2000 只读         00020000 08:01 436061                     /lib64/ld-2.14.1.so
7f5b25ec2000-7f5b25ec3000 可读可写     00021000 08:01 436061                     /lib64/ld-2.14.1.so
7f5b25ec3000-7f5b25ec4000 可读可写     00000000 00:00 0 
7fff18c37000-7fff18c58000 可读可写     00000000 00:00 0                          [栈]
7fff18d0c000-7fff18d0d000 可读可执行 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 可读可执行 00000000 00:00 0                  [vsyscall]

2
我怀疑在很多情况下,前32K被保留用于监视器代码/ram使用。在很多8051评估板上,默认为所有应用程序设置为0x1000或0x2000,具体取决于驻留监视器的不同(一些监视器还可作为调试器使用)。
32K可能是您的u-boot等加载程序的空间。

2

我认为答案更多地与中断处理有关。 中断处理程序地址是在硬件中设置的。 在Intel 8086中,有一个直接的翻译表,用于中断处理程序代码和相应的中断处理例程。 可能,这是通过一些组合电路完成的,因此,为了保持向前兼容性,将它们放在内存的开始而不是结尾可能更明智,以防止每次更改。 因此,执行起始地址将位于内存的另一端。 此外,必须在该块中包含足够的代码来加载内存段程序和跳转指令以从该代码地址切换到执行代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接