《深入理解计算机系统》 Chapter 7 读书笔记

《深入理解计算机系统》Chapter 7 读书笔记

链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(货被拷贝)到存储器并执行。

链接的时机

  • 编译时,也就是在源代码被翻译成机器代码时
  • 加载时,也就是在程序被加载器加载到存储器并执行时
  • 运行时,由应用程序执行

链接器使分离编译称为可能。

一、编译器驱动程序

大部分编译系统提供编译驱动程序:代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。

1.将示例程序从ASCⅡ码源文件翻译成可执行目标文件的步骤

   (1)运行C预处理器:源程序main.c->ASCII码中间文件main.i

   (2)运行C编译器:main.i->ASCII码汇编语言文件main.s

   (3)运行汇编器:main.s->可重定位目标文件

二、静态链接

静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的可以加载和运行的可执行目标文件作为输出。输入的可重定位目标文件由各种不同的代码和数据节(section)组成。指令在一个节中,初始化的全局变量在另一个节中,而未初始化的变量又在另外一个节中。

为了构造可执行文件,链接器必须完成两个任务:符号解析,重定位

三、目标文件

1.三种形式

  • 可重定位目标文件。包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。
  • 可执行目标文件。包含二进制代码和数据,其形式可以被直接拷贝到存储器并执行。
  • 共享目标文件。一种特殊类型的可重定位目标文件,可以在加载或者运行地被动态地加载到存储器并链接。

编译器和汇编器生成可重定位目标文件(包括共享目标文件)。链接器生成可执行目标文件。从技术上来说,一个目标模块就是一个字节序列,而一个目标文件就是一个存放在磁盘文件中的目标模块。

四、可重定位目标文件

1.一个典型的ELF可重定位目标文件的格式:

  • .text:已编译程序的机器代码。
  • .rodata:只读数据,比如printf语句中的格式串和开关语句的跳转表。
  • .data:已初始化的全局C变量。
  • .bss:未初始化的全局C变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。
  • .symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。
  • .rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件结合时,需要修改这些位置。
  • .rel.data:被模块引用或定义的任何全局变量的重定位信息。
  • .debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项调用编译驱动程序时才会得到这张表。
  • .line:原始C源程序中的行号和.text节中机器指令之间的映射。
  • .strtab:一个字符串表,其内容包括:.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串序列。

五、符号和符号表

每个可重定位目标模块m都有一个符号表,包含m所定义和引用的符号的信息。

在链接器的上下文中,三种不同的符号:

1.由m定义并能被其他模块引用的全局符号。全局链接器对应于非静态的C函数以及被定义为Cstatic 属性的全局变量。

2.由其他模块定义并被模块m以引用的全局符号——外部符号,对应于定义在其他模块中的C函数和变量

3.只被模块m定义和引用的本地符号。

六、符号解析

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

在编译时,编译器向汇编器输出每个全局符号,或者是强或者是弱,而汇编器把这个信息隐含地编码在可重定位目标文件的符号表里。

函数和已初始化的全局变量时强符号;

未初始化的全局变量是弱符号。

根据强弱符号的定义,Unix链接器使用下面的规则来处理多重定义的符号:

规则1:不允许有多个强符号。
规则2:如果有一个强符号和多个弱符号,那么选择强符号。
规则3:如果有多个弱符号,那么从这些弱符号中任意选择一个。

2.与静态库链接

所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库

3.链接器如何使用静态库来解析引用

  • 对于命令行上的每个输入文件f,链接器会判断f是一个目标文件还是一个存档文件。如果f是一个目标文件,那么链接器吧f添加到E, 修改U和D来反映f中的符号定义和引用,并继续下一个输入文件。
  • 如果f是一个存档文件,那么链接器就尝试匹配U中未解析的符号和由存档文件成员定义的符号。如果某个存档文件成员m,定义了一个符号来解析U中的一个引用,那么就将m加到E中,并且链接器修改U和D来反映m中的符号定义和引用。对存档文件中所有的成员目标文件都反复进行这个过程,直到U和D都不再发生变化。在此时,任何不包含在E中的目标文件都简单地被丢弃,而链接器将继续处理下一个输入文件。
  • 如果当链接器完成对命令行上输入文件的扫描后,U是非空的,那么链接器就好输出一个错误并终止。否则,它会合并和重定位E中的目标文件,从而构建输出的可执行文件。

七、重定位

一旦链接器完成了符号解析这一步,它就是把代码中的每个符号引用和确定的一个符号定义(即它的一个输入目标模块中的一个符号表条目)联系起来。

重定位由两步组成:

  • 重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。然后,链接器将运行时存储器地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。当这一步完成时,程序中的每个指令和全局变量都有唯一的运行时存储器地址了。
  • 重定位节中的符号引用。在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。为了执行这一步,链接器依赖于称为重定位条目的可重定位目标模块中的数据结构。

1.重定位条目

当汇编器生成一个目标模块时,它并不知道数据和代码最终存放在存储器中的什么位置。它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。所以,无论何时汇编器遇到对最终位置位置的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在.rel.text中 已初始化的数据的重定位条目放在.rel.data中。

八、可执行目标文件

可执行目标文件的格式类似于可重定位目标文件的格式。ELF头部描述文件的总体格式。它还包括程序的入口点,也就是当程序运行时要执行的第一条指令的地址。.text 、.rodata和.data 节和可重定位目标文件中的节是相似的,除了这些节已经被重定位到它们最终的运行时存储器地址以外。.init节定义了一个小函数,叫做_init,程序的初始化代码会调用它。因为可执行文件是完全链接的(已被重定位了),所以它不再需要.rel节。

九、加载可执行目标文件

加载器将可执行目标文件中的执行代码和数据从磁盘拷贝到存储器中,然后通过跳转到程序的第一条指令或入口点来运行该程序。这个将程序拷贝到存储器并运行的过程叫做加载。

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

unix> ./p

十、动态链接共享库

共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并和一个在存储器中的程序链接起来。这个过程称为动态链接,是由一个叫做动态链接器的程序来执行的。

共享库也称为共享目标,在Unix系统中通常用.so后缀来表示。微软的操作系统大量地利用了共享库,它们称为DLL(动态链接库)。

时间: 2024-12-18 17:29:56

《深入理解计算机系统》 Chapter 7 读书笔记的相关文章

《Linux内核设计与实现》Chapter 5 读书笔记

<Linux内核设计与实现>Chapter 5 读书笔记 在现代操作系统中,内核提供了用户进程与内核进行交互的一组接口,这些接口的作用是: 使应用程序受限地访问硬件设备 提供创建新进程与已有进程进行通信的机制 提供申请操作系统其他资源的能力 一.与内核通信 1.系统调用的作用 系统调用在用户空间进程和硬件设备之间添加了一个中间层,作用是: 为用户空间提供了一种硬件抽象接口: 系统调用保证了系统的稳定和安全,即可以避免应用程序不正确地使用硬件设备,窃取其他进程的资源: 每个进程都运行在虚拟系统中

《Linux内核设计与实现》Chapter 2 读书笔记

<Linux内核设计与实现>Chapter 2 读书笔记 一.获取内核源码 1.使用Git 我们曾经在以前的学习中使用过Git方法 $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git 更新分支到Linux的最新分支 $ git pull 可以获取并随时保持与内核官方的代码树一致 2.安装内核源代码 压缩形式为bzip2 $ tar xvjf linux-x.y.z.tar.bz2 压缩

《深入理解Java虚拟机》读书笔记---第二章 Java内存区域与内存溢出异常

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来.这一章就是给大家介绍Java虚拟机内存的各个区域,讲解这些区域的作用,服务对象以及其中可能产生的问题. 1.运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域. 1.1程序计数器 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型中里,字

《深入理解Java虚拟机》读书笔记:Java内存区域

xmind文件下载地址 <深入理解Java虚拟机>读书笔记:Java内存区域,布布扣,bubuko.com

《深入理解Java虚拟机》读书笔记---第一章 走进Java

一.为什么要读此书 <深入理解Java虚拟机>这本书读了很多次,每次读都会有不一样的感受.首先说一下为什么要读这本书,如果把Java比喻成乾坤大挪移,那了解虚拟机的工作原理就是练习九阳神功,java语言是招式,对虚拟机的认识是内功心法,只有内功心法强大,所使的招式才强大,这就是为什么阳顶天只能把乾坤大挪移练到第四层,而张无忌能练到第七层.由于java虚拟机的强大,把很多功能都隐藏了,例如内容管理,垃圾回收机制等,使得很多java程序猿对这一块的知识所有缺失,编码的时候也是似懂非懂的,以至于遇到

《Linux内核设计与实现》Chapter 3 读书笔记

<Linux内核设计与实现>Chapter 3 读书笔记 进程管理是所有操作系统的心脏所在. 一.进程 1.进程就是处于执行期的程序以及它所包含的资源的总称. 2.线程是在进程中活动的对象. 3.进程提供两种虚拟机制:虚拟处理器和虚拟内存. 4.内核调度的对象是线程,而不是进程. 二.进程描述符及任务结构 内核把进程的列表存放在叫做任务队列的双向循环链表中.链表中的每一项都是类型为task_struct的进程描述符结构,该结构定义在<linux/sched.h>文件中. 1.分配进

深入理解计算机系统9个重点笔记

引言 深入理解计算机系统,对我来说是部大块头.说实话,我没有从头到尾完完整整的全部看完,而是选择性的看了一些我自认为重要的或感兴趣的章节,也从中获益良多,看清楚了计算机系统的一些本质东西或原理性的内容,这对每个想要深入学习编程的程序员来说都是至关重要的.只有很好的理解了系统到底是如何运行我们代码的,我们才能针对系统的特点写出高质量.高效率的代码来.这本书我以后还需要多研究几遍,今天就先总结下书中我已学到的几点知识. 重点笔记 编写高效的程序需要下面几类活动: 选择一组合适的算法和数据结构.这是很

《深入理解JVM虚拟机》读书笔记

前言:<深入理解JVM虚拟机>是JAVA的经典著作之一,因为内容更偏向底层,比较枯燥难啃,所以之前一直没有好好的阅读过.最近因为刚好有空,又有了新目标.所以打算和<构架师的12项修炼>一起看,这样荤素搭配,吃饭不累~ 笔记: 1.如果开发人员不了解虚拟机的一些技术特性的运行原理,就无法写错最适合虚拟机运行和自优化的代码. 2. 原文地址:https://www.cnblogs.com/xujanus/p/8513587.html

《深入理解Java虚拟机》读书笔记——第1章 走近Java

(注:原文请见<深入理解Java虚拟机>周志明 著,知识点部分参考百度百科) 总述 第1章分为两个部分:Java概述及自己动手编译JDK的教程. 1 Java概述 介绍了Java的整体特性.Java的技术体系组成.Java及JVM的发展史. 1.1 Java的总体特性 1)Java不仅仅是一门编程语言,更是由一系列计算机软件和规范组成的技术体系. 2)Java具有众多优点: a.Java虚拟机在千差万别的物理机上建立了统一的运行平台,实现了跨平台性.(主要) b.提供了相对安全的内存管理和访问