1.程序从源代码到运行的整个流程如图1:
2.静态链接是将程序所依赖的类库与程序本身代码链接在一起形成了一个很大的可执行文件。这个可执行文件包含了运行所需的所有程序代码,不需要再依赖外部类库。但是这样做的好处在于减轻了符号(变量与函数)重定位的难度,加快了装载速度,因为它们被揉进一个文件,所以其在文件中的相对位置是不会改变的。当该可执行文件装载进进程空间时,那么只需获得装载首地址,即可确定所有符号(变量与函数)的地址。但是坏处就在于因为所有库被放进了一个文件,使得可执行文件十分庞大,一个简单的helloworld程序在通过静态链接后都有8M之多,因为可能只是使用到了一个库中的一个函数,就需要将整个库的程序放进可执行文件中,造成空间的浪费。而且如果多个程序依赖同一个库A,那么很可能造成系统内存中存放了多个库A的副本,因为静态链接的情况下,库A已经放进了可执行文件了,不需要和其他进程共享库A。如图2,在静态链接的情况下,三个进程都依赖了库A,所以在内存中有三份A的拷贝。
图2
3.由于静态链接十分浪费空间,所以考虑对于多个程序依赖的同一个库A,能否让多个程序共享同一个库A的程序副本(地址无关问题)(如图3)?
图3 动态链接就是为了解决这个问题,节省存储空间所提出的。对于共享库,因为每个程序的操作不一样,所以数据段是不能进行共享的,但是共享库的代码段时可以进行共享的。所以库的程序指令部分共享,但数据部分需要每个程序自己存储一份数据副本。但在共享库A后,程序只有在装入运行时才能确定库A在内存中的地址,为了解决这个问题,动态链接使用了一个中间表项
.got
。.got
中记录了程序模块所依赖的外部符号(函数与变量)的地址,显然这个表项的值只有在装载时才能填充。而在链接生成目标文件或者可执行文件时,为指令提供符号在.got
中的地址。4.延迟绑定:可以看到当使用动态链接时,需要在装入才能确定符号真实地址,所以在装入时要做很多重定位操作,这会严重影响程序启动的效率,考虑到有的函数在程序执行到结束时都可能不会调用,所以没必要在一开始就将这些函数的地址确定。为了实现在第一次调用时才绑定函数地址,又增加了一个
.got.plt
表,当程序调用函数时,不是直接到.got
表中找函数的地址,而是到.got.plt
表中找到对应表项,执行此表项指向的函数。5.显示运行时链接:同样考虑到有的库可能程序运行结束都不可能用到或者很少用到,所以能否在程序需要用到这些库的时候再进行装载(如插件、驱动),为了实现这种方式,动态连接器开放了一些API来让程序控制装载和卸载指定的模块。
6.ABI:ABI是二进制程序层面的应用接口,它规定了更加底层的东西,比如printf函数,对于API来说它只关心函数的参数类型返回类型,而对于ABI来说它关心的是这个函数参数入栈顺序、内存分布、符号解析等。
7.变量存储:静态变量(包括局部静态变量)和全局变量是存储与单独的section(.data)中的,而局部变量存储在进程的栈中,在编译时,编译器就会将局部变量转换为相应的出栈入栈操作。
8.程序启动时,CUP的控制权: 控制权
网友评论