目录

第七章 动态链接

7.1动态链接的实现过程

7.2动态链接的步骤和实现

第八章 Linux共享库的组织

第九章 Windows 下的动态链接

9.1DLL简介

9.2DLL优化

9.3DLL HELL

第十章 内存

10.1程序的内存布局

10.2栈的调用惯例

10.3堆与内存管理

第十一章 运行库

11.1入口函数和程序初始化

11.2C/C++运行库

11.3运行库与多线程

11.4C++全局构造与析构

第十二章 系统调用与API

12.1系统调用介绍

12.2系统调用原理


第七章 动态链接

静态链接的不足:每个程序内部除了公用库函数还有相当数量的其它库函数和它们所需的辅助数据结构,浪费内存和磁盘空间(尤其是多进程情况下)、模块更新困难等,此外其对程序的更新、部署和发布带来麻烦。

动态链接:把程序的模块相互分割开,不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。如当某程序加载时发现还依赖其它目标文件系统会接着将所需依赖文件(内存中有副本的不需重新加载)全部加载至内存,等所有依赖目标文件加载完毕,系统开始进行链接工作。链接原理与静态链接类似,包括符号解析、地址重定位等。

动态链接文件:Linux中ELF动态链接文件被称为动态共享对象(DSO)简称共享对象,以.so为扩展名;Windows中,动态链接文件被称为动态链接库,以.dll为扩展名。

固定装载存在的问题:采用静态库共享(与静态库有明显的区别)的方式将程序的各个模块统一交给操作系统管理,操作系统在某个特定的地址划分出一些地址块,为那些已知的模块预留足够的空间。存在地址冲突、升级困难等问题。

装载时重定位:在链接时对所有绝对地址的引用不做重定位,而把这一步推迟到装载时再完成,一旦模块装载地址确定,那么系统就对程序中所有的绝对地址引用进行重定位。适用于动态链接库中的可修改数据(可修改数据在不同进程间具有多个副本)部分,而不适用于指令(指令部分无法在多个进程之间共享)部分。

地址无关代码:针对装载时重定位无法解决指令部分在多个进程间共享的问题,将指令中那些需要修改的部分分离出来,跟数据部分放在一起,这样指令部分就可以保持不变,而数据部分可以在每个进程中拥有一个副本,该技术即地址无关代码。其4种地址引用方式如下:

共享模块的全局变量问题:为了使得链接过程正常运行,链接起会在创建可执行文件时在他的.bss段创建一个全局变量的副本,所有使用这个变量的指令都位于可执行文件中的那个副本,共享库编译时默认都把定义在模块内部的全局变量当作定义在其它模块的全局变量。

延迟绑定:当函数第一次被用到时才进行绑定,如果没有用到则不进行绑定,这样大大加快程序的启动速度,有利于那些有大量函数应用和大量模块的程序。

7.1动态链接的实现过程

动态链接过程:操作系统读取可执行文件的头部并检查文件的合法性,然后从头部的 Program Header 中读取每个 Segment 的虚拟地址、文件地址和属性,并将它们映射到进程虚拟空间的相应位置,装载完可执行文件后采用动态链接器将外部符号的引用与相应共享对象的实际位置链接,当所有动态链接工作完成后东岱链接器将控制权转交到可执行文件的入口地址,程序执行。

7.2动态链接的步骤和实现

动态链接器自举:动态链器本身不可以依赖于其它任何共享对象(编写动态链接器时保证不适用任何系统库、运行库),且动态连接器本身所需的全局和静态变量的重定位工作由它本身完成(启动时通过一段精巧代码控制,同时不用全局、静态变量和函数调用)。这种具有一定限制条件的启动代码往往被称为自举。

自举步骤:动态连接器入口地址即自举代码入口,操作系统将控制权交给动态链接器后,自举代码开始执行。自举代码先找到它自己的GOT(第一个入口保存了.dynamic段偏移地址,由此找到了动态链接器本身的.dynamic段),通过.dynamic中的信息,自举代码可获得动态链接器本身重定位表和符号表等,从而得到动态链接器本身的重定位入口,先将它们全部重定位,此后动态连接器才可以开始使用自己的全局变量和静态变量。

第八章 Linux共享库的组织

共享库管理问题:大量程序使用动态链接机制导致系统里面存在数量极为庞大的共享对象,若没有很好的方法将这些共享对象组织起来,整个系统中的共享对象文件则会散落在各个目录下,给长期维护和升级造成困难。

共享库版本命名:命名规则为 libname.so.x.y.z ,最前面使用前缀 lib 、中间式库的名字和后缀 .so,最后面跟着的式三个数字组成的版本号,x为主版本号(重大升级与旧版本不兼容)、y为次版本号(增量升级增加新接口,向后兼容)、z表示发布版本号(错误修正和性能改进)。

共享库的存放位置:Linux遵顼FHS标准进行系统文件的存放,包括各个目录的结构、组织和作用。FHS规定一个系统中主要有两个存放共享库的位置(1)/lib 主要存放系统最关键和基础的共享库,如动态链接器、C语言运行库、数学库等;(2)/usr/lib 主要保存一些非系统运行时所需的关键性共享库,主要是开发时用到的共享库;(3)/usr/local/lib 主要存放一些跟操作系统本身不相关的库,主要是一些第三方的应用程序库,如python按章后的共享库。

环境变量:改变共享库查找路径最简单的方法是使用LD_LIBRARY_PATH环境变量,这个方法可以临时改变某个应用程序的共享库查找路径,而不会影响系统中的其它程序,若某个进程设置了该变量则进程启动时动态链接器在查找共享库时会首先查找由该变量指定的目录。LD_PRELOAD里面指定的文件会在动态链接器按照固定规则搜索共享库之前装载,他比LD_LIBRARY_PATH优先级更高,正常情况下应该避免使用LD_PRELOAD。

第九章 Windows 下的动态链接

9.1DLL简介

DLL:动态链接库缩写,相当于Linux 下的共享对象。

符号导出:当一个PE需要将一些函数或变量提供给其它PE文件使用时,我们把这种行为叫做符号导出,如一个DLL将符号导出给EXE文件使用,Windows PE中所有导出的符号被集中存放在导出表(提供一个符号名与符号地址的映射关系)结构中。

EPX文件:创建DLL的同时会得到一个EXP文件,这个文件是链接器在创建DLL时的临时文件,链接器在第一遍遍历所有目标文件并收集所有导出符号信息创建DLL导出表时会将导出表放在临时目标文件EXP中。

导出重定向:将某个导出符号重定向到另外一个DLL。

导入表:在某个程序中使用了来自DLL的函数或者变量,将该行为称做符号导入。 当某PE文件被加载,加载器将所有需要导入的函数地址确定并将导入表中的元素调整到正确的地址,以实现动态链接过程。

延迟载入:当链接一个支持延迟载入的DLL时,链接器会产生与普通DLL导入非常类似的数据,但操作系统会忽略这些数据,当延迟载入的API第一次被调用时,由链接起添加的特殊的桩代码就会启动,这个桩代码负责对DLL的装载工作。

9.2DLL优化

DLL导入导出问题:DLL的代码段和数据段并非地址无关,它默认被装载到由ImageBase指定的目标地址中,若目标地址被占用,则需要装载到其它地址,从而引起整个DLL的Rebase,对于拥有大量DLL程序,频繁Rebase会造成程序启动速度减慢,此时查找符号的过程也会比较耗时。

DLL优化:(1)重定基地址(所有需要重定位的地方只需加上一个固定差值);(2)对每个导出函数提供一个唯一对应的序号(仅供内部使用的函数可以采用只有序号没有函数名的方法,这样外部使用者就无法推测其含义和使用方法,该方法比函数名导入方法稍快,但不推荐使用该方法作为导入导出的手段);(3)导入函数绑定(大多情况下这些DLL都以同样顺序被装载到同样的内存地址,将导出函数地址保存到模块的导入表中可以省去每次启动时符号解析的过程,DLL更新和DLL发生重定基址会导致绑定地址失效)。

9.3DLL HELL

DLL HELL概述:DLL可能出现版本更新时发生不兼容的问题,三种可能原因导致该问题,(1)使用旧版本的DLL替代原来一个新版本的DLL而引起;(2)新版本DLL中的函数无意发生该表而引起,即不向下兼容;(3)新版本DLL安装引入新BUG。

解决DLL HELL的方法:(1)静态链接,在编译产生应用程序时使用静态链接的方法链接它所需要的运行库;(2)防止DLL覆盖,Windows可使用文件保护足式未授权应用覆盖系统DLL;(3)避免DLL冲突,让每个应用程序拥有一份自己依赖的DLL;(4).NET下的ELL HELL解决方案,.NET框架下一个程序集有应用程序集和库程序集(即DLL动态链接库)两种类型,利用Manifest文件描述程序集名字、版本号及程序集的各种资源。

第十章 内存

10.1程序的内存布局

平坦内存模型:整个内存是一个统一的地址空间,用户可以使用一个32位的指针访问任意内存位置。尽管内存空间被称为平坦的,但实际上内存在不同地址区间有不同地位,如大部分系统会将内存空间中的一部分留给内核使用,应用程序无法访问这一段内存(Linux默认将高地址的1GB分配给内核、Windows则分配2GB)

用户空间默认内存空间区域:(维护函数调用上下文,常在最高地址处分配,向低地址增长)、(动态分配的内存区域,通常存在栈的下方,向高地址增长,一般比栈大很多)、可执行映像文件(存储着可执行文件内存在内存的映像)、保留区(内存中受保护而禁止访问的内存区域的总称)等

10.2栈的调用惯例

堆栈帧:栈保存了一个函数调用所需维护的信息,常被称为堆栈帧或活动记录,它一般记录(1)函数的返回地址和参数;(2)临时变量,包括非静态局部变量及编译器自动生成的其它变量;(3)上下文,包括函数调用前后需保持不变的寄存器。

调用惯例:函数调用方和被调用方对于函数如何调用需有一个明确的约定,双方都遵循该约定函数才能被正确调用,其包含(1)函数参数的传递顺序和方式;(2)栈的维护方式,函数将参数压栈后需将被压入的参数全部弹出,该工作可由函数调用方或函数本身完成;(3)名字修饰的策略,不同调用惯例采用不同的名字进行修饰,以进行区分。主要函数调用惯例如下:

函数返回值传递:函数将返回值存储在eax(4字)中,返回后函数的的调用方再读取eax,当返回值超过4字节时会在调用方函数的栈上额外开辟空间并将该空间的一部分作为传递返回值的临时对象,并在调用时将临时对象的地址作为隐藏参数传递给被调用函数,被调用函数将数据拷贝给临时变量用eax传出,被调用函数返回后调用方函数读取eax指向的临时对象内容。c++返回较大对象会产生非常多的额外开销,对象要经过两次拷贝构造函数的调用才能完成返回对象的传递。

10.3堆与内存管理

堆的作用:栈上的数据在函数返回时会被释放掉,所以无法将数据传递至函数外部,而全局变量没办法动态地产生,只能在编译的时候定义,缺乏表现力,此时堆是唯一的选择。

堆空间:程序向操作系统申请一块适当大小的堆空间,然后由程序自己管理这块空间,管理堆空间分配的往往是程序的运行库,运行库通过堆的分配算法对堆空间进行管理。

Linux进程堆管理:两种堆空间分配方法,brk()系统调用,实际上是设置进程数据段结束地址,可以扩大或缩小数据段;mmap(),向操作系统申请一段虚拟地址空间,该虚拟空间可映射到某文件,当没映射到某文件我们称其为匿名空间。

Windows进程堆管理:用VirtualAlloc()向系统申请空间,与Linux的mmap()相似。通过对管理器实现创建、分配、释放和销毁堆空间,若堆空间不足VirtualAlloc会向操作系统申请更多的内存,直到操作系统没有空间可分配为止。

堆分配算法:管理一大块连续的内存空间,按照需求分配和释放空间,有3种常见的方法:,(1)空闲链表,将堆中各空闲的块按照链表的方式连接起来,用户请求时遍历整个链表直到找到何时大小的块并将其拆分,释放空间时将它们合并到空闲链表中,缺点是一旦链表被破坏堆就无法正常工作;(2)位图,将整个堆划分为大量大小相同的块,当用户请求内存时将整数个块分配给用户,第一个块被称为已分配区域的头,其余的被称为已分配区域的主体,每个块只有头、主体和空闲3种状态,可以在数组中用两位表示一个块,其速度快、稳定性好、无需额外信息便于管理,但易产生内存碎片且位图很大的时候将降低缓存命中率;(3)对象池,一些场合被分配对象的大小是较为固定的几个值,若每次分配空间大小都一样就可以按照这个每次请求分配的大小作为一个单位,把整个堆空间划分为大量的小块,每次请求只需找到一个小块就可以,实现可采用空间链表或位图法。

第十一章 运行库

11.1入口函数和程序初始化

程序的运行步骤:(1)操作系统创建进程并把控制权交给程序入口,该入口一般是运行库中某个函数的入口;(2)入口函数对运行库和程序运行环境进行初始化,包括堆、I/O、线程、全局变量构造等;(3)入口函数初始化完成并表用main执行程序主体;(4)main执行完毕并返回入口函数,入口函数进行清理,包括全局变量析构、堆销毁、I/O关闭等,然后进行系统调用结束进程。

I/O的覆盖范围:包括文件、管道、网络、命令行、信号等,广义讲I/O指代任何操作系统理解为文件的事务。

文件操作:Linux叫做文件描述符,Windows叫做句柄,文件句柄总是和内核的文件对象关联,内核可通过句柄计算出内核里文件对象的地址,但并不对用户开放。

MSVCI/O初始化工作:(1)打开文件表;(2)若能够继承自父进程,则从父进程获取继承的句柄;(3)初始化标准输入输出。

11.2C/C++运行库

C运行库大致包含的功能:(1)启动与退出,包含入口函数和入口函数所依赖的其它函数;(2)标准函数,由C语言标准规定的C语言标准库所拥有的函数实现;(3)I/O;(4)堆;(5)语言实现;(6)调试。

11.3运行库与多线程

多线程运行库:提供两方面的服务(1)提供那些多线程操作的接口,比如创建线程、退出线程、设置线程优先级等函数接口;(2)C运行库本身要能够在多线程环境下正确运行。

线程局部存储TLS如果定义一个全局变量为TLS类型,只需在定义前面加上相应关键字即可,如GCC的_thread,一旦一个全局变量被定义成TLS的类型,那每个线程都会拥有这个变量的一个副本,任何线程对该变量的修改都不会影响其它线程中该变量的副本。

Windows TLS实现:Windows下一个全局变量或静态变量会被放到.data或.bss段,当我们使用_declspec(thread)定义一个线程私有变量时,编译器会把这些变量放到PE文件的.tls段,当新线程启动时,他会从进程堆中分配一块空间并将.tls段中内容复制到这块空间,因此每个线程都有自己独立的.tls副本。

CreateThread() _beginthread()_beginthread()是前者的包装,前者在动态链接下不会出现内存泄漏但在静态链接下会出现内存泄漏,因此在使用CRT时建议尽量使用 _beginthread()、_begintheradex()、_endthread()、_endthreadex() 等函数创建线程。

11.4C++全局构造与析构

第十二章 系统调用与API

12.1系统调用介绍

系统调用概念:操作系统将可能产生冲突的系统资源保护起来,阻止应用程序直接访问,这些资源包括文件、网络、IO、各种设备等,在操作系统提供的接口下这些应用程序可进行系统调用访问资源。

Linux系统调用:X86下,系统调用由080中断完成,各个通用寄存器用于传递参数,EAX寄存器用于表示系统调用的接口号,如EAX=1表示退出进程(exit)、EAX=2表示进程创建(fork)、EAX=3表示读取文件或IO(read)、EAX=4表示写文件或IO(write),每个系统调用对应内核源码中一个函数(以sys_开头),常见系统调用如下,位于/usr/include/unistd.h:

12.2系统调用原理

用户态到内核态的切换:操作系统一般通过中断来从用户态切换到内核态。

操作系统的系统调用:操作系统一般不会用一个中断号来对应一个系统调用,一般用一个或少数几个中断号来对应所有的系统调用。

Windows系统调用:Windows内核提供了数百个系统调用(被称为系统服务)但并没有公开这些系统调用,而是在这些系统调用上建立了一个API,Windows.h 包含了Windows API的核心部分,各类API如下:

子系统:又称Windows 环境子系统,他是Windows架设在API和应用程序之间的另一个中间层,使得各种平台的不同应用程序能够兼容运行环境。

3.程序员的自我修养 - 完结相关推荐

  1. 《程序员的自我修养》

    <程序员的自我修养>这本书偏底层,来来回回读了有三四遍了,每一次都有新的收获,不过很快又会忘记,所以写下了这本书从17年12月份至今的全书的笔记,留作以后自己复习. 第二章:编译和链接 源 ...

  2. 【读书笔记】程序员的自我修养总结(七)

    [读书笔记]程序员的自我修养总结(七) 标签: [编程开发] 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 说明:这是程序员的自我修养一书的读书总结,随着阅 ...

  3. 腾讯朋友力荐书籍:程序员的自我修养:链接、装载与库

    后台开发需要学习底层知识,只有底层知识掌握了,学一些中间件是信手捏来,中间件也是跑在底层的操作系统上.<<程序员的自我修养:链接.装载与库>>对学习底层知识非常有帮助,腾讯的朋 ...

  4. 程序员的自我修养,好文

    很认真的聊一聊程序员的自我修养 本文正在筹备第二版,以下是第一版主要内容 首先要谈的是,今天的话题所聊的程序员包含哪些人? 在中国,写程序,不仅仅是一种兴趣,更多的时候,还是一种普通职业和谋生工具 大 ...

  5. 【读书笔记】程序员的自我修养总结(一)

    程序员的自我修养总结(一) 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 说明:这是程序员的自我修养一书的读书总结,随着阅读的推进,逐步增加内容. 1. 关 ...

  6. 《C# 程序员的自我修养》送书活动结果公布

    截止到9月28日24:00 ,本次送书活动<C# 程序员的自我修养> 共收到150多位同学参与回复. 以下5位同学将获赠书籍一本: 夏树.Damon.水墨清华.天天.kang   以上同学 ...

  7. 很认真地聊一聊程序员的自我修养

    最近看了<浅谈程序员的英语学习>,对于文章中的观点我非常认同,英语是非常重要的,但文章站的高度还是太高,具体表述的学习方法我不是很认同,也认为不太实际,恰好之前有一篇一直没有发表的文章想重 ...

  8. Java程序员的自我修养?

    论Java程序员的自我修养,相信很多人一开始都觉得软件以后会前程似锦,可以一展才华,每一个参加Java软件开发培训班或者自学Java编程的学员都相信Java的发展前景,对于零基础学员而言自学Java软 ...

  9. 一个“程序员的自我修养”是什么?

    在<喜剧之王>中,周星驰扮演的尹天仇,一直梦想成为一名演员,而他不管是在扮演跑龙套,或者在街坊中开设演员训练班,亦或成为主角时,他对待演员的态度,始终是认真,热爱而又投入的.而那一本他随身 ...

最新文章

  1. div 自动换行_js自动打字--autotypejs
  2. 教你用TensorFlow实现神经网络(附代码)
  3. 博客园添加访问次数统计
  4. redis 用setbit(bitmap)统计活跃用户
  5. python实现维吉尼亚加密法
  6. 分享Java面试中的几个重要基础问题
  7. 【渝粤教育】国家开放大学2018年秋季 0341-21T高级英语听力(2) 参考试题
  8. C# 根据年、月、周、星期获得日期等
  9. Eclipse上安装maven插件时出错
  10. ARM太贵,80多家科技巨头悄然站队开源芯片架构RISC-V
  11. CC自定义防护验证最佳实践
  12. 指针和引用的相同与不同
  13. 服务器定时备份数据库 执行文件
  14. 【Alpha版本】十天冲刺集结令
  15. CVS常用命令速查手册
  16. rviz可视化点云_ROS_PCL+Rviz创建点云并三维显示
  17. glide源码中包含了那种设计模式_Glide源码解读(一)
  18. meshlab模型简化
  19. ubuntu20.04下源码安装hyperscan库安装记录
  20. ADS8332芯片驱动程序

热门文章

  1. 基于Python的信用评分卡模型-give me some credit数据集,AUC 0.93 KS 0.71
  2. 100寸大屏幕的影院观感 神画Q1智能影院了解一下
  3. nvd3使用(1)——多线图中的一个很隐蔽的bug
  4. 一维搜索方法/黄金分割法(附matlab代码)
  5. android--获取手机的IMSI码 并判断是中国移动\中国联通\中国电信
  6. MySQL数据库管理工具Navicat与PhpMyAdmin功能对比
  7. AES-GCM算法 Java与Python互相加解密
  8. MySQL 数据表优化设计(九):如何设计统计数据表?
  9. autorelease 释放池
  10. 前端防错以及好用小tips指南总结