链接过程

 

 

ELF头:以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。

.text:已编译程序的机器代码。

.rodata:只读数据,比如printf语句中的格式串。

.data:已初始化的全局C变量

.bss:未初始化的全局C变量。在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。

.symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。

.rel.text:.text的重定位信息。

.rel.data:.data的重定位信息。

.debug:一个调试符号表

.line:原始C源程序中的行号和.text节中机器指令之间的映射。

.strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串序列。

段头部表:用于描述可执行文件连续的片映射到连续的存储器段的映射关系。

.int:.int用于定义了一个小函数,叫做_init,程序的初始化代码会调用它。

目标文件有三种形式。

可重定位目标文件:包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。

可执行目标文件:包含二进制代码和数据,其形式可以被直接拷贝到存储器并执行。

共享目标文件:一种特殊类型的可重定位目标文件,可以在加载或运行时被动态地加载到存储器并链接。

 

链接器的两大任务:符号解析和重定位。

符号解析:将目标文件中的每个全局符号都绑定到一个唯一的定义。

链接器如何解析多重定义的全局符号?

函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。根据强弱符号的定义,linux链接器使用下面的规则来处理多重定义的符号:

规则1:不允许有多个强符号。

规则2:如果有一个强符号和多个弱符号,那么选择强符号。

规则3:如果有多个弱符号,那么从这些弱符号中任意选择一个。

 

重定位:确定每个符号的最终存储器地址,并修改对那些目标的引用。

重定位由两步组成:

重定位节和符号定义:在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。

重定位节中的符号引用:在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。

 

静态库

所有的编译系统都提供一种机制,将所有相关模块打包成一个单独的文件,称为静态库。它可以用做链接器的输入。当链接器构造一个输出的可执行文件时,它只拷贝静态库里被应用程序引用的目标模块。

静态库以一种称为存档的特殊文件格式存放在磁盘中,存档文件名由后缀.a标识。

.o相当于windows里的.obj文件,多个.o合在一起生成.a文件。

在符号解析的阶段,链接器从左到右按照它们在编译器驱动程序命令行上出现的相同顺序来扫描可重定位目标文件和存档文件。所以,关于库的一般准则是将它们放在命令行的结尾。

 

共享库

也称为共享目标。在linux系统中通常用.so后缀来表示的。在windows平台上,称为dll。

共享库是以两种不同的方式来“共享”的。首先,在任何给定的文件系统中,对于一个库只有一个.so文件。所有引用该库的可执行目标文件共享这个.so文件中的代码和数据,而不是像静态库的内容那样被拷贝和嵌入到引用它们的可执行的文件中。其次,在存储器中,一个共享库的.text节的一个副本可以被不同的正在运行的进程共享。

 

静态库是必须要链接到执行的文件中去的,而动态库是不需要链接到最后的可执行文件中的,也就是说,对于最后的执行文件而言,你是否删除静态库无所谓,但是一旦删除了动态库,最后的执行文件就运行不了。

和静态链接库相比,动态链接库可以共享内存资源,这样可以减少内存消耗。另外,动态链接是需要经过操作系统加载器的帮助才能被普通执行文件发现的,所以动态链接库可以减少链接的次数,有了这个特点,我们就不难发现为什么,很多软件软件的补丁其实都是以动态库发布的。


.ko文件是Linux内核模块文件的后缀名,用来在Linux系统启动时加载内核模块。

Linux有许多功能是通过模块的方式,在需要时才载入kernel,如此可使kernel较为精简,进而提高效率,这类可载入的模块,通常是设备驱动程序。

insmod  //载入模块   

rmmod   //移除模块

lsmod    //显示模块

mknod Name {b|c} major minor

mknod建立一个目录项和一个特殊文件(将设备驱动节点编入项目的文件系统)的对应索引节点。Name是设备名称,b标志这个文件是面向块的设备,c标志这个特殊文件是面向字符的设备,最后两个参数指定主设备的数目。


int ioctrl(int handle, int cmd, ...)

功能:提供了一种获得内核驱动设备信息和向设备发送控制参数的手段,用于向设备发控制和配置命令,或调用了驱动提供的功能。



要运行可执行目标文件p,可以在linux外壳的命令行中输入它的名字:  linux> ./p

因为p不是一个内置的外壳命令,所以外壳会认为p是一个可执行目标文件,通过调用驻留在存储器中的操作系统代码(称为加载器)来运行它。任何linux程序都可以通过调用execve函数来调用加载器。加载器将可执行目标文件中的代码和数据从磁盘拷贝到存储器中,然后通过跳转到程序的第一条指令或入口点来运行该程序。这个将程序拷贝到存储器并运行的过程叫做加载。

 

int execve(const char* filename,const char *argv[],const char *envp[])

execve函数加载并运行可执行目标filename,且带参数列表argv和环境变量列表envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。

在execve加载了filename之后,它调用启动代码来设置栈,并将控制传递给新程序的主函数,该主函数有如下形式的原型。

int main(int argc,char **argv,char **envp)

envp:指向envp[]数组

argv:指向argv[]数组

argc:给出argv[]中非空指针的数量

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值