写在前面,最近工作闲暇,利用差不多一周的时间,阅读了《程序员的自我修养》
-
CPU主频率目前一直被4GHZ的天花板所限制
-
理论上讲,增加CPU的数量可以提高运算速度,并且在理想状况下,速度的提高与CPU的数量成正比。但是实际上并非如此,因为我的程序并不是都能分解成若干完全不相干的子问题。就比如一个女人可以花10个月生出一个孩子,但是10个女人并不能在一个月就是生出一个孩子。
-
线程拥有的私有存储空间:栈、线程的局部存储、寄存器(包括PC寄存器》
PC,是program counter的缩写,就是程序计数器,PC寄存器中的内容,是下一条要取的指令的16位存储单元地址,在程序执行的过程中,PC中的值会自动加一】)
-
线程同步问题,是由于非原子运算导致;线程在做计算的时候使用私有寄存器保存中间结构,完成运算后,在从寄存器中写回共享内存中,导致多线程并发修改同一个共享内存,会得到不稳定的结果(非原子运算会被打断)
-
通常将编译和链接合并到一起的操作成为构建
-
预编译 –> 编译 –> 汇编 –> 链接
- 预编译
- 删除并且展开所有宏定义
- 处理所有条件编译
- 处理#include,将包含的文件(文件目录)插入待该位置(这是个递归过程)
- 删除所有的注释
- 添加行号和文件名标识,以便报错或者告警的时候可以显示行号
- 编译
- 把预处理文件进行一系列词法分析、语法分析、语义分析以及优化后生产相应的汇编代码文件,
- 汇编
- 汇编器是将汇编代码转变成可以执行的指令,每一个汇编语句几乎都能对应一条机器指令
-
编译器就是将高级语言翻译成机器语言(目标代码)的一个工具
-
编译:词法分析、语法分析、语义分析、中间代码生成(代码优化、三地址码)、目标代码生成(平台差异)与优化
-
编译器可以将一个源代码文件编译成一个未链接的目标文件,然后由连接器最终将这些目标文件链接起来形成可执行文件
-
符号(Symbol)这个概念随着汇编语言的普及迅速被使用,它用来表示一个地址,这个地址可能是一段子程序(后来发展成函数)的起始地址,也可以是一个变量的起始地址
-
在一个程序被分割成过个模块以后,这些模块之间最后如何组合形成单一的程序;模块间符号的引用;以上即是链接过程所要解决的问题
-
把源代独立编译,然后按照需要组装起来的过程就是链接
-
链接的过程,就是把一些指令对其他模块的符号地址的引用加以修正。链接过程主要包括,地址和空间分配、符号决议(地址绑定)和重定位
-
静态链接 –> 每个模块的代码文件经过编译生成目标文件(.o文件),目标文件与库一起链接行程最终的可执行文件。最常见的库就是运行时库,它是支持程序运行的基本函数的集合。库其实是一组目标文件的包,就是一些常用代码编译成目标文件后打包存放。
-
目标文件就是源代码编译后但未进行链接的那些中间文件,它跟可执行文件的内容与结构很相似,所以一般跟可执行文件采用相同个格式存储
- 目标文件包括的内容
- 机器指令代码、数据
- 链接是所需要的一些信息,符号表、调试信息、字符串
-
一般目标文件将这些信息按不同的属性,以节(Section)的形式存储,有时候也叫段(segment)
- 段(总的来说,分为代码段和数据段)
- 代码段,.code / .text
- 数据段
- .data 存放初始化的全局变量和局部变量
- .rodata 只读数据段,存放字面值
- .bss段 存放未初始化的全局变量和局部变量
- 段【Macho】
- 代码段,__text
- 数据段
- __data 存放初始化的全局变量和局部变量(数据的实际顺序是由高到低地址)
- __cstring 只读数据段,存放字面值
- __bss段 存放未初始化的全局变量和局部变量
- 目标文件,除了主要的指令段和数据段以外还包含许多内容
-
文件头
-
段表
-
重定位表,记录了代码段或者数据段中,需要重定位的符号(需替换符号在所在段中的偏移)
-
字符串表,字符串用,用在表中的index来代替
-
符号表:函数和变量统称为符号,函数名或变量名就是符号名,变量和函数的地址,就是符号
-
SYMBOL TABLE:
000000000000006c l 0e SECT 02 0000 [.data] _main.static_var
0000000000000120 l 0e SECT 04 0000 [.bss] _main.static_var2
0000000000000000 g 0f SECT 01 0000 [.text] _func1
0000000000000068 g 0f SECT 02 0000 [.data] _global_init_var
0000000000000030 g 0f SECT 01 0000 [.text] _main
0000000000000004 01 COM 00 0200 _global_uninit_var
0000000000000000 g 01 UND 00 0000 _printf
-
-
-
C语言的源代码文件中所有全局的变量和函数经过编译后,相对应的符号名称前加上下划线”_”
- 链接过程一般分为两步
- 空间与地址分配:合并所有输入的目标文件的段和符号表,并重新建立映射关系
- 符号解析与重定位
-
程序是如何使用操作系统提供的API。在一般的情况下,一种语言的开发环境往往会附带有语言库。这些库就是对操作系统的API的包装
-
程序执行和装载
-
创建一个进程
-
创建一个独立的虚拟地址空间,创建虚拟地址空间实际上只是分配一个页目录
-
读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系;当操作系捕获到缺页错误时,它应该知道程序当前所需要的页在可执行文件中的哪一个位置,这就是虚拟空间与可执行文件之间的映射关系,也是传统意义上的”装载“
由于可执行文件在装载时,实际上是被映射的虚拟空间,所以可执行文件又被叫做image
-
将CPU指令寄存器设置成可执行文件入口,启动运行
-
-
进程虚存控件分布
- 将可执行文件中的段(Section)按照权限合并,以减少内存浪费
- 以代码段为代表的权限为可读执行的段
- 以数据段和BSS段为代表的权限为读可写的段
- 以只读数据段为代表的权限为只读的段
- 可执行文件中以段(Section)为单元划分、装载程序以段(Segment)划分;一个装载的段(页)可能包含合并后多个Section;总的来说,”segment”和“section”是从不同的角度来划分同一个ELF文件,这个在ELF中被称为不同的视图(View),从“Section”的角度来看ELF文件就是链接视图(linking View),从“Segment”的角度来看就是执行视图(Excution View)。
- 将可执行文件中的段(Section)按照权限合并,以减少内存浪费
-
进程虚拟内存空间(VMA)映射关系
- 一个进程基本上可以分为几种VMA
- 代码VMA,权限只读、可执行;有映像文件(image)【可执行文件】
- 数据VMA,权限可读写,可执行;有映像文件
- 堆VMA,权限可读写、可执行;无映像文件,匿名,可向上扩展
- 栈VMA,权限可读写、不可执行;无映像文件,匿名,可向下扩展
- 一个进程基本上可以分为几种VMA
-
进程启动时的运行环境信息,例如,系统环境变量、进程运行参数,操作系统再进程启动前将这些信息保存到进程的虚拟空间的栈(也成为堆栈)中
-
静态链接的缺陷
- 在多进程操作系统中,每个进程都要链接所需的公共库函数;浪费空间
- 程序发布繁琐;如果是静态编译,那么依赖的库每次升级都需要重新链接,发布给用户
-
动态链接中,依赖的动态共享库也需要与主程序的可执行文件一起链接,需要确认目标文件中的符号是静态符号还是动态符号
-
-
优化动态链接性能
- 延迟绑定,当函数第一次被用到的时候才进行绑定
-
-
动态链接的步骤
- 启动动态连接器
- 动态链接器不可以依赖其他任何共享对象
- 动态连接器本身所需要的全局和静态变量的重定位工作有他本身完成,依靠自举(bootstrap)程序。
- 装载所有需要的共享对象
- 重定位和初始化
- 启动动态连接器
-
动态链接器
- 动态链接器本身是静态链接
- 一般是PIC,地址无关,代码段可复用
- 装载地址由操作系统决定
-
进程内存分布
-
栈保存了一个函数调用所需要的维护的信息,成为堆栈帧。
-
”烫”的由来
-
堆是申请内存空间的地方,程序可以请求一块连续的内存,并且自由使用,这块内存在程序主动放弃之前都会一直保持有效
-
- 系统调用,用于访问系统资源,包括文件、网络、IO、各种设备等
PS. 其中11章,12章系统调用原理部分、13章、以及文中涉及windows的部分没有看