快捷搜索:  汽车  科技

程序员的自我修养源代码(程序员的自我修养-链接)

程序员的自我修养源代码(程序员的自我修养-链接)1. CPU频率到了4GHz被天花板所限,于是向多核发展ii. SMP与多核b) 万变不离其宗i. 硬件架构1. 虽然日新月异,但架构基本没变,即CPU,内存与IO

程序员的自我修养源代码(程序员的自我修养-链接)(1)

1, 温故而知新

a) 从Hello World说起

i. 一些基本问题

1. 可能是你没有想过的,比如没有操作系统hello world可以运行吗?hello world是从哪开始执行的,main函数之前和之后发生了什么?为什么从main开始执行。

b) 万变不离其宗

i. 硬件架构

1. 虽然日新月异,但架构基本没变,即CPU,内存与IO

ii. SMP与多核

1. CPU频率到了4GHz被天花板所限,于是向多核发展

2. 可以把SMP与多核看成同一概念

c) 站得高,望得远

i. 所有计算机科学领域的任何问题都可以通过增加一个中间层来解决

ii. 应用程序—[操作系统API]--运行库—[系统调用,即中断号]—操作系统—[硬件规格]—硬件

d) 操作系统做什么

i. 不要让CPU打盹

1. CPU资源很宝贵,单任务-》多任务-》操作系统加入管理进程

ii. 设备驱动

1. 对各种硬件设备进行抽象

iii. 内存不够怎么办

1. 原始的直接分配有三个问题

a) 空间不隔离,可以篡改别人内容,不安全

b) 内存使用效率低

c) 程序运行地址不确定

2. 关于隔离

a) 思想:每个进程使用虚拟空间

3. 分段

a) 两个程序虚拟地址一样,但会被分到不同物理地址段上,这样上述(1)(3)问题就解决了

4. 分页

a) 分成固定大小页(如4K),如果虚拟页不在物理内存就发生“页错误”,然后由操作系统接管从磁盘装入内存

b) 从“虚拟页”到“物理页”的转换部件是MMU,处于CPU内部

iv. 众人拾柴火焰高

1. 线程基础

a) 概念、权限、优先级

b) 调度,IO密集型、CPU密集 型

2. 线程安全

a) 多线程内部情况(用户线程 不等于 内核线程)

i. 一对一,一对多,多对多

程序员的自我修养源代码(程序员的自我修养-链接)(2)

2, 编译和链接

a) 被隐藏了的过程

i. 预处理、编译、汇编、链接

b) 编译器做了什么

i. 词法分析、语法分析、语义分析、中间语言生成、目标代码生成与优化

c) 链接器年龄比编译器长

i. 纸带:修改地址需要手工重定位

ii. 汇编:允许符号,自动生成代码

iii. 模块:模块间符号

d) 模块拼装------静态链接

i. 重定位就是给绝对地址引用的位置“打补丁”,使它们指向正确的地址。

程序员的自我修养源代码(程序员的自我修养-链接)(3)

3, 目标文件里有什么

a) 目标文件的格式

i. 目标文件和可执行文件的格式几乎一样 linux按ELF存储,ELF分4类:可重定位文件.o,可执行文件,共享目标文件.so,核心转储文件core dump

ii. file foobar.o

b) 目标文件是什么样的

i. 文件头:基本属性

ii. 段:.text代码段、.data数据段、.bss未初始化数据段等

iii. 段表:记录各个段的偏移位置

c) 挖掘SimpleSection.o

i. 查看段:objdump –h simpleSection.o

ii. 查看段大小:size simpleSecion.o

iii. 代码段:

1. 显示反汇编:objdump –h –d simpleSection.o

iv. 数据段和只读数据段:

1. .data段存放已经初始化的全局静态与局部静态变量

2. .rodata段存放的是只读变量和字符串变量

v. .BSS段

1. 未初始化的全局静态与局部静态变量

d) ELF文件结构描述

i. 文件头:ELF魔数、机器字长、版本、平台、段表位置和长度、段数量、程序头入口

ii. 段表

1. readelf –S SimpleSection.o

iii. 重定位表

iv. 字符串表

1. 其它地方引用字符串只给个数字下标就可以了

e) 链接的接口---符号

i. ELF符号是模块拼接的“粘合剂”

ii. .symtab段存放信号表

iii. 特殊符号

1. __executable_start程序入口等

2. 在由ld链接器的链接脚本定义

3. 程序中可以直接引用这些符号

iv. 符号修饰与函数签名

1. Linux是_Z4开头

2. c filt命令可以还原

v. 弱符号和强符号

1. 不允许强符号重复定义

2. 同时有强符号和弱符号,选择强符号

3. 同时有多个弱符号,选择占用空间最大的

4. 对于未定义的弱符号,链接器默认为0

5. 可以通过__attribute__((weakref))声明弱引用,程序中引用了编译也不会报错

6. 库中的弱符号,可以被用户自定义的弱符号覆盖,例如程序判断是否与-lpthread连接,即判断pthread_create是否为0

f) 调试信息

i. Gcc编译-g 会多出很多debug段

程序员的自我修养源代码(程序员的自我修养-链接)(4)

4, 静态链接

a) 空间与地址分配

i. .o文件合并起来,相同性质的段合并在一起

ii. 链接器为目标文件分配地址和空间包含两意思:

1. 输出在目标文件中的空间

2. 启动运行程序时装载后的虚拟地址空间(这是重点)

iii. 两步链接

1. 收集信息,简单合并

2. 符号解析与重定位(重点)

a) 虚拟空间从0x8048000开始

b) 为各个符号地址 偏移

b) 符号解析与重定位

i. 跨文件符号暂时填不了地址,需要重定位,暂时先用0替换

ii. 链接器怎么知道哪些要重定位—有个重定位表

c) COMMON块

i. 收集弱符号的地方,扫描完所有目标文件后,才能决定使用哪个弱符号

d) C 相关问题

i. 全局构造函数优先于main执行,放在.init段

ii. 析构函数在main退出后执行,放在.fini段

e) 静态库链接

i. 静态库可以简单认为是一组目标文件的集合

f) 链接过程控制

i. 一般通过链接脚本控制,默认脚本在/usr/lib/ldscripts

ii. 手工制作最“小”程序(替换main,不用C库,自定段)

1. 使用系统调用0x80实现控制台打印,用汇编实现

2. 使用系统调用0x01实现退出

3. Ld加参数-e指定程序入口(非main)

4. 自定义ld链接脚本

g) BFD库

程序员的自我修养源代码(程序员的自我修养-链接)(5)

5, Windows PE/COFF

程序员的自我修养源代码(程序员的自我修养-链接)(6)

6,

a) 进程虚拟地址空间

i. 整个4G被分成两部分,高1G给系统,低3G给进程

ii. 32位,空间能不能超过4G

1. 虚拟空间:不能超过

2. 实体内存:可以超过,32位CPU有36位物理地址

b) 装载的方式

i. 一次性读入内存—不现实

ii. 覆盖装入—需要精巧设置,太累,容易出错

iii. 页映射—需要精心设计,涉及淘汰规则等

c) 从操作系统角度看可执行文件的装载

i. 进程建立

1. 创建一个独立的虚拟空间(实际没动作,等缺页再说)

2. 建立可执行文件至虚拟空间的映射(也没分配内存)

3. CPU跳转到可执行入口

ii. 缺页错误

1. 执行虚拟地址时发现缺页,就实际分配页存,读取磁盘到内存,继续执行

d) 进程虚拟空间的分布

i. 链接器会把属性相似的放在一起,叫segment,减少碎片,提高读写效率

ii. 链接视图:section

iii. 加载视图:segment

1. Segment并不是页的整数倍

2. Segment在虚拟地址平面展开,没有地址复用

3. 相临Segment会映射于同一个物理内存页

e) Linux内核装载ELF过程简介

程序员的自我修养源代码(程序员的自我修养-链接)(7)

7, 动态链接

a) 为什么要动态链接

i. 节省内存(运行时)和磁盘(发布时)

ii. 程序开发和发布解耦

iii. 动态链接

1. 磁盘和内存只保存一份

iv. 可扩展性和兼容性

1. 插件

2. 把动态差异的东西放在动态库里

v. 基本实现

1. 可执行程序与so之间的链接由动态链接器完成

b) 简单的动态链接例子

i. 为什么编译时需要so

1. 让静态链接器知道哪些符号是静态符号,哪些符号是动态符号

ii. 动态链接程序运行时的地址空间分布

1. cat /proc/进程ID/maps

2. 程序启动时,会把控制权给动态链接器,链接完成后会归还给程序

c) 地址无关代码

i. 固定装载的问题

1. 静态共享库:操作系统在某个特定地址分出一些地址块,为已知模块预留足够空间

2. 共享对象在编译时不能假设自己在进程虚拟地址空间中的位置

ii. 装载时重定位

1. 在链接时,对所有绝对地址(动态符号)不作重定位,等模块装载完成,即目标地址确定,再把程序中所有的绝对地址引用进行重定位。

2. 数据部分使用装载时重定位没问题,但指令如果也是装载时重定位,就会出现几个程序都有共享对象的指令副本,解决不了节省内存的效果,为了解决这个问题,出现了地址无关代码-fPIC

iii. 地址无关代码

1. 使用-fPIC的目的:几个进程共用共享对象的指令内存,达到节省内存作用

2. 共享模块内部的数据和函数可以用相对偏移实现地址无关。

3. 共享模块访问外部,使用GOT表访问,这样共享模块本身就是地址无关的了

iv. 共享模块的全局变量问题

1. 共享模块内的全局变量,全部指向可执行文件中的副本,然后通过GOT访问。

d) 延迟绑定PLT

i. 访问时才查表填,并GOT表项目

ii. 后续访问直接取结果

e) 动态链接相关结构

i. 程序启动后调用的动态链接库在哪

1. 在编译时ELF已经文件头中已经指明 .interp段

ii. .dynanmic段指明依赖动态库的基本信息

iii. .dynsym段保存动态链接相关的符号

f) 动态链接的步骤和实现

i. 动态链接器自举:

1. 本身也是共享模块,“鸡生蛋,蛋生鸡”

2. 自举过程中不能调用函数,不能使用外部变量

ii. 完成自举后,装载共享对象

1. 动态链接器将可执行文件信号合并到一个合局符号表中

2. 寻找符号依赖的对象,根据.dynamic段打开so文件并加载

3. 当一个符号需要加入合局符号表时,该符号已经在在,则后加的符号被忽略。

a) 最好将内部函数使用static函数,这样不会加入合局符号表,就不会被覆盖

4. 动态连接库

a) 本身是静态链接的

b) 本身是PIC的 即地址无关,否则还处理重定位

iii. 显式运行时链接

1. dlopen/dlsym/dlerror/dlclose

猜您喜欢: