所在小组: 第二组
组内昵称:李显良
心得体会
静态链接
将一组可重定位目标文件生成可以加载和运行可执行目标文件
目标文件的理解
-
可重定位目标文件
包含代码和数据但不能直接运行,比如点o文件
-
可执行文件
包含二进制代码和数据,可以直接运行
-
共享目标文件
也是可重定位目标文件,可以在程序加载或者运行时,被动态的加载到存储器并链接;多个进程可以共享同一个共享库节省系统存储,比如so文件
可重定位目标文件
text: 代码段
data:全局已初始化变量,常量
bss:全局未初始化变量
symtab: 符号表,存放程序定义和引用的函数和全局变量,部变量是放在栈上的,符号表不需要记录
rel.text rel.data: 引用外部模块代码和数据
符号解析
-
静态链接
静态库的好处:在连接时只会拷贝自己引用的函数和数据到目标文件,而重定位目标文件需要全量拷贝
-
链接顺序
在gcc编译链接时被引用的库放在引用库的后面
-
重定位
连接器的重定位就是把代码中的每个符号引用和确定的一个符号定定义联系起来;在目标模块时不知道数据和代码放在存储器什么位置,链接的作用就是通过重定位的决定引用来确定运行时的地址
共享库
需要生成位置无关的代码,可以通过dlopen来动态加载代码,达到灵活控制的作用
进程
进程为程序提供一个假象,好像程序独占操作系统处理器;进程可以通过中断、故障、陷入系统调用方式由用户态进入内核态
上下文切换
操作系统通过上下文切换来实现多任务运行,上下文切换就是保存当前进程的上下文信息(堆栈代码相关);恢复某个先前被抢占的进程的上下文;将控制传递给这个新恢复的进程
中断导致上下文切换也会导致高速缓存失效,更好利用高速缓存需要减少更少的上下文切换
smgu
25
所在小组
第四组
组内昵称
彳亍
心得体会
静态链接
优点:① 代码装载速度快,执行速度略比动态链接库快;缺点:使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费。
动态链接
优点:①更加节省内存并减少页面交换,因而极大地提高了可维护性和可扩展性;③不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;④适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。缺点:速度比静态链接慢。当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,统统撕掉。
编译器 =》链接器 =》 加载器 =》动态链接器
异常控制流
异常的类别:
中断(硬件中断,来自I/O设备的信号,非任何一条专门的指令造成->这个意义上来说是异步的,剩下的都是同步的)
陷进(有意的异常,是指向一条指令的结果->系统调用,运行在内核模式中)
故障(故障处理程序能修正,如缺页异常,重新执行,否则终止引起故障的应用程序)
终止(不可恢复的致命错误,处理程序返回到内核中的abort例程,该例程会终止这个应用程序)
进程
用户模式和内核模式:
设置了模式位时,进程就运行在内核模式中;用户程序必须通过系统调用接口间接地访问内核代码和数据;异常发生时,控制权传递到异常处理程序,处理器将模式从用户模式变为内核模式->处理程序运行在内核模式中->放回应用程序代码时,处理器把模式切回到用户模式;linux的/proc文件系统存有内核数据结构,用户模式可读;/sys文件系统:系统总线和设备的额外低层信息;
上下文切换:
系统调用阻塞(如sleep系统调用);系统周期性定时器中断的机制,通常为1毫秒或每10毫秒,内核觉得当前进程已经运行了足够长时间了,切换带一个新的进程
所在小组
第一组
组内昵称
nigel
心得体会
- C源码 通过编译器变为 汇编代码 通过汇编器变为 目标代码
- (目标代码 + 静态连接库)通过链接器 变为可执行代码
- 可执行代码 通过加载器 加载到内存中,CPU去读取执行
在Linux平台下可执行文件和目标文件使用的是叫ELF格式的文件,这里面主要包含:
- .text: 代码段和指令段
- .data: 输出化好的数据
- .rel.text: 重定位表,即不知道该跳转到哪里,就放在这里面
- .symtab: 符号表,函数名和地址的映射
-
链接器根据输入的所有文件,把所有符号表里面的信息收集起来构成一个全局符号表。
-
链接器再根据重定位表,把所有不确定要跳转的地址的代码,根据符号表里面的地址进行修正
-
链接器最后把所有的目标文件的对应段进行一次合并,变成最终的可执行代码。
windows下面可执行文件的格式是PE(Portable Executable Format),因为可执行文件的格式和linux的ELF的格式都不一样,所以不能相互解析
异常控制流
- ECF 称为异常控制流
- 在任何情况下,当处理器检测到有时间发生时,他就会通过一张叫做一场表的跳转表,进行一个间接过程调用,到一个专门设计用来处理这类事件的操作系统子程序,当异常处理程序完成处理后,根据引起异常的事件的类型,会发生一下三种情况
- 处理程序将控制返回给当前指令
- 处理程序将控制返回给下一个指令
- 中断程序
共享库:
- 一个目标模块。在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来,这个过程叫做动态链接,由一个叫做动态链接器的程序来执行。
gaohua
28
所在小组
第七组
组内昵称
高华
心得体会
了解链接器的工作原理
- 帮助构造大型程序
- 理解语言的作用域规则的实现
链接可以在编译时由静态编译器来完成,也可以在加载时和运行时由动态链接器来完成
动态链接库对于在windows下编程的程序员来说很熟悉(各种 dll 文件),读了这章内容才算是对于动态链接库有了更深的理解。
链接器的两个主要任务
- 符号解析
- 重定位
静态链接
静态链接器一般以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的,可以加载和运行的可执行目标文件作为输出。
目标文件有三种形式
- 可重定位目标文件
- 可执行目标文件
- 共享目标文件
所在小组:第二组
组内昵称:跃山
心得体会:
在系统上运行程序
链接
- 编译器驱动程序
- 静态链接
- 目标文件
- 可重定位目标文件
- 符号和符号表
- 符号解析
- 重定位
- 可执行目标文件
- 加载可执行目标文件
- 动态链接共享库
- 从应用程序中加载和链接共享库
- 位置无关代码
- 库stub机制
- 处理目标文件的工具
编译器驱动程序
静态链接
静态链接:以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行的可执行目标文件作为输出。
符号解析,目标文件定义和引用符号,每个符号对应一个函数、全局变量或静态变量。符号解析的目的是将每个符号引用和定义相关联。
重定位,编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。
目标文件
目标文件三种形式:可重定位目标文件、可执行目标文件、共享目标文件
可重定位目标文件(ELF)
- .text:已编译程序的机器代码
- .rodata:只读数据,比如printf语句中的格式串和开关语句的跳转表
- .data:已初始化的全局和静态C变量;(局部变量在运行时被保存在栈中,既不在.data也不在.bss)
- .bss:未初始化的全局或静态C变量;仅仅是占位符不占用实际空间,运行时在内存分配,初始值为0
- .symtab:符号表,存放在程序中定义和引用的函数和全局变量的信息
- .rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非用户显式地指示链接器包含这些信息。
- .rel.data:被模块引用或定义的所有全局变量的重定位信息。一般而言,任何已初始化的全局变量,如果它的初始值是一个全局变量地址或者外部定义函数的地址,都需要被修改。
- .debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项调用编译器驱动程序时,才会得到这张表。
- .line:原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译器驱动程序时,才会得到这张表。
- .strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以nu11结尾的字符串的序列。
符号和符号表
每个可重定位目标模块m都有一个符号表,由模块m定义并能被其他模块引用的全局符号,全局链接器符号对应于非静态的C函数和全局变量,有其他模块定义并被模块m引用的全局符号,这些符号称为外部符号,对应于在其他模块中定义的非静态C函数和全局变量,只被模块m定义和引用的局部符号,他们对应于带static属性的C函数和全局变量。这些符号在模块m中任何位置可见,但是不能被其他模块引用。
认识到本地链接器符号和本地程序变量不同是很重要的
.symtab中的符号表不包含对应于本地非静态程序变量的任何符号。这些符号在运行时再栈中管理。链接器对此类符号不感兴趣。
定义为带有C static属性的本地过程变量是不在栈中管理的,相反,编译器在.data或.bss中为每个定义分配空间,并在符号表中创建一个有唯一名字的本地链接器符号
COMMON 未初始化的全局变量
.bss 未初始化的静态变量,和初始化为0的全局或静态变量
符号解析
重定位
可执行目标文件
加载可执行目标文件
动态链接共享库
从应用程序中加载和链接共享库
位置无关代码
库打桩机制
- 编译时打桩
- 链接时打桩
- 运行时打桩
处理目标文件的工具
异常流控制
异常
异常分为四类:中断
、陷阱
、故障
和终止
。
- 中断是由处理器外部的I/O设备中的事件产生的,而不是由一条专门的指令造成的,所以中断是异步异常,处理中断的异常处理程序叫做中断处理程序;其他的三种都属于同步异常,它们是执行一条指令的直接产物,我们把这类指令叫做故障指令。
- 陷阱是有意的异常,在用户态的应用程序进行系统调用的时候会执行syscall指令,执行这个指令会陷入到内核态,触发一个异常处理程序,这个异常处理程序会调用相应的内核程序。
- 故障由错误情况引起,它可能被故障处理程序修正。当一个故障发生时,处理器将控制转移给故障处理程序,如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行 它;否则会调用内核中的abort例程来终止引起故障的程序。
- 终止是不可恢复的致命错误造成的结果,终止处理程序会将控制返回到一个abort例程来终止这个应用程序。
上下文就是内核重新启动一个进程所需要的状态,它由一些对象的值组成,包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含当前进程信息的进程表以及包含进程已打开文件的信息的文件表。
上下文切换:
- 保存当前进程的上下文。
- 恢复某个先前被抢占的进程被保存的上下文。
- 将控制传递给这个新恢复的进程。
进程
系统调用错误处理
进程控制
信号
非本地跳转
操作进程的工具
所在小组
第六组
组内昵称
undefined
总结
链接
链接到编译过程
4步走,工具名称清晰明了:
cpp: code preprocess
cc1: code complier
as: assemble
ld: loader
静态链接
核心主要就是将多份目标文件的符号表链接,而符号表如何设计来保证能正确的重定位。
符号表有哪些设计来保证重定位能高效实现?
打桩机制
分为3部分:编译时打桩、链接时打桩、运行时打桩
常用的方式还是LD_PRELOAD环境变量,可以hook很多系统库方法。
异常控制流
异常的概念
多种类别的异常:中断、系统调用、故障、终止
每个异常都有唯一的异常号,且由操作系统进行分配和初始化
那硬件中断和软件中断的区别?
进程的概念
进程的核心抽象是:独立的逻辑控制流、私有的地址空间
其中又分为:逻辑控制流和并发流、私有空间、用户态内核态、以及上下文切换
一些中断操作是如何唤醒对应的进程的?例如epollo_wait
所在小组
第五组
组内昵称
张学广
心得体会
第六章 存储器层次结构
这一章介绍了计算机中的各种存储器,以及各自的优缺点和组合使用,之后介绍了如何编写缓存友好的代码,在这部分有很好的收货。
计算机中的存储是分层的,从最高速的cpu缓存,到 L1、L2、L3这些高速缓存,之后是主存以及最后的磁盘存储,规律是存储从小到大,速度从快到慢,造价从高到底,这样的组合利用了各种存储器的优劣势可以兼顾速度大小与造价。
之后是些高速缓存友好的代码,有以下几点
- 让常见的情况运行的更快
- 尽量增加缓存命中
到实际中需要注意的两点:
- 对局部变量反复利用
- 步长为1的引用模式
命中率主要看高速缓存的原理,一行一行的读取数据是要比一列一列读取快的多的。
其次也要注意程序的切换,切换程序会导致缓存的刷新,导致命中率降低
所在小组
第二组
组内昵称
可可
心得体会
elf header
gdb定位变量的利器
需要关注的是bss 和data
data: 已经初始化的全局或者静态C变量
bss: 未初始化的全局或者静态变量(包括初始化为0的数据),不占用磁盘空间(编译文件的空间),运行的时候会放到内存中。
关联golang
go中的全局文件是在data里吗,biru g0
go调度中协程等地址是stack还是data?
符号解析
c++永远的痛
全局符号,因为编译器不能确定找不到的符号究竟别人有没有定义,所以可以编译。但是到了链接这里,找遍了所有也没有找到,也就报错了。undefined reference
Exceptions
所谓的异常其实是计算机中最正常的事
- 时钟,并发的保证
- 信号,go1.14中强制调度的实现。通过信号,我们获得了本被抢占的权利。
进程
异常的完美体现
- 独立的逻辑控制流(异常?),提供一个我们独占处理器的假象
- 私有的地址空间(虚拟地址?),提供了一个独占内存的假象
关联golang
blue
36
所在小组
第一组
组内昵称
bluewhale
你的心得体会
链接
链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程
- 链接可以执行于编译时,也就是在源代码被翻译成机器代码时;
- 也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;
- 甚至执行于运行时,也就是由应用程序来执行。
链接器主要完成符号解析和重定位两个任务
目标文件由三种形式:
- 可重定位目标文件,包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。
- 可执行目标文件,包含二进制代码和数据,其形式可以被直接复制到内存并执行。
- 共享目标文件,一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态的加载进内存并链接。
编译器和汇编器生成可重定位目标文件(包括共享目标文件)。链接器生成可执行目标文件。从技术上来说,一个目标模块就是一个字节序列,而一个目标文件就是一个以文件形式存放在磁盘中的目标模块。
一个典型的ELF可重定位目标文件包含下面几个节:
.text:已编译程序的机器代码。
.rodate:只读数据,比如printf语句中的格式串和开关语句的跳转表。
.data:已初始化的全局和静态C变量。局部C变量在运行时被保存在栈中,既不出现在.data节中,也不出现在.bss节中。
.bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。在目标文件中,未初始化的变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0。
.symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。实际上,每个可重定位目标文件在.symtab中都有一张符号表(除非程序员特意用STRIP 命令去掉它)。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。
处理多重定义
- 不允许有多个同名的强符号;
- 如果有一个强符号和多个弱符号同名,那么选择强符号;
- 如果有多个弱符号同名,那么从这些弱符号中任意选择一个。
动态链接共享库
静态库的一些缺点:
共享库:
一个目标模块。在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来,这个过程叫做动态链接,由一个叫做动态链接器的程序来执行。
共享库也叫作共享目标:
-
Linux中通常用.so后缀结尾来表示
-
Windows中用.dll后缀结尾来表示
库打桩 机制允许用户截获对共享库函数的调用,取而代之执行自己的代码
所在小组
第六组
组内昵称
利健锋
你的心得体会
只有全局变量,全局函数,静态变量,静态函数有符号的概念!
目标文件三种形式:
- 可重定位目标文件
- 可执行目标文件
- 共享目标文件
Unix链接器处理多重定义的规则:
- 不允许有多个强符号。
- 如果有一个强符号和多个弱符号,那么选择强符号。
- 如果有多个弱符号,那么从这些弱符号中任意选择一个。
重定位组成:
- 重定位节和符号定义
- 重定位节中的符号引用
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程
所在小组
第四组
组内昵称
murphy
你的心得体会
C语言在编译之后,生成的问题称之为目标文件,它本质上是一种源代码和可执行程序之间的中间文件,尽管这个中间文件和可执行程序一样也是二进制形式的。
这个中间文件只包含你之前所编译的代码,但是很多时候,我们还需要其他的库的支撑,菜呢个构成一个完整的可执行程序。
这个将自己编写的目标文件和库结合起来,才能构成一个完整的二进制可执行文件。
而链接(Link),就是将将自己编写的目标文件和库结合起来的过程。
一般来说,链接可以分为静态链接和动态链接,库也随之分为静态链接库和动态链接库。静态链接在链接阶段,会将源文件用到的库函数和会变成生的目标文件合并为可执行文件。其生成的文件因为包含了所有需要涉及的静态链接库中的函数,所以较大。一个print函数,其存在于几个程序之中,他就会被链接多次。而动态库则只有一份,会生成位置无关的.so文件,在链接时只需做标记,在需要的时候进行链接。
而而负责执行此项任务的程序被称之为链接器(LInker)。
所在小组
第二组
组内昵称
梁广超
心得体会
1、静态链接是链接器在链接时将可重定位目标文件组合起来,形成一个可执行目标文件。为了构造可执行文件,链接器必须完成两个主要任务: 符号解析和重定位;
2、静态库,这里引入一个库的概念,编译系统提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库,其文件后缀为.a,里面包含多个.o文件。它可以用做链接器的输人,当链接器构造一个输出的可执行文件时,它只拷贝静态库里被应用程序引用的目标模块。
3、重定位将输入的模块合并,并为每个符号分配运行时地址。其实就是在处理.text与.data.bss的联系关系。
4、链接器如何解析多重定义的全局符号,
规则1:不允许多个强符号;
规则2:如果有一个强符号和多个弱符号,那么选择强符号;
规则3:如果有多个弱符号,那么这些弱符号中任意选择一个。
5、动态链接,动态链接就是在运行的时候再去链接。为了使得动态库在内存中只有一份,需要做到不管动态库装载到什么位置,都不需要修改动态库中代码段的内容,从而实现动态库中代码段的共享。
han
40
所在小组
静默组
组内昵称
Han
心得体会
编译器能够帮助我们发现很多问题,在大型c/c++项目里面,一个基本要求是编译的时候不能有warning,这样就能消除掉很多隐患。
动态链接库有很多好处,但不注意的话,也会引起令人困惑的问题:
- 动态库冲突
- 版本依赖
特别是复杂的安装环境下,运行的环境容易新的安装或者版本升级破坏,最终导致程序不可运行。以前在产品开发的过程中,遇到最多的是openssl各种版本的冲突,有时候程序会有莫名其妙的行为,反复分析才发现时引用了错误的openssl版本,ldd
命令可以帮助检查程序的动态库依赖。
异常是控制流中的普遍情况(控制流中的突变),虽然叫异常,但很多状态变化,都是通过异常来进行转化和完成的。
- I/O设备的中断(磁盘、网卡等等)
- 内核系统调用(trap)
- 故障(缺页异常)
上下文切换的开销虽然很小,但大量的上下文切换会导致系统性能下降,不少性能优化的逻辑也是在这个点上就是优化: