程序员的自我修养总结(一)_程序员的自我修养总结

2020-02-28 员工个人工作总结 下载本文

程序员的自我修养总结(一)由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“程序员的自我修养总结”。

1、通过学习《程序员的自我修养》,重新对栈,堆,静态存储区的认识。

解答:

局部变量存放在栈中,全局变量和静态数据存放在静态存储区,在二进制代码中,显示在数据段。

对于一个进程的内存空间而言,可以在逻辑上分为3个部分:代码区,静态数据区和动态数据区。动态数据区一般就是堆(heap)栈(stack)。

堆和栈是两种不同的动态数据区,栈是一种线性结构,而堆是一种链式结构。

栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出;

在Windows下,栈是乡下生长的,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在Windows下,栈的大小是2M(也有的说是1M,总之是一个编译时确定的常数,栈的大小由编译器设定),如果申请的空间超过栈的剩余空间时,将提示overflow。

堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

堆是向高地址扩展的数据结构,是不连续的内存区域。链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

栈是由系统自动分配,速度较快。但程序员无法控制。

堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。

栈由编译器自动分配释放

堆由程序员分配释放

静态存储区由系统释放

2.对ELF文件装载的总结

ELF文件装载的步骤如下:

首先在用户层,bash进程会调用fork()系统调用创建一个新的进程,然后新的进程调用execve()系统调用执行制定的ELF文件,原先的bash进程继续返回等待刚才启动的新进程结束。

在进入execve()系统调用后,Linux内核就开始进行真正的装载工作。在内核中,execve()系统调用相应的入口是sys_execve(),sys_execve()进行一些参数的检查复制之后,调用do_execve()。

do_execve()会首先查找被执行的文件,如果文件找到,则读取文件的前128个字节(主要是判断文件类型)。当do_execve()读取了这128个字节的文件头部后,然后调用search_binary_handle()去搜索和匹配合适的可执行文件,search_binary_handle()会通过判断文件头部的魔数确定文件的格式,并调用相应的装载处理过程,比如,如果是elf文件,则调用load_elf_binary();这个装载函数的主要步骤是:

(1)检查ELF可执行文件格式的有效性,比如魔数、程序头表中段(Segment)的数量;

(2)寻找动态链接的“.interp”段,设置动态链接器路径(与动态链接有关)

(3)根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码、数据、只读数据。

(4)初始化ELF进程环境,比如进程启动时EDX寄存器的地址应该是DT_FINI的地址

(5)将系统调用的返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,对于静态链接的ELF可执行文件,这个程序入口点就是ELF文件的文件头中e_entry所指的地址;对于动态链接的ELF可执行文件,程序入口点是动态链接器。

当load_elf_binary()执行完毕,返回至do_execve(),再返回至sys_execve()时,上面的第5步中已经把系统调用的返回地址改成了被装载的ELF程序的入口地址了。所以当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到了ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件装在完成。

下面总结Linux中动态链接的过程

一、首先理解与动态链接相关的几个段

与动态链接相关的几个段(sections),下面这几个段既可以在共享对象中,也可以在可执行文件中(当然,通过静态链接生成的可执行文件是不存在如下段的)。

1.“.interp”段,指明了动态链接器的位置

2.“.dynamic”段,可以看成是动态链接下ELF文件的“文件头”,这里面保存了动态链接器所需要的基本信息,比如依赖于哪些共享对象,动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。

DT_SYMTAB动态链接符号表的地址“.dynsym”的地址

DT_STRTAB动态链接字符串表的地址“.dynstr”的地址

DT_STRSZ动态链接字符串表大小

DT_HASH动态链接哈希表地址“.hash”的地址

DT_SONAME本共享对象的“SO-NAME”

DT_RPATH动态链接共享对象搜索路径

DT_INIT初始化代码地址(这对应于“.init”段的地址)

DT_FINIT结束代码地址(这对应于“.finit”段的地址)

DT_NEED依赖的共享代码文件,DT_REL动态链接重定位表地址

DT_RELA

DT_RELENT动态重定位表入口数量

DT_RELAENT

3.“.dynsym”动态符号表,这个表只保存了与动态链接相关的符号

4.“.rel.dyn”段和“.rel.plt”段。“.rel.dyn”实际上是对数据引用的修正,它所修正的位置位于“.got”以及数据段;而“.rel.plt”是对函数引用的修正,它所修正的位置位于“.got.plt”

二、然后理解动态连接器的作用和实现方法

在Linux下,动态连接器ld.so实际上是一个共享对象,操作系统同样通过映射的方式,将它加载到进程的地址空间中。操作系统在加载完动态链接器之后,就将控制权交给了动态链接器的入口地址(与可执行文件一样,共享对象也有入口地址)。当动态链接器得到了控制权之后,它开始执行一系列自身的初始化操作(自举),然后根据当前的环境参数,开始对可执行文件进行动态链接工作。当所有动态链接工作完成之后,动态链接器会将控制权交到可执行文件的入口地址,程序开始执行。

动态链接器入口地址即是自举代码的入口,当OS将进程控制权交给动态链接器时,动态链接器的自举代码即开始执行。自举代码首先会找到它自己的GOT。而GOT的第一个入口保存的即是“.dynamic”段的偏移地址,由此找到了动态链接器本身的“.dynamic”段。通过“.dynamic”中的信息,自举代码便可以获得动态链接器本身的重定位表和符号表等,从而得到动态链接器本身的重定位入口,先将它们全部重定位。从这一步开始,动态链接器代码中才可以开始使用自举的全局变量和静态变量。

三、动态链接器完成链接的过程

完成基本自举以后,动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表当中,我们可以称它为全局符号表。然后链接器开始寻找可执行文件所依赖的共享对象,在可执行文件的“.dynamic”段中有一种类型的入口DT_NEEDED,它所指出的是该可执行文件(或共享对象)所依赖的共享对象。并将这些共享对象的名字放入到一个装载集合中。然后链接器开始从集合里取一个所需要的共享对象的名字,找到相应的文件后打开该文件,读取相应的ELF文件头和“.dynamic”段,然后将它相应的代码段和数据段映射到进程空间中。如果这个ELF共享对象还依赖于其它共享对象,那么将所依赖的共享对象的名字放到装载集合中。如此循环直到所有依赖的共享对象都被装载进来为止,当然,链接器可以有不同的装载顺序,如果我们把依赖关系看做一个图的话,那么这个装载过程就是一个图的遍历过程,链接器可能会使用深度优先或者广度优先或者其它的顺序来遍历整个图,这取决于链接器,比较常见的算法是广度优先。

当一个新的共享对象被装载进来的时候,它的符号表会被合并到全局符号表中,所以当所有的共享对象都被装载进来的时候,全局符号表里面将包含进程中所有的动态链接所需要的符号。

当上面的步骤完成后,链接器开始重新遍历可执行文件和每个共享对象的重定位表,将它们的GOT/PLT中的每个需要重定位的位置进行修正。因为此时动态链接器已经拥有了进程的全局符号表,所以这个修正过程也显得比较容易,跟我们前面提到的地址重定位的原理基本相同。重定位完成之后,如果某个共享对象有“.init”段,那么动态链接器会执行“.init”段中的代码,用以实现共享对象特有的初始化过程,比如常见的,共享对象中的C++的全局/静态对象的构造就需要通过“.init”来初始化。相应地,共享对象中还可能有“.finit”段,当进程(指可执行文件对应的进程)退出时,会执行“.finit”段中的代码,可以用来实现类似C++全局对象析构之类的操作。

如果进程的可执行文件也有“.init”段,那么动态链接器不会执行它,因为可执行文件中的“.init”段和“.finit”段由程序初始化部分代码负责执行(我们将在库这一部分学习程序初始化)。

四、对链接过程中出现版本交会问题的解决方法

动态链接库在查找共享库过程中,会出现次版本号交会问题,此时通过基于符号的版本

机制方案来解决。程序员可以在链接共享库时编写一种叫做符号版本脚本的文件,在这个文件中指定这些符号(导入和导出符号)与集合之间及集合与集合之间的继承关系。链接器在链接时根据符号版本脚本中指定的关系来产生共享库,并且设置符号的集合与她们之间的关系。实际上是这样子的:

1.在构造共享对象时,在共享对象中加入符号版本脚本文件,得到的共享对象就含有版本信息

2.若其它共享对象或可执行文件引用此共享对象,则其中就会包含相应的版本信息

3.如果在版本信息低于此共享对象或可执行文件的系统中运行,动态链接器就会报运行错误。

《程序员的自我修养总结(一).docx》
将本文的Word文档下载,方便收藏和打印
推荐度:
程序员的自我修养总结(一)
点击下载文档
相关专题 程序员的自我修养总结 程序员 修养 自我 程序员的自我修养总结 程序员 修养 自我
[员工个人工作总结]相关推荐
    [员工个人工作总结]热门文章
      下载全文