一个编译链接的过程解读

来源:互联网 发布:大数据高并发系统架构 编辑:程序博客网 时间:2024/03/29 21:05

一个编译链接的过程详解

2008-06-19 15:43

Operating Systems A Modern Perspective笔记之编译链接

先扫盲:


1. 操作系统存储管理模块的设计与所基于的硬件环境息息相关。

2. 编译:绑定变量到一个0开始的相对地址空间中。某些变量(如C中的局部变量)存储在运行栈中,其地址要等到在链接时与其他模块绑定到一起才能确定。对于处 在不同的可重定位模块中的函数地址,编译的过程也不能确定。故自动注释每个这种对外部地址的访问,待链接时能在代码中设置正确的地址。
链接:转换程序所生成的各种数据段和可写的重定位模块绑定在一起,形成一个数据段,然后链接程序通过改变所有地址的基址,来重定位指令中的地址。
加载:加载时地址再次调整。一旦加载器确定了程序映象要加载的实际位置,就会把程序中的地址与物理存储位置绑定在一起。

3. 动态地址重定位小结:

3.1 采用malloc的方式:实际上是将未分配的堆空间的部分地址帮定到了进程地址空间。当然进程也可以有自己的堆空间,malloc的实现也可以采用在进程堆空间中划出空间的方法。

3.2 硬件重定位的方式:在CPU中包含三个重定位寄存器,将代码,栈,数据段分别作为分离的重定位模块来进行管理。在每个函数的入口点处,初始化代码加载代码 和数据段寄存器,使它们指向对应可重定位模块的绝对映象分区(初始化代码使用的地址由链接程序提供)。所有的跨模块跳转会导致代码段寄存器的改变。所有跨 模块的数据访问会导致数据段寄存器的改变。
在这种模式下:PC=代码段寄存器+段中相对地址
每个可重定位寄存器可以设置一个与之伴随的界限寄存器,其中放存储段长。
重 定位寄存器与界限寄存器实际上可以置于MPU core与Memory Controller的物理接连之间。在相对地址从MPU core送出的时候,重定位寄存器和界限寄存器可以并行的进行逻辑计算,若越界,则由界限寄存器相关的硬件逻辑产生一个中断发往Interrupt Controller。

4. 页式虚存

4.1 页表记录逻辑页号与实际页号的对应关系。MMU是连接在CPU地址线与存储控制器之间的地址转换芯片。经MMU转换后的地址直接送往MAR(Memory Address Register)。
页表存放在内存中由OS core管理。也可以采用多级页表,这样查找时的访存次数会不同。在进程切换时需要向MMU重新初始化进程的页表地址。
可以在MPU系统设计中采用TLB(Translationlookaside buffer),作为页表项的一个cache,减小页表平均查找时间。

4.2 页的载入:可采取在进程被创建时为其分配固定大小内存空间(固定的页帧数)的方法。在缺页时FIFO、LRU、LFU等调度策略都可以采用。

实例篇——静态地址重定位时一个编译链接加载的简单示例

有代码段:

static int gVar;
extern put_record(int iNum);

int proc_a( int arg ){

    ...

    gVar = 7;

    put_record(gVar);

    ...

}}

1.gVar是一个模块内局部变量(因为static关键字),编译器为其在proc_a同一模块内分配内存空间。假设给其分配到相对位置0x36.并在模块内符号表里记录符号的内存地址。

2.外部应用和定义组成了一张External reference table.编译器在链接时会对ERT中的每一个表项,在全局范围内查找其绝对位置(通过查找别的模块的符号表),将变量的内存地址写入符号表中。

3.对于在当前模块外的put_record()函数入口地址,编译器会在编译生成的中间代码中(.lib中),临时的将变量符号名填入需要用到变量的地方,等待在链接时替换为其绝对地址。

通过编译过程,生成的中间代码如下:
相对地址        代码

0000

....

0008        entry     proc_a

....

0036       [space for gVar variable]

....

0220       load       R1,=7

0224       store      R1,0036

0228       push       0036

0232       call       'put_record'

....

0400      [external definition table]

0404      put_record 0232

     

....

0600      [optional symbol table]

0604      gVar 0036

0630      proc_a 0008

....

0799     [last location in the module]

    链接时将各个可重定位模块绑定到一起。首先用户声明的前后顺序,将各个模块连接起来。在一个个模块绑定的时候,会根据模块被连接到的绝对基地址,对模块内被引用了的地址进行重新运算。

    在将各个模块连接到一起后,链接程序搜索整个模块中的外部地址引用表[external definition table],将相应位置的临时符号(如这里的'put_record')替换为该符号的实际地址。

    在链接过程以后,external definition table失去了作用,被删去,而符号表被汇集成一个全局符号表,可以有选择的保存在程序的某些位置(通常是尾部)。

上面的模块经过链接后,得到下面的代码:

相对地址         生成的代码

0000

....

1008          extry proc_a

....

1036          [space for gVar variable]

....          

1220       load       R1,=7

1224       store      R1,1036

1228       push       1036

1232       call       2334

1399       (end of proc_a)

           (other modules)

2334        entry put_record

....

2670       (optional symbol table)

....        gVar 1036

             proc_a 1008

            put_record 2334

2999        (last location in the module)

通过上面的过程,便形成了一个可以被加载器加载的二进制文件(.out,.bin等等【不清楚.exe算不算,所以没列出来】)。当然,二进制文件也可以通过无损压缩,形成容量小的多的镜像文件(如.srec)

在实际加载程序时(如用户要求操作系统加载文件系统中的.out文件,或者用户双击了一个.exe文件),操作系统的加载程序会将二进制代码拷贝到内存中,并在运行前做最后一次地址重定位。

假设程序被加载到从内存地址4000开始放置。则程序中所有要访问到的程序、数据位置都要在加载前调整。调整后的最终内存中程序如下:

0000         (别的程序,这个地址多半被映射到ROM上去了)

4000        (other module)

5008          extry proc_a

....

5036          [space for gVar variable]

....          

5220       load       R1,=7

5224       store      R1,5036

5228       push       5036

5232       call       5334

5399       (end of proc_a)

           (other modules)

6334        entry put_record

....

6670       (optional symbol table)

....        gVar 5036

             proc_a 5008

            put_record 6334

6999        (last location in the module)   

最后来小结一下:

1.符号表的作用是为了debug的时候能让debugger把代码和当前内存位置对上号。当然如果没有符号表代码尺寸会减小很多。所以final release里面肯定不能带符号表。

2.外部引用符号表主要是在编译阶段使用,在链接确定外部符号地址后,这个表项就可以删除了。

3.试想一下这种情况:一个模块里面的外部符号引用太多了(extern关键字用的太多了),导致编译的时候外部符号表溢出!

一个哥们以前在中兴工作的时候就遇到过这样的情况。

4.通过写这篇读书笔记,感觉自己对编译链接过程的理解也加深了不少。 

   当然我们不会去做编译器,有机会做操作系统内存管理部分的人也不会太多。不过每天都接触到的compile and link,了解深一点总是没有错的,有些时候面对一大堆的link error,想一下背后的原理,也就觉得了然了。

 

0 0
原创粉丝点击