深入理解计算机系统 读书笔记

csapp 不多说了。早就该读完了

计算机系统漫游

信息就是位 + 上下文

程序被程序翻译成不同的格式

了解编译系统

优化系统性能

看懂链接报错

避免安全漏洞

处理器读并解释存储在存储器中的指令

典型的系统硬件组织

总线bus 传word

IO

内存 DRAM

cache SRAM

程序结构和执行

信息的表示和处理

信息存储

寻址和字节顺序

大端小端

01 05 64 94 04 08 add %eax, 0x8049464

整数和浮点的不同表达

布尔袋鼠 ,布尔环,位运算

无符号与二进制补码 -128 127

无论哪种解码方式, 都是字节码的解释而已

扩展一个数字的位表示

零扩展

符号扩展

浮点

程序的机器级表示 基于IA32

反汇编

数据格式

字word 16位 double word 32位

三种GAS后缀,movb 字节 movw字 movl双字

操作指示符 operand

立即数 immediate $0x1F

寄存器 eax

存储器引用 地址

数据传送指令

mov

movl $0x4050, %eax

立即数 ->寄存器

movl %ebp, %esp

寄存器->寄存器

movl (%edi,%ecx), %eax

内存->寄存器

movl $-18, (%esp)

立即数->内存

movl %eax, -12(%ebp)

寄存器->内存

push %ebp

subl $4, %esp

movl %ebp,(%esp)

pop

movl (%esp), %eax

addl $4, %esp

算术和逻辑操作

加载有效地址 lea aka load effective address 我还以为是leave

xor %eax ,%eax 赋0

指令

效果

描述

leal S, D

D = &S

加载有效地址

incl/decl/negl/notl D

D=D@1

加一减一

add/sub/imul/xor/or/and S,D

控制

条件码

cmp test set

跳转

jmp je jg jl ja jb

跳转表

过程

栈帧

转移控制

call leave ret

;leave

movl %ebp, %esp

popl %ebp

寄存器使用惯例

%ebp %esp 必须保存

%eax %edx %ecx调用者保存寄存器caller save %ebx %esi %edi 被调用者保存寄存器 callee save

递归

数组?

movl (%edx, %eax, 4), %eax

指针运算 假设数组E的起始地址和索引i分别在%edx和 %ecx中

表达式

类型

汇编

E

int *

xe

movl %edx, %eax

E[0]

int

M[xe]

movl (%edx), %eax

E[i]

int

M[xe + 4i]

movl (%edx, %ecx, 4), %eax

&E[2]

int *

xe+8

leal 8(%edx), %eax

*(&E[i] + i)

int

M[xe+ 4i + 4i]

movl (%edx, %ecx), %eax

&E[i]-E

int

i

movl %ecx, %eax

数组循环

减少imul

优化递增变量

异类数据结构

编译时算好偏移

理解指针

越界

浮点算术

浮点寄存器

栈表达式求值用到的寄存器

c中嵌入汇编

处理器体系结构

定义一个简单的指令集,就叫它Y86吧

%eax, %ecx $edx %esi $edi %esp %ebp

条件码 ZF SF OF

程序计数器PC

存储器,内存,数组代替

汇编设计

mov按照mov的类别设计 irmovl(立即数 →寄存器) rrmovl(寄存器→寄存器) rmmovl(寄存器→内存) mrmovl (内存→寄存器)

opl addd sub and xor

jxx

call ,ret

pushl, popl

逻辑电路

布尔逻辑

存储器和时钟控制

集合关系

Y86的顺序实现

取指,fetch 从PC中拿到地址,从地址抽出指令指示符 icode ifun

解码decode 从寄存器里读

执行 execute 算术逻辑单元(ALU)执行指令指明的动作(ifun)或者移动栈指针或者跳转

访问内存memory

写回寄存器

更新PC

阶段

opl rA, rB

subl %edx, %edx

取指

icode:ifun <- M[PC]rA:rB <- M[PC+1]valP <- PC+2

icode:ifuicode:ifun <- M[0x00c] = 6:1rA:rB <- M[0x00d] = 2:3valP <- 0x00c+2 = 0x00e

解码

valA <- R[rA]valB <- R[rB]

valA <- R[%edx] = 9valB <- R[%ebx] = 21

执行

valE <- valB op valASet CC

valE <- 21 - 9 = 12ZF <- 0, SF <- 0 , OF <- 0

访存

写回

R[rB] <- valE

R[rB] <- valE = 12

更新PC

PC <- valP

PC <- valP=0x00e

其他流程类似,不过是获取值通过ALU实现

一种实现

时序

后面在设计电路了。理解PC

流水线的通用原理

时钟驱动组合逻辑

流水线过深,收益反而下降

指令控制

指令重排

预测PC

分支预测

hazard

data hazard

读写阶段不同造成寄存器错误

条件码

PC

存储器写回

control hazard

解决方案

stall nop nop 吞吐降低

转发

第四章真是太他妈复杂了。讲cpu 指令集 流水线怎么设计

优化程序性能

优化编译器的能力和局限

*xp += *yp; *xp+=&yp;和 *xp += 2* *yp 在xp=yp时优化效果不同,因为编译器必须假定指针使用不同的寄存器memory aliasing

表示程序性能,CPE

消除循环

循环不变量优化,经典问题了。

减少调用

消除不必要的寄存器引用

循环中访问指针可能不会被优化,导致多次访问指针。

降低循环开销

转换到指针代码

通过指针改善数组效率?

提高并行度

循环展开

这种优化的性能提升有上限。为什么?

分支预测和预判错误处罚

理解内存性能

save & load

设计。数据结构和算法影响性能

消除连续函数调用(浪费堆栈)以及计算放到循环外,不必要的寄存器引用以及引入临时变量保存中间结果

循环展开,迭代分割,数组换指针

确认和消除性能瓶颈

profiling -pg

内存层次结构

存储技术

DRAM 实现内存

SRAM 实现cache 弥补cpu内存之间的的差距,带来局部性

局部性

利用局部性

内存层次结构

cache 读成cash,卧槽,我一直读错了??

cache的命中,缓存策略,置换策略LRU等等。这里的概念讲的很少。有专门的书讲这个

cache

cache set, cache line

direct-mapped cache

字选择,

行替换,击中直接替换当前行

这里细节比较复杂,还好有图说明。可以找出pdf来看。这里不解释了。看不懂。后面还有书来讲这个,到时候仔细看

set associative cache组相联

行替换,LRU/LFU

fully associative cache

做TLB

怎么写

写回write back 需要标志位dirty bit记录cache block是否被修改过

直写 write through

写不命中

写分配or写不分配

L1 icache dcache L2 university cache

性能指标

命中率/不命中率

命中时间

不命中惩罚 miss penalty

影响因素

cache越大越容易命中,但是增加命中时间

块儿越大命中率高一些,提高空间局部性,但是占用大,行数少,降低时间局部性

相连度,高相联度带来慢的速度,增加命中时间,但是高相联度冲突不命中影响低

L1直接映射,L2组项联,L3直接映射

写策略,直写快但是浪费,写回带宽高但是复杂

下层cache多用写回

cache-friendly

cache 对性能的影响

在系统上运行程序

链接

链接基本上都看过了。简单抽取新点子。不详细记录了

静态链接

符号解析

重定位

符号表

bss 记成better save space

name mangling,直译确实很糟糕。毁坏。。这个毁坏不是destroy,是把布压碎的那种毁坏。意译好一点或者别翻译了

全局符号处理,强弱符号。符号覆盖(hook)

符号依赖,注意库在后面。放在前面就被忽略

异常控制流

异常处理

异常表

类别

原因

异步?

返回行为

中断

IO设备的信号

异步

总是返回到下一条指令

陷阱 syscall

有意的异常

同步

总是返回到下一条指令

故障 缺页错误

潜在可恢复的错误

同步

可能返回到当前指令

终止 硬件错误

不可恢复的错误

同步

不会返回

异常号

描述

异常类型

0

除法错误

故障

13

一般保护故障

故障

14

缺页

故障

18

机器检查

终止

32~127

操作系统定义的异常

中断或陷阱

128

系统调用

陷阱

129~255

操作系统定义的异常

中断或陷阱

进程,多任务,抢占。时间片

用户模式和内核博士

/proc 访问内核数据 /proc/pid/maps老朋友 了

上下文切换

中断污染cache

系统调用和错误处理,错误会返回-1且标记errno,记得处理

进程控制

创建/终止,获取pid等等

fork细节

创建一次返回两次

并发执行

相同但是独立的地址空间,基于COW

文件共享,注意关闭

回收子进程

加载运行程序

信号

信号处理问题

信号被阻塞,直接返回

待处理的信号不会排队等待

系统调用可以被中断

有的系统被中断不重新运行

sigaction和setjmp处理信号。

号码

名字

默认行为

相应事件

1

SIGHUP

终止

终端挂起

2

SIGINT

终止

键盘中断

3

SIGQUIT

终止

键盘退出

4

SIGILL

终止

非法之灵

5

SIGTRAP

终止并dumping core

跟踪陷阱

6

SIGABRT

终止并dumping core

abort函数信号

7

SIGBUS

终止

bus err

8

SIGPFE

终止并dumping core

浮点异常

9

SIGKILL

终止

杀死程序

10

SIGUSR1

终止

用户自定义

11

SIGSEGV

终止并dumping core

段错误

12

SIGUSR2

终止

用户自定义

13

SIGPIPE

终止

向一个没有读的pipe里写,broken pipe

14

SIGALRM

终止

alarm函数信号

15

SIGTERM

终止

软件中指

16

SIGSTKFLT

终止

栈故障

17

SIGCHLD

忽略

子进程暂停or终止

18

SIGCONT

忽略

继续进程如果被暂停或终止

19

SIGSTOP

停止,直到下一个SIGCONT

不来自终端的暂停

20

SIGTSTP

停止,直到下一个SIGCONT

来自终端的暂停

21

SIGTTIN

停止,直到下一个SIGCONT

后台从终端读

22

SIGTTOU

停止,直到下一个SIGCONT

后台向终端写

23

SIGURG

忽略

socket紧急

24

SIGXCPU

终止

CPU时间限制超出

25

SIGXFSZS

终止

文件大小限制超出

26

SIGVTALRM

终止

虚拟定时器期满

27

SIGPROF

终止

剖析定时器期满

28

SIGWINCH

忽略

窗口大小变化

29

SIGIO

终止

某个描述符上可执行IO操作

30

SIGPWR

终止

电源故障

操作进程工具

strace 分析调用

ps看进程 top看资源

/proc

测量程序执行时间

主要介绍了采集数据的方法 counter,多次测量,基于gettimeofday精确时间等。非常粗略

虚拟内存

虚拟寻址 MMU做转换

VM 虚拟地址数组 映射VP

VP三个状态,未分配,缓存,未缓存

DRAM比SRAM慢十倍,相比缓存不命中要昂贵很多

全相联,任何虚拟页VP可以放在任何物理页PP中

替换策略很重要,替换错虚拟页代价(处罚)非常高

总是写回

页表

在上面的场景下,VP都在分配磁盘,可能缓存在内存中,(在内存中也可能有对应的物理页也有可能没有),管理各种VP,需要页表

子概念,页表项。注意下图的包含关系

页命中,上图中击中DRAM页,就会分配物理页,也就是命中

不命中,就是缺页,page fault,假如上图中击中vp3,就会牺牲一个vp,上面场景,没被访问的vp4倍淘汰,更新vp3

局部性,良好工作机不会产生磁盘流量,以及内存颠簸thrashing,频繁换入换出

VM 管理功能

多进程不同页表,物理页可共享

简化链接,简化共享代码段,简化内存分配,简化加载

VM作为内存保护工具

页表,页表项标志位控制,以及段错误保护

地址翻译

结合cache和VM内容较细,不讨论

利用TLB(translation lookaside buffer,不是透明大页)加速地址翻译,缓存一波vpe,代替走l1 cache

多级页表

内存映射

动态内存分配,以及碎片

分配器设计

伙伴系统

垃圾收集

标记清除法

有向可达图

常见内存错误

写错误地址,读未初始化内存,栈溢出,指针引用错误,误解指针运算,引用不存在的对象, 内存泄漏等等

程序间的交互与通信

这部分内容和一些书重复。简单列举一些没注意过的/重要的点

系统级IO

一切都是文件

size_t ssize_t系统函数多用ssize_t, 错误处理返回值是-1

read write的正确用法,这段代码很常见了

ssize_t readn(inf fd, void *usrbuf, size_t n){

size_t nleft = n;

ssize_t nread;

char *bufp = usrbuf;

while (nleft > 0) {

if ((nread = read(fd, bufp, nleft)) < 0) {

if (errno = EINTR)

nread = 0;

else

return -1;

} else if(nread == 0)

break;

nleft -= nread;

bufp += nread;

}

return (n - nleft);

}

ssize_t writen(inf fd, void *usrbuf, size_t n){

size_t nleft = n;

ssize_t nwritten;

char *bufp = usrbuf;

while (nleft > 0) {

if ((nwritten = write(fd, bufp, nleft)) < 0) {

if (errno = EINTR)

nwritten = 0;

else

return -1;

} else if(nread == 0)

break;

nleft -= nwritten;

bufp += nwritten;

}

return n;

}

读取元数据

共享文件

描述符表 文件表 vnode表

子进程集成fd,注意关闭

IO重定向 dup2

网络编程

cs模型

网络api

socket api

socket拿到fd

connect阻塞 对应tcp哪个阶段?

bind server端绑定

listen,server使用,等着

accept,server端真正的接受了client。accept返回前在tcp哪个阶段?

web服务器以及http事务

状态码

get post 等api

并发编程

基于进程的并发模型

进程间IPC

处理子进程

处理SIGCHILD,在handler里waitpid

fork之前的fd需要关闭两遍

基于IO多路复用的并发模型

select

while select, 也不高效

事件驱动,写好handler,缺点是比较分散比较复杂

基于线程的并发模型

创建线程,detach干活。浪费

共享变量?需要线程之间同步,信号量,锁等等

基于线程的IO多路复用

并发性问题

线程安全

共享变量保护

不可重入函数

静态变量指针?

竞争

死锁

ref