第一章 计算机系统漫游
1、大部分现代计算机系统都使用ASCII 标准来表示文本字符
2、八位一字节
3、\n的ascii码为10,6进制0x0a
4、像hello.c这样只有ACSii字符构成的称为文本文件,所有其他文件都被称为二进制文件。
5、程序被编译的过程[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s5wuxfyE-1617068293520)(C:\Users\40704\AppData\Roaming\Typora\typora-user-images\image-20210302170727520.png)]
- 预处理阶段:预处理器cpp读取系统的头文件的内容,并把它直接插入到系统文本中。得到.i 文件
- 编译阶段
- 汇编阶段
- 链接阶段
6、P4、5页的问题的答案
7、处理器的核心是大小为一个字的存储设备,称为程序技术器(PC)。在任何时刻,pc都指向主存中的某条机器语言指令
8、pc的位数?
9、操作系统实现一个cpu并发执行多个程序的机制为:上下文切换。
10、Amdahl定律——加速比S
11、操作系统内核是应用程序和硬件之间的媒介,他提供三个基本的抽象:1)文件是对i/o设备的抽象,2)虚拟内存是对主存和磁盘的抽象,3)进程是处理器,主存和i/o设备的抽象。
第二章 信息的表示和处理
- 无符号编码基于传统的二进制表示法
- 补码编码是表示有符号整数的最常见的方式,有符号整数就是可以为正或者为负的数字。
- 浮点数编码是表示实数的科学计数法的以2为基数的版本。
- 大多数计算机使用8位的块,或者字节,作为最小的可寻址的内存单位,而不是访问内存中单独的位
- 尽管c编译器维护着这个类行信息,但是他生成的实际机器级程序并不包含关于数据类行的信息(??????????)
- 0x或0X开头为十六进制
- 一个字节的值域0x00~0xFF
- 十六进制大小写一样。
- A——1010,C——1100,E——1110
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-shOpKLox-1617068293521)(C:\Users\40704\AppData\Roaming\Typora\typora-user-images\image-20210310155229264.png)]
- char *在32位和64位的字节数,float的字节数,long的字节数
- unsigned 关键字表示是否为有符号变量,char默认为什么根据编译器。
- 数据在地址中的存储顺序:大端法——顺着,高位在低地址。小端法——低位在低地址。
- 理解P32的不同数据在不同机器下跑出的结果。
- 字符串的结尾默认为\n,strlen()函数计算的长度不包括\n
- unicode,utf-8,ascii的概念
- a^a=0,利用异或运算可以实现翻转
- 注意掩码的技巧:x&0xFF,(x^0xFF),x^(0xFF)
- 逻辑运算符如果第一个参数求职就能确定表达式结果,那么逻辑运算符就不会对第二个参数求职
- 移位运算:逻辑左移,逻辑右移,算术右移。算术右移对有符号整数数据的运算有帮助。
- 几乎所有编译器都对有符号数使用算术右移。
- 二进制用后缀字母B,十六进制用H
- 进制的小数转换
- 补码编码的运算,注意公式
- 截断和拓展的变换
- 注意有符号整数的最小数的表示,注意范围
- 判断是大端机器还是小端机器的方法。
- 对于常数的乘法, C 编译器自动生成移位和加法代
码 - 无符号整数除法:逻辑右移,有符号:算术右移
- 由于整数乘法比移位和加法的代价要大的多,许多C语言编译器试图以移位、加法和减法的组合来消除很多整数乘以常数的情况。
- 除以二的补码除法,注意使用偏置值
重要例题:2.10 2.11 2.12 2.13 2.19 2.21 2.24
第三章 程序的机器级表示
处理器的发展历程
程序编码过程
- C预处理器(cpp)拓展源代码,插入所有用#include命令指定的文件,并拓展所有用#define声明指定的宏.生成.i中间文件。
- 编译器(cc1)产生俩个源文件的汇编代码(.s文件)
- 汇编器(as)将汇编代码转化成二进制目标代码文件。
- 链接器将俩个目标代码文件与实现库函数的代码合并,并产生最终的可执行代码文件。
目标代码是机器代码的一种形式,它包含所有的二进制表示,但还没有填入全局变量的地址。
可执行代码是机器代码的第二种形式,也就是处理器执行的代码格式。
机器级编程的抽象
- 第一种是有ISA来定义机器级程序的格式和行为,它定义了处理器状态、指令的格式,以及每条指令对状态的影响。
- 第二种抽象是,机器级程序使用的内存地址是虚拟地址,提供的内存模型看上去是一个非常大的字节数组
x86-64含有的寄存器
- 程序计数器——PC,%rip
- 整数寄存器
- 条件码寄存器
- 一组向量寄存器
程序的内存包含:程序的可执行代码,操作系统需要的一些信息,用来管理过程调用和返回时栈,以及用户分配的内存块
一些工具
gcc
注意大小写
- -Og,-O1,-O2:优化级别
- -o : 设定输出文件,不加的话会默认生成a.out
- -S:生成汇编文件
- -c:生成目标代码文件(.o)
objdump
objdump -d mstore.o
汇编代码的格式
AT&T:gcc和objdump的默认格式
intel格式
二者有一定区别
通用目的寄存器
也叫整数寄存器
规则
- 生成1字节和2字节的指令会保持剩下的字节不变
- 生成4字节的指令会把高位4个字节置为0。
分类
- %rax存返回值
- %rip存栈指针
- %rdi,%rsi,%rdx依次存参数
- 其他用于调用者保存和被调用者保存
操作数指示符
操作数分类
- 立即数——$+c语言语法的整数;(无0x就是10进制)
- 寄存器
- 内存引用
寻址模式
比例变址寻址:一个立即数偏移;基址寄存器;变址寄存器;比例因子(只能为1,2,4,8)
常用指令
数据传送指令
movb,movw,movl,movq,movabsq
常规的movq指令只能以表示32位补码数字的立即数作为源操作数,然后把这个值符号拓展得到64位的值,放到目的位置。movabsq指令可以以任意64位立即数作为源操作数,并且只能以寄存器为目的。
movz,movs
movz为零扩展数据传输指令,movs为符号扩展指令;
没有movzlq指令,通过movl实现;
cltq
cltq把%eax扩展到%rax
//找错
movb $0xF,(%ebx)
movw (%rax),4(%rsp)
movq %rax,$0x123
压入和弹出栈指令
pushq,popq
注意二者的等价形式
算术和逻辑操作
加载有效地址
leaq
一元操作符
inc,
加减乘除异或
addq,subq,xor
移位运算符
sal,sar,shr
移位运算符也有单操作数形式,相当于移动1位.(3.26)
特殊的算术操作
imulq,mulq,cqto,idivq,divq
imulq指令有两种形式,双操作数和单操作数
分为imulp和mulp
单操作数乘法指令用于计算俩个64位值的全128位乘积,要求一个存放在%rax中,另一位为源操作数.
乘积结果高64位存放在%rbx中,低64位在%rax中.(低高位是指字节地址,反应在寄存器就是(%rsi)和8(%rsi))
控制
条件码
cmp*,test*
cmp只设置条件码而不改变任何其他寄存器
cmp与sub指令行为一样
test与and指令一样
典型的test用法:testq %rax,%rax来测试是否为0
访问条件码
跳转
跳转指令的编码
当执行相对寻址时,程序计数器的值是跳转指令后面的那条指令的地址.而不是跳转指令本身的地址.------体现在机器码上
用条件传送来实现条件分支
cmov*
基于条件数据传送的代码会比基于条件控制转移的代码性能要好.(原因:流水线)
循环
do while循环和while循环
while循环
while循环的编译优化级别不同,得到的汇编代码不同.
for循环
switch
switch语句可以根据一个整数索引值进行多重分支.
使用跳转表数据结构.
和使用很长的if-else语句相比,使用跳转表的优点是执行开关语句的时间和开关情况的数量无关.
gcc根据开关情况的数量和开关情况值的稀疏程度来翻译开关语句,当开关情况数量比较多,并且值的范围跨度比较小时,就会使用跳转表.
jmp *.L4(,%rsi,8) //汇编中利用跳转表的语句. *:间接跳转 比例变址寻址
switch语句中,会先对输入的n根据跳转情况进行一定的调整,然后比较在一定的范围,剩下的根据跳转表跳转,跳转表对重复情况的处理是使用同一代码标号,对缺失的情况使用默认的标号.
.rodata //只读数据
ja //无符号数大于
过程
过程P调用过程Q,Q在执行后返回到P。这个过程包括下面一个或多个机制:传递控制,传递数据,分配和释放内存。
当x86-64过程需要的存储空间超出寄存器能够存放的大小时,就会在栈上分配空间,这个部分称为栈帧。
x86-64可以通过及攒机最多传递6个整形参数。
通过栈传递参数时,所有的数据大小都向8的倍数对其。
栈上的局部储存
局部数据必须储存在内存中的情况.
- 寄存器不足够存放所有的本地数据
- 对一个局部变量使用&
- 某些局部变量是数组或结构,因此必须能够通过数组或结构引用被访问到.
->和.的区别
(*rp).width=rp->width
如果定义的结构体是指针,那么使用->.如果不是,则通过.来访问成员变量.
数据对齐
任何K字节的基本对象的地址必须是K的倍数.
内存越界和缓存区溢出
缓存区溢出:在定义数组后,会为其在栈上分配相应的空间,如果后续的写入等操作超过数组大小,会导致其他区域被改写
防护措施
栈随机化
每次使用的栈的位置都发生改变,可以防止恶意攻击的代码找到栈,进行修改.
栈破坏检测
在栈帧中任何局部缓存区与栈状态之间存储一个特殊的金丝雀值.
限制可执行代码区域
限制哪些内存区域能够存放可执行代码.
浮点代码
- 过程P调用过程Q,Q在执行后返回到P。这个过程包括下面一个或多个机制:传递控制,传递数据,分配和释放内存。
- 当x86-64过程需要的存储空间超出寄存器能够存放的大小时,就会在栈上分配空间,这个部分称为栈帧。
- 注意call指令过程中,pc值和栈指针的变化。
- x86-64可以通过及攒机最多传递6个整形参数。
- 通过栈传递参数时,所有的数据大小都向8的倍数对其。
- 16个整数寄存器的分类。
- short **U[6]的大小
- 定长数组和变长数组在编译器层面的优化
- 数据对齐:
- &和leaq指令
- 3.46的知识点.
- 缓存区溢出,栈随机化,金丝雀值(栈保护),限制哪部分内存可以储存可执行代码。
- long *p; p++; 的汇编代码是什么? 存放指针的寄存器加多少?
重要例题:3.41;3.42;3.44***;3.46***;
第四章 处理器体系结构
Y86-64
程序员可见状态
程序寄存器,条件码,PC,虚拟内存,状态码
Y86-64指令
- 指令编码长度从1个字节到10个字节不等
- Y86-64的算术指令中不能使用立即数,需要将常量先加载到寄存器中
- .pos伪指令告诉从某某地址开始, .align 8指令告诉按8字节边界处对其
一些指令的详情
pushq %rsp //将栈指针压入栈
虽然之前章节说这条等价于
pushq %rbp
sub $8,%rsp
movq %rbp,(%rsp)
但是实际存入栈的还是%rsp的原地址,而不是减8后的.
更深层次的原因是在于后面SEQ的顺序中讲到,在译码阶段,%rsp的值已近被保存到valA中,后续的访存阶段也用的是这个值,%rsp因为-8而减小是在执行阶段,影响的是valE的值.
popq %rsp
/**/
movq (%rsp),%rax
add $8,%rsp
RICS和CISC
CISC:复杂指令集计算机
RISC:精简指令集计算机
x86-64属于CISC
ARM属于RICS
HCL
存储器和时钟
时钟寄存器
存储单个位或字.时钟信号控制寄存器加载输入值.
Y86-64用时钟寄存器保存程序计数器(PC),条件码寄存器(CC),程序状态(Stat).
随机访问存储器
包括:a.虚拟内存,b.寄存器文件.(Y86-64有15个程序寄存器)
寄存器文件(又叫寄存器堆)
- 寄存器文件有俩个读端口,还有一个写端口.(不一定只有一个写端口)
- 读端口有地址输入srcA和srcB
- 数据输出valA,valB
- 写端口有地址输入****dstW,数据输入valW
读过程:
当srcA或srcB被设成某个寄存器ID时**,在一段延迟后**,存储在相应寄存器的值就会出现在valA或valB上.
写过程:
每次时钟上升时,输入valw上的值会被写入输入dstW上的寄存器ID指示的程序寄存器.当dstW设为特殊的ID值0xF时,不会写任何程序寄存器.
SEQ处理阶段
取指(Fetch)
- 根据PC值提取第一个字节,分为俩个4位的数.分别被icode,ifun控制逻辑块读取.
- 根据icode值,可以得到三个一位的信号,instr_valid(是否合法的指令),need_regids(指令是否包括寄存器指示符字节),need_vals(指令是否包括常数字)
- imem_error信号.
- Align硬件单元会处理剩下的9个字节,将他们放入寄存器字段,和常数字.
译码(Decode)
对指令字段译码,产生寄存器文件使用的四个寄存器表示符----dstE,dstM,srcA,srcB
//HLC for srcA in SEQ
word srcA = [
icode in {IRRMOVQ, IRMMOVQ, IOPQ, IPUSHQ}: rA; #注意IOPQ和IPUSHQ
icode in {IPOPQ, IRET}: RRSP; #注意IRET
1 : RNONE;
]
执行(execute)
执行阶段包括ALU,ALU功能根据alufun信号,对输入值,进行ADD,SUBTRACT,AND或EXCLUSIVEOR运算.
ALU的输出就是valE信号.
//HLC for ALUA in SEQ
word aluA = [
icode = { } : valA;
icode = { } : valC;
icode = { } : -8;
icode = { } : 8;
#other instructions don't need ALU; #which?..............待填
]
word alufun =[
]
word set_cc = icode in { IOPQ };
访存(memory)
word mem_addr = [
]
bool mem_read = icode in {IMRMOVQ, IPOPQ, IRET };
word Sata = [
]
写回(write back)
#待分析
word dstE = [
icode in {IRRMOYQ, IIRMOVQ, }: rB; #IMRMOVQ的特殊,之后看............待填irmovq
icode int {IPOPQ, }
]
更新PC(PC update)
word new_pc = [
:valC;
:valC;
:valM;
:valP;
]
各种指令的流程跟踪
rrmovq | irmovq | mrmovq | rmmovq | OPq | ||
---|---|---|---|---|---|---|
取指 | icode:fun | icode:ifun=M1[PC] | icode:ifun=M1[PC] | icode:ifun=M1[PC] | icode:ifun=M1[PC] | icode:ifun=M1[PC] |
rA:rB | rA:rB=M1[PC+1] | rA:rB=M1[PC+1] | rA:rB=M1[PC+1] | rA:rB=M1[PC+1] | rA:rB=M1[PC+1] | |
valC | valC=M8[PC+2] | valC=M8[PC+8] | valC=M8[PC+8] | |||
valP | valP=PC+2 | valP=PC+10 | valP=PC+10 | valP=PC+10 | valP=PC+2 | |
译码 | valA,srcA | valA=R[rA] | valA=R[rA] | valA=R[rA] | ||
valB,srcB | valB=R[rB] | valB=R[rB] | valB=R[rB] | |||
执行 | valE=valB OP valA | valE=0+valA | valE=0+valC | valE=valB+valC | valE=valB+valC | valE=valB OP valC |
Cond. Codes | set CC | |||||
访存 | Read/write valM | valM=M8[valE] | M8[valE]=valA | |||
写回 | E port, dstE | R[rB]=valE | R[rB]=valE | R[rb]=valE | ||
M port,dstM | ||||||
更新PC | PC | PC=valP | PC=valP | PC=valP | PC=valP | PC=valP |
由于内存可能为变址寻址操作数,mrmovq和rmmovq也需要rB
pushq | popq | jxx | call | ret | ||
---|---|---|---|---|---|---|
取指 | icode:fun | icode:ifun=M1[PC] | icode:ifun=M1[PC] | icode:ifun=M1[PC] | icode:ifun=M1[PC] | icode:ifun=M1[PC] |
rA:rB | rA:rB=M1[PC+1] | rA:rB=M1[PC+1] | rA:rB=M1[PC+1] | |||
valC | valC=M8[PC+1] | valC=M8[PC+8] | ||||
valP | valP=PC+2 | valP=PC+2 | valP=PC+9 | valP=PC+10 | ||
译码 | valA,srcA | valA=R[rA] | valA=R[%rsp] | valA=R[rA] | ||
valB,srcB | valB=R[%rsp] | valB=R[%rsp] | valB=R[rB] | |||
执行 | valE=valB OP valA | valE=valB+(-8) | valE=valB+8 | valE=valB+valC | ||
Cond. Codes | Cnd=Cond(CC,fun) | |||||
访存 | Read/write valM | M8[valE]=valA | valM=M8[valA] | M8[valE]=valA | ||
写回 | E port, dstE | R[%rsp]=valE | R[%rsp]=valE | |||
M port,dstM | R[rA]=valM | |||||
更新PC | PC | PC=valP | PC=valP | PC=Cnd?valC:valP | PC=valP |
- pushq要用到valB,注意写回和访存操作?
- popq为什么要用valA,valB存同样的? valE为什么用valB? 访存写回为什么这样做?
- jxxq和callq的取指指令
- jxxq的执行阶段和PC update阶段
流水线
SEQ+
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YbTdQKsx-1617068293522)(C:\Users\40704\AppData\Roaming\Typora\typora-user-images\image-20210329152031963.png)]
pipe-
- 更新PC提前
- selectA是在valA和valP之间选择
- AlUA的值是在valC和valA之间选择
流水线冒险
原因
方法
- 用暂停来避免数据冒险
- 用转发来避免数据冒险
- 加载/使用数据冒险
- 避免控制冒险
面试相关
- 进程和线程的区别
- 解释孤儿进程,僵死进程,惊群效应
- 进程间通信方式
- malloc、realloc、calloc的区别
- malloc与free的实现原理
- 内存管理,mmu实现
- kill一个进程的过程是什么样子的
- Unicode与utf-8的区别
- 大端小端是什么?能否用c++代码简单测试下当前os是大端还是小端的?
- 如果内存只有1G,但是进程要占2G是否可以?
- 内存对齐:内存对齐了解吗?给一个结构体,包含char、int、指针,sizeof有多大,为什么?如果想紧凑对齐,应该怎么做?
- 内存分布
- char和int的强制转换
- C++,左值,右值
- float 和 double 是怎么存储的?结合大小端说一下