链接器和加载器,以及编译器和汇编器与体系结构的细节密切相关,这包括硬件体系结构和操作系统对目标计算机在体系结构方面的约定。
硬件体系结构的两个方面影响到连接器:程序寻址和指令格式。
应用程序二进制接口
ABI包含了应用程序在这个系统下运行时必须遵守的编程约定,ABI包含一系列的系统调用和使用这些系统调用的方法,以及可以使用的内存地址和使用机器寄存器的规定。
ABI要求每个程序包含一个该程序各例程使用的所有静态数据的地址表。
内存地址
直接顺序和对齐,大端小端,无须争论,小端指低位在低字节,对齐是必须的。
过程调用和可寻址性
对于局部和全局静态数据,编译器可以为一个例程引用的所有静态变量创建一个指针
表。如果某个寄存器存有指向这个表的指针,那么例程可以通过使用表指针寄存器将对象在
表中的指针读取出来,加载到另一个使用表指针寄存器作为基址的寄存器中(其实就是说相对偏移就够了。),并将第二个寄
存器做为基址寄存器来寻址任何想要访问的静态目标。因此,关键技巧是表的地址存入到第
一个寄存器中。在 SPARC 上,例程可以通过带有立即操作数的一系列指令来加载表地址,同
时在 SPARC 或者370上例程可以通过一系列子例程调用指令将程序计数器(保存当前指令地
址的寄存器)加载到一个基址寄存器,虽然后面我们还会讨论这种方法在对待库代码时会遇
到问题。一个更好的解决方法是将提取表指针的工作交给例程的调用者,因为调用者已经加
载了自己的表指针,并可以从自己的表中获取被调用例程的表的指针。
图 3 所示为一个典型的例程调用序列。Rf 是框架指针,Rt是表指针,Rx 是临时寄存器。
调用者将自己的表指针保存到自己的栈框架中,然后将被调用例程的地址和它的指针表地址
载入到寄存器中,再进行调用。被调用的例程可以通过Rt中的表指针找到它需要的所有数
据,包括它随后要调用的例程的地址和表指针。(多重调用者说)
我画了一个表来解释这段话。
我完全搞不懂这段话的意思,假如有数据段和bss段的话,为什么还需要这种东西。
一个进程应该只有一个全局的静态变量表。外界当然不让访问。
分页和虚拟内存
程序地址空间:unix模型。
映射文件:古老的交换模式是把交换出来的页面保存在独立于文件系统名字空间的单独匿名磁盘空间上的。
换页发明不久,就让换页系统和文件系统统一到一块了。
(在现在恐怕没人想到还有这样的历史。)
共享库和程序:
共享库是相当简单的,只需要将可执行文件映射到每一个程序的地址空间即可。
不可重定位代码和只读的数据,以RO的方式映射。
可写的数据以cow方式映射。
操作系统还可以让所有映射到该文件的进程之间共享RO 和尚未被写的C
OW 数据对应的物理页框
我依然不明白共享库怎么带来复杂度了,假设共享库放在某一物理内存处。
进程当然是想把它映射到虚拟空间的什么地方就映射到什么地方,问题何在?
位置无关代码,第9章和第10章再看吧。
嵌入式体系结构:
怪不得要分散加载呢,主要是内存布局问题,不过有智慧的人,还是思考更高深的问题吧。