第一章:计算机系统漫游
编译系统
hello.c->hello.i
gcc -E hello.c -o hello.i
hello.i->hello.s
gcc -S hello.i -o hello.s
hello.s->hello.o
gcc -c hello.s -o hello.o
hello.o->hello
gcc hello.o -o hello
第二章:信息的表示和处理
//数据在不同架构处理器下的表示
typedef unsigned char * byte_pointer;
void show_bytes(byte_pointer start, size_t led)
{
size_t i;
for (i = 0 ;i < led; i++)
{
printf("%.2x",start[i]);
}
printf("\n");
}
void show_int(int x)
{
show_bytes((byte_pointer)&x,sizeof(int));
}
void show_float(float x)
{
show_bytes((byte_pointer)&x,sizeof(float));
}
void show_pointer(void *x)
{
show_bytes((byte_pointer)&x,sizeof(void *));
}
int main()
{
int i =12345;
show_int(i);
return 0;
}
整数、浮点数在计算机内部的表示与运算
第三章:程序的机器级表示
while、do-while、for性能分析
三种都是通过条件测试和跳转指令实现的。
switch、if-else性能分析
switch通过一个跳转表实现,所以只需要执行一次跳转,而if-else需要多次跳转指令,代码效率相对低。
堆栈的分析对比
堆:一般由程序员申请控制,如C语言的malloc,地址由小到大增长,满足后入先出的原则。需要程序员自身控制内存的分配问题。一般栈的空间有限。
栈:一般由系统控制,如int a这种语句,地址由大到小递减,也满足后入先出的原则。我们C语言执行一个函数就是在栈中开辟空间执行,故函数执行完毕,会自动释放内存空间。堆的空间相对较大。
对抗缓冲区溢出攻击
-
栈随机化:每次程序运行时,程序栈位置随机,不容易被黑客进行漏洞发掘。
-
栈破坏检测:在缓冲区和栈保存的状态值之间存储一个特殊值,成为金丝雀值(随机产生
-
限制可执行代码区域:限制内存区域存放可执行代码范围
第四章:处理器体系结构
参考微嵌、数电、模电
第五章:优化程序性能
编写高效的程序
-
选择合适的算法和数据结构
在某些情况下,减少函数调用的开销
-
了解编译器的能力和局限性
-
探索并行化
投机执行
指程序执行时,遇到分支结构,计算机会预测分支结果,甚至直接执行某个分支的代码。如果预测正确,则将执行结果送到寄存器中,失败则丢弃。此处由分支模块来控制。
第六章:存储器层次结构
存储技术
寄存器—缓存(cache)—主存—外存
-
缓存:可以存在多级缓存结构,为静态随机访问存储器(SRAM),用双稳态电路逻辑状态来存储信息。
-
主存:为动态随机访问存储器(DRAM),用电容充放电来存储信息,需要不断刷新来保持信息。SDRAM同步动态随机访问存储器速度优于DDRAM。
-
外存:
-
磁盘(机械硬盘)
磁片、磁面、磁道、521字节、扇区、读/写头
磁盘取决于寻道时间+旋转时间(转速)+传送时间
-
固态硬盘(flash)
闪存芯片(plane、block、page)
读、写、擦除,只能将1改为0,不能将0改为1
写以page为单位,擦除以block为单位
-
局部性
-
时间局部性,指同一变量在一段时间被多次访问
-
空间局部性,指访问在一段连续的空间内,如数组的顺序访问
合理利用局部性原理,可以提高程序执行速度
存储器层次结构
中心思想:速度更快、容量更小的存储设备作为速度更慢、容量更大的存储设备的缓存
相邻存储器层次结构之间块的大小通常一致。
几个概念:
-
缓存命中:需要使用k+1层数据时,需要先检索k层数据,如果在k层有缓存的数据,则为命中。
-
cache的内部结构:内部为一个或多个set(组){S};每个set包含一个或多个cache line{E};每个cache由三部分组成,分别为有效位(1 bit,表示当前cache line的数据是否有效,1有效)、标记、数据块(内存数据的副本){B},数据块中前几位表示地址{m}。
直接映射高速缓存
当cache的每个组只有一个cache line时,称为直接映射。
读取数据的过程
-
组选择,根据组索引位,选择组
-
行匹配,对比cache line中的标记和地址中的标记是否一致,如果一致,表示数据一定在当前cache line中,如果有效位为0或标记不匹配,则目标数据不在次cache line中。
-
字抽取,块偏移决定目标数据在数据块中的位置。
组相联/高速缓存
第七章: 链接
链接
链接是指将可重定位目标文件以及所必要的系统文件组合起来,生成一个可执行目标文件的操作。
可重定位目标文件
每一个可重定位目标文件大致分为3部分:
-
ELF header:(ELF是可执行可链接格式的缩写)
gcc -c hello.c -o hello.o #查看hello.o的字节数 wc -c hello.o #查看hello.o的EFL header信息 readelf -h hello.o
Magic字段解释:
Type字段:REL表示为可重定位文件
-
Section:
-
.text:存放已经编译好的机器代码
-
.data:存放已初始化的全局变量和静态变量的值
-
rodata:存放只读数据(printf的格式串和switch语句中的跳转表)
-
.bss:存放未初始化的全局变量和静态变量(被初始化为0的全局变量和静态变量)(并不占用实际空间,是一个占位符)
-
.comment:存放的是编译器的版本信息
-
.symtab:Symbol Table,符号表
readelf -s hello.o
-
全部符号:由该模块定义,同时能被其他模块引用
-
外部符号:由其他模块定义,同时被该模块引用
-
局部符号:只能在该模块定义、使用
-
-
.rel.text:Relocation Table,重定位表
-
.debug:调试信息
-
.line:原始C程序的行号和.text section中机器指令之间的映射
-
.strtab:String Table,字符串表
-
-
以及Section的表:
readelf -S hello.o
符号解析
-
强符号:函数和已经初始化的全局变量
-
弱符号:未初始化的全局变量
强符号与弱符号,当有同名强符号时,链接时会报错;而同名强、弱共存时,可能会导致程序出现意想不到的错误。
静态库
archive文件是一组可重定位目标文件的集合,在Linux中以.a结尾
libc.a包含atoi、printf、scanf、strcpy、rand
构造静态库
ar rcs 库名.a ***.o ***.o
解包静态库
ar -x 库名.a
静态库的用法
-
将所需模块编译成.o文件
-
将编译成的.o文件通过ar命令打包成.a文件
-
编写模块函数的声明文件.h,并在主模块中include此文件,即可在主文件中调用此函数
-
使用gcc编译主模块时,在后面添加.a文件路径即可完成链接
静态库的解析过程
gcc -static main.o -o main ./test.a
链接器先处理main.o,然后处理test.a,最后处理libc.a,文件输入顺序非常重要,一般库放在命令结尾,如果库不是独立的,我们需要对其进行排序操作。(被调用的库在后,相互引用可多次写明静态库)
重定位
-
重定位节和符号定义
-
重定位节中的符号引用
可执行目标文件
与可重定位目标文件类似
代码运行时内存分布:
在Linux_x86_64位系统上,代码段从地址0x4000000开始,然后向上为数据段,堆在数据段之上,然后是共享区域,共享区域之上为栈(起始地址为248 -1),其上为操作系统保留。
动态链接共享库
静态库局限性
-
静态库需要定期维护和更新
-
几乎灭个C程序都需要使用标准的I/O函数
共享库
在Linux系统中以.so结尾表示,在Windows中以.dll结尾表示。
gcc -shared -fpic -o 共享库名.so ***.o ***.o
应用
-
分发软件,因为共享库在链接过程中,不是真正与主程序链接在一起,所以只需要升级动态库,在重新执行程序时,就可以完成程序升级。
-
构建高性能Web服务器,无需关闭或重启服务就能实现升级
handle = dlopen("./***.so",RTLD_LAZY)