【CSAPP】lab_in_CSAPP
本博客为CMU的15-213/15-513 Introduction to Computer Systems (ICS)的配套教材CSAPP(深入理解计算机系统)的lab笔记
开始前的准备
下载self-study-handout
在Linux中解压handout文件
由于该课程和lab所需环境是Linux,我们需要把下载的tar压缩包解压到Linux中。
以解压第一个lab——datalab为例,在命令行中执行以下命令,将tar压缩包解压到当前目录下:1
tar -xvf datalab-handout.tar
解压后,会在当前目录下生成一个datalab-handout的文件夹,里面包含了我们所需要的所有东西。
关于CentOs换源
问题背景
刚好这天tz过期了,暂时懒得续费,可是直接安装又太慢了,所以想能不能换个源。
结果不知道是不是误操作了,原本的repo被我删了。导致我续费了tz之后,执行yum命令总是显示:1
2
3
4
5
6
7
8Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
There are no enabled repos.
Run "yum repolist all" to see the repos you have.
To enable Red Hat Subscription Management repositories:
subscription-manager repos --enable <repo>
To enable custom repositories:
yum-config-manager --enable <repo>
然后执行yum repolist all
,显示的是所有repo的status都是disabled.1
2
3
4
5
6
7
8
9
10Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
repo id repo name status
C7.0.1406-base/x86_64 CentOS-7.0.1406 - Base disabled
C7.0.1406-centosplus/x86_64 CentOS-7.0.1406 - CentOSPlus disabled
C7.0.1406-extras/x86_64 CentOS-7.0.1406 - Extras disabled
C7.0.1406-fasttrack/x86_64 CentOS-7.0.1406 - Fasttrack disabled
C7.0.1406-updates/x86_64 CentOS-7.0.1406 - Updates disabled
C7.1.1503-base/x86_64 CentOS-7.1.1503 - Base disabled
C7.1.1503-centosplus/x86_64 CentOS-7.1.1503 - CentOSPlus disabled
很懵逼。
解决方法
首先用cd /etc/yum.repos.d/
进入cd /etc/yum.repos.d/
目录,再执行wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
,把源换成ali的源。
关于make的错误
在着手做datalab前,首先看这个lab的readme,然后尝试跑一遍给出的示例。然后在跑make的时候,出现了这样的错误:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27gcc -O -Wall -m32 -lm -o btest bits.c btest.c decl.c tests.c
In file included from /usr/include/features.h:399:0,
from /usr/include/stdio.h:27,
from btest.c:16:
/usr/include/gnu/stubs.h:7:27: fatal error: gnu/stubs-32.h: No such file or directory
include <gnu/stubs-32.h>
^
compilation terminated.
In file included from /usr/include/features.h:399:0,
from /usr/include/stdio.h:27,
from decl.c:1:
/usr/include/gnu/stubs.h:7:27: fatal error: gnu/stubs-32.h: No such file or directory
include <gnu/stubs-32.h>
^
compilation terminated.
In file included from /usr/include/features.h:399:0,
from /usr/include/limits.h:26,
from /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/limits.h:168,
from /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/syslimits.h:7,
from /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/limits.h:34,
from tests.c:3:
/usr/include/gnu/stubs.h:7:27: fatal error: gnu/stubs-32.h: No such file or directory
include <gnu/stubs-32.h>
^
compilation terminated.
make: *** [btest] Error 1
解决方法
然后我在stackoverflow
上搜索关于这个问题的帖子,很快就发现了这样的回答(节选):
You’re missing the 32 bit libc dev package:
On Ubuntu it’s called libc6-dev-i386 - do sudo apt-get install libc6-dev-i386. See below for extra instructions for Ubuntu 12.04.
On Red Hat distros, the package name is glibc-devel.i686 (Thanks to David Gardner’s comment).
On CentOS 5.8, the package name is glibc-devel.i386 (Thanks to JimKleck’s comment).
On CentOS 6 / 7, the package name is glibc-devel.i686.
我的系统是CentOs7,于是我按照他给解决方法的安装了一下开发包:1
sudo yum install glibc-devel.i686
然后再跑make,又遇到了这样的报错:1
2
3
4
5gcc -O -Wall -m32 -lm -o btest bits.c btest.c decl.c tests.c
/usr/bin/ld: skipping incompatible /usr/lib/gcc/x86_64-redhat-linux/4.8.5/libgcc_s.so when searching for -lgcc_s
/usr/bin/ld: cannot find -lgcc_s
collect2: error: ld returned 1 exit status
make: *** [btest] Error 1
ok,这次我直接去问newbing了。他给出的回答也很对口:
您遇到的编译错误是因为您在64位的系统上使用-m32选项来编译32位的程序,但是链接器找不到对应的32位的库文件,而只能找到64位的库文件,所以它跳过了不兼容的库文件,并报告无法找到-lgcc_s这个库文件12。您应该检查您的系统是否安装了32位的开发库,比如glibc-devel.i686和libgcc.i6863。您可以使用以下命令来安装这些库文件:
1 | sudo yum install glibc-devel.i686 libgcc.i686 |
原来是我还少了一个libgcc.i686的包,同样安装好之后,make就可以成功运行啦!1
2
3gcc -O -Wall -m32 -lm -o btest bits.c btest.c decl.c tests.c
gcc -O -Wall -m32 -o fshow fshow.c
gcc -O -Wall -m32 -o ishow ishow.c
datalab
bitxor
使用~和&实现异或1
2
3
4
5
6
7
8
9
10/*
* bitXor - x^y using only ~ and &
* Example: bitXor(4, 5) = 1
* Legal ops: ~ &
* Max ops: 14
* Rating: 1
*/
int bitXor(int x, int y) {
return ~(~(x&~y)&~(~x&y));
}
原理解释:1
x^y = (x&~y)|(~x&y)
tmin
1 | /* |
知识背景补充:
在32位系统中,
01111111 11111111 11111111 11111111 即表示 2147483647
负数时最高位为1,负数的补码为对应的原码部分取反加1,故有:
-1 即 10000000 00000000 00000000 00000001
取反后为 11111111 11111111 11111111 11111110 再加1得补码为 11111111 11111111 11111111 11111111
;
-2 即 10000000 00000000 00000000 00000010
取反后为 11111111 11111111 11111111 11111101 再加1得补码为 11111111 11111111 11111111 11111110
;
依次类推…
-2147483647 即 11111111 11111111 11111111 11111111
取反后为 10000000 00000000 00000000 00000000 再加1得补码为 10000000 00000000 00000000 00000001
;
-2147483648 的补码即 10000000 00000000 00000000 00000000
(别想为什么找不出原码是多少了,正是因为原码表示不出来才有补码来表示的)
所以综上,32位系统补码的范围: 10000000 00000000 00000000 00000000 ~ 01111111 11111111 11111111 11111111
所以32位下二进制补码最小值是1000 …… …… …… …… 0000
,十六进制表示即0x8000
,也就是 1<<31
。
tmax
1 | /* |
原理解释:同上,可知最大值为0111 …… …… …… …… 1111
,十六进制表示即0x7fff
。
可以发现0x7fff
+1再取反也就是0x7fff
本身。所以我们可以通过判断~(x+1)^x
是否为0来判断x
是否为最大值。
但是这样会有一个问题,就是0xffffffff
也会被判断为最大值,所以我们需要再加一个判断条件,即!(x+1)
,这样就可以判断出x
是否为最大值了。
出现的小问题
1 | make btest |
这个警告的原因是,编译器认为你的表达式中的|运算符可能会造成优先级的混淆。|运算符的优先级低于+、~和^运算符,但是高于!运算符。因此,你的表达式相当于!(~((x+1)^x)|(!(x+1))),而不是你可能想要的!((~(x+1))^x|!(x+1))。为了避免这个警告,你可以在|运算符的两边加上括号,明确表达你的意图,例如!((~(x+1)^x)|(!(x+1)))。
allOddBits
1 | /* |
原理解释:1
0xaaaaaaaa = 10101010101010101010101010101010
negate
1 | /* |
原理解释:1
-x = ~x+1
isAsciiDigit
1 | /* |
原理解释:1
2
30x30 = 00110000
0x39 = 00111001
判断一个 ASCII 码字符是否为数字字符 '0' 到 '9',函数的实现使用了位运算,其中 lowerBound 和 upperBound 分别表示数字字符的最小和最大 ASCII 码值,diff1 和 diff2 分别表示字符与最小值和最大值的差值,isGreaterEqualLowerBound 和 isLessEqualUpperBound 分别表示字符是否大于等于最小值和小于等于最大值(算术右移31位,左侧补符号位,判断结果是1还是0)。最后,函数返回值为两个判断条件的逻辑与运算结果。
conditional
1 | /* |
原理解释:1
x?y:z = (x&y)|(~x&z)
isLessOrEqual
1 | /* |
原理解释:1
2x<=y = (x-y)<=0
需要考虑溢出问题,所以需要判断x和y的符号位是否相同,如果相同,再判断x-y的符号位是否为负数。而且在判断x-y的时候,用的是(x+~y)>>31,而不是(x+~y+1)。
logicalNeg
1 | /* |
原理解释:1
!x = x==0
howManyBits
1 | /* |
原理解释:
首先,我们用右移 31 位的操作来得到 x 的符号位,我们用 flag 来存储这个结果,flag 是 0 或者 -1。
然后,我们用按位非操作来得到 flag 的取反,也就是 ~flag,这个结果是 -1 或者 0。
接下来,我们用按位与操作来把 x 和 ~flag 做运算,也就是 (~flag) & x,这个结果是 x 或者 0。这是因为,如果 x 是正数,那么 flag 是 0,~flag 是 -1,也就是全是 1 的二进制,所以 (~flag) & x 就相当于 x & -1,也就是 x。如果 x 是负数,那么 flag 是 -1,~flag 是 0,也就是全是 0 的二进制,所以 (~flag) & x 就相当于 x & 0,也就是 0。
然后,我们用按位非操作来得到 x 的取反,也就是 ~x,这个结果是 -x - 1。这是因为,按位非操作的规则是,把每一位都翻转,所以 ~x 就相当于 x 的补码,也就是 -x - 1。
接下来,我们用按位与操作来把 flag 和 ~x 做运算,也就是 flag & (~x),这个结果是 0 或者 -x - 1。这是因为,如果 x 是正数,那么 flag 是 0,所以 flag & (~x) 就相当于 0 & (~x),也就是 0。如果 x 是负数,那么 flag 是 -1,也就是全是 1 的二进制,所以 flag & (~x) 就相当于 -1 & (~x),也就是 ~x。
最后,我们用按位或操作来把 (~flag) & x 和 flag & (~x) 做运算,也就是 ((~flag) & x) | (flag & (~x)),这个结果是 x 或者 -x。这是因为,如果 x 是正数,那么 (~flag) & x 是 x,flag & (~x) 是 0,所以 ((~flag) & x) | (flag & (~x)) 就相当于 x | 0,也就是 x。如果 x 是负数,那么 (~flag) & x 是 0,flag & (~x) 是 -x - 1,所以 ((~flag) & x) | (flag & (~x)) 就相当于 0 | (-x - 1),也就是 -x - 1。但是,我们要注意,-x - 1 其实就是 x 的绝对值,因为 -x - 1 的补码就是 x。所以,这个写法也可以得到 x 的绝对值。
第二步,我们计算用二进制表示 x 需要多少位。我们可以用一个技巧,就是用二分搜索的方法来找到 x 中最高位是 1 的位置。我们可以把 x 分成两半,看看它的高 16 位是否有 1,如果有,就说明我们需要至少 16 位来表示 x,如果没有,就说明我们只需要低 16 位就够了。我们可以用逻辑非操作来判断 x 的高 16 位是否有 1,因为逻辑非操作的规则是,如果输入是 0,就得到 1,如果输入不是 0,就得到 0。所以,如果我们把 x 右移 16 位,然后用逻辑非操作,就可以得到一个 0 或者 1 的结果,表示 x 的高 16 位是否有 1。然后,我们再用左移 4 位的操作,就可以得到一个 0 或者 16 的结果,表示我们需要的最少位数。我们用 b16 来存储这个结果,然后再把 x 右移 b16 位,就相当于把 x 的高 16 位去掉了,只留下了低 16 位。接下来,我们重复这个过程,把 x 分成两半,看看它的高 8 位是否有 1,如果有,就说明我们需要再加 8 位,如果没有,就说明我们不需要再加。我们用 b8 来存储这个结果,然后再把 x 右移 b8 位,就相当于把 x 的高 8 位去掉了,只留下了低 8 位。我们继续这个过程,分别用 b4, b2, b1, b0 来存储 x 的高 4 位,高 2 位,高 1 位,和最低位是否有 1 的结果,然后分别把 x 右移相应的位数,直到 x 变成 0。最后,我们把 b16, b8, b4, b2, b1, b0 都加起来,就得到了 x 的二进制表示需要的位数。但是,这还不够,因为我们还要考虑符号位,无论 x 是正数还是负数,我们都需要用一位来表示它的符号,所以我们还要再加 1,才能得到最终的结果。
floatScale2
1 | /* |
原理解释:
需要用到浮点数IEEE-754规则的知识,按下不表,只说说实现上的小细节。
实现2更贴近IEEE-754的具体结构,1+8+23。他把各个组成部分分离出来分别进行判断和处理。实现1则是用相对来说比较复杂的掩码来进行判断和处理。
实现1非常简洁,常数0x800000的二进制是 0000 0000 1000 0000 0000 0000 0000 0000 0000,也就是只有第 9 位是 1,其他位都是 0。这样,+ 运算的结果就是把 uf 的指数部分加一,然后把符号位、指数部分和尾数部分重新组合成一个新的浮点数,然后返回这个结果。
floatFloat2Int
1 | /* |
原理解释:
floatPower2
1 | /* |
最终结果
1 | make clean |
bomblab
前言
phase_1
字符串
phase_2
等比数列
phase_3
条件判断跳转对应数对
phase_4
二分查找+条件判断
func4
func4是一个二分查找算法,它的功能是在一个有序的数组中,查找一个给定的值,并返回它在数组中的索引位置。如果没有找到这个值,就返回一个负数,表示它应该插入的位置。它的参数是:
- 第一个参数是要查找的值,存放在%edi寄存器中。
- 第二个参数是查找范围的下界,存放在%esi寄存器中。
- 第三个参数是查找范围的上界,存放在%edx寄存器中。
它的返回值是查找结果,存放在%eax寄存器中。
func4的逻辑是这样的:
- 首先,它将查找范围的上界和下界相减,得到一个差值,存放在%eax寄存器中。
- 然后,它将这个差值右移31位,得到一个符号位,存放在%ecx寄存器中。
- 接着,它将这个符号位和差值相加,再右移一位,得到一个平均值,存放在%eax寄存器中。
- 然后,它将这个平均值和查找范围的下界相加,得到一个中间值,存放在%ecx寄存器中。
- 接着,它将这个中间值和要查找的值进行比较,如果相等,就返回中间值作为索引位置;如果不等,就根据比较结果缩小查找范围,并递归调用func4函数。
- 最后,在递归返回时,它将返回值左移一位,并加一,得到最终的返回值。
我希望这能帮助你理解func4的功能和逻辑。如果你还有其他问题,欢迎继续提问。😊
phase_5
phase_6
secret_phase
树
结构体
对齐
十六进制
保留位
:0x6030f0
这个节点是一个树形结构的节点,它由四个32位整数组成,分别表示节点的值,左子节点的地址,右子节点的地址和保留位。这个节点的地址是0x6030f0,它的值是24,左子节点的地址是0x603110,右子节点的地址是0x603130,保留位为0。这个节点占用32个字节,所以它需要两行来显示,第一行显示前16个字节,第二行显示后16个字节。第二行的地址是第一行的地址加上16个字节,所以是0x603100。你可以把这个节点看作一个结构体,例如:
1 | struct node { |
在目录下创建一个txt文件,把拆除的炸弹密码都放进去。一次性读档还原进度。1
./bomb solved.txt
cachelab
相关资料:
part a
问题分析
定义构造cache和cache_line的结构
实现时间戳和LRU算法
实现cache的具体运作逻辑
指令解析
- getopt.h
part b
相关资料:https://zhuanlan.zhihu.com/p/484657229转置过程中的矩阵A、矩阵B和cache的关系
cache(s=5,E=1,b=5),E=1说明映射方法是直接映射。
s=5,可知一共有32组,每行有2^5=32个字节,可以存储8个int
blocking 的原理
充分利用cache加载的内容,减少miss的次数,提高效率。
对角线问题
A与B对角线上的块在缓存中对应的位置是相同的,而它们在转置过程中位置不变,所以复制过程中会发生相互冲突。
以A的一个对角线块p,B与p相应的对角线块q为例,复制前, p 在缓存中。 复制时,q会驱逐p。 下一个开始复制 p 又被重新加载进入缓存驱逐 q,这样就会多产生两次miss。
所以我们为了避免这种情况,可以尝试将最后一层循环展开,利用变量先存储已经访问的目标,再进行复制。
valgrind
valfrind是一个用来检测内存错误的工具,它可以检测出内存泄漏、使用未初始化的内存、访问已经释放的内存等错误。它的原理是在程序运行时,将程序的所有内存访问映射到一个虚拟内存中,然后通过检测虚拟内存的访问情况来检测内存错误。