20145206 《信息安全系统设计基础》期中总结
一、常用命令总结
man -k:
常用来搜索,结合管道使用。例句如下:
man -k k1 | grep k2 | grep 2
搜索同时含有k1和k2,且属于系统调用。
最后的数字意味着帮助手册中的区段,man手册共有8个区段,最常用的是123,含义如下:
1.Linux
2.系统调用
3.c语言
但是当单独用man语句的时候,想查看其中的单独某个区段内的解释时,用法是这样的:
man 3 printf
即查找c语言中printf的用法。
grep -nr
这条语句可以用来查找关键字,全文搜索,并且可以直接查找文件内的内容。其中:
n:为显示行号
r:为递归查找
例如,如果想查找某个宏,我们已知宏保存在include文件夹中,所以可以使用下列语句:
grep -nr XXX /usr/include(XXX为所要找的宏)
cheat
cheat是非常好用的“打小抄”搜索工具,能够方便的告诉你你想要的内容。
二、常用工具
vim
vim是一种非常好用的编辑器,总共有六种基本模式,最常用的是普通模式、插入模式和命令行模式。需要熟悉这三种模式之间的切换方式:
普通→插入: i 或 a
插入→普通: Esc 或 Ctrl + [
普通→命令行: :
命令行→普通:Esc 或 Ctrl + [
常用的进入、保存和退出指令:
进入:vim 文件名
保存:命令行模式 :w
退出:命令行模式 :q
常用动作:
删除:dd删除整行
复制:yy复制整行
粘贴:p
※实用功能:交换上下行——ddp,快速交换光标所在行与它下面的行。
vim的功能很强大,并且可以移植到很多种不同的程序中,但是我们现在使用的过程中真正用到的命令很少也很简单.
gcc
常用选项
-c 只编译不链接,生成目标文件.o
-S 只编译不汇编,生成汇编代码
-E 只进行预编译,不做其他处理
-g 在可执行程序中包含标准调试信息
-o file 将file文件指定为输出文件
-v 打印出编译器内部编译各过程的命令行信息和编译器的版本
-I dir 在头文件的搜索路径列表中添加dir目录
编译过程
预处理:gcc –E hello.c –o hello.i; gcc –E调用cpp 生成中间文件
编 译:gcc –S hello.i –o hello.s; gcc –S调用ccl 翻译成汇编文件
汇 编:gcc –c hello.s –o hello.o; gcc -c 调用as 翻译成可重定位目标文件
链 接:gcc hello.o –o hello ; gcc -o 调用ld** 创建可执行目标文件
-o后面是接的你给生成的文件指定的名字,如果不指定,则默认为a.out
在命令行上运行这个可执行目标文件需要输入它的名字:
./a.out
其中./代表当前目录。
gdb
注意:使用GCC编译时要加“-g”参数,然后才能够用gdb调试
GDB最基本的命令有:
gdb programm(启动GDB)
l 查看所载入的文件
b 设断点
info b 查看断点情况
run 开始运行程序
bt 打印函数调用堆栈
p 查看变量值
c 从当前断点继续运行到下一个断点
n 单步运行(不进入)
s 单步运行(进入)
quit 退出GDB
四种断点:
1.行断点
b [行数或函数名] <条件表达式>
2.函数断点
b [函数名] <条件表达式>
3.条件断点
b [行数或函数名] <if表达式>
4.临时断点
tbreak [行数或函数名] <条件表达式>
另外的调试工具:
cgdb,有单独的debug窗口,更方便查看
ddd,图形页面
Make和Makefile
这是实现自动化编译的好方法。
Makefile的一般写法:
一个Makefile文件主要含有一系列的规则,每条规则包含以下内容:
需要由make工具创建的目标体,通常是可执行文件和目标文件,也可以是要执行的动作,如‘clean’;
要创建的目标体所依赖的文件,通常是编译目标文件所需要的其他文件。
创建每个目标体时需要运行的命令,这一行必须以制表符TAB开头
格式为:
test(目标文件): prog.o code.o(依赖文件列表)
tab(至少一个tab的位置) gcc prog.o code.o -o test(命令)
.......
即:
target: dependency_files
command
定义变量的两种方式:
(1)递归展开方式
VAR=var
(2)简单方式
VAR:=var
使用变量的格式为:
$(VAR)
三、课本重要知识点总结
静态库与动态库
静态库:.a(linux)、.lib(windows)
动态库:.so(linux)、.dll(windows)
静态库(以linux为例)
创建该库:
gcc -c addvec.c multvec.c
ar rcs libvector.a addvec.o multvec.o
涉及到的参数所做动作:
gcc -c只编译,不连接成为可执行文件。
即:把.c文件编译成.o文件
ar -r:在库中插入模块(替换)
-c:创建一个库
-s:写入一个目录文件索引到库中
即:把两个.o文件归档成静态库存档文件.a并且写入目录文件索引到库中
创建它的可执行文件
gcc -02 -c main2.c
gcc -static -o p2 main2.o ./libvector.a
相关参数含义:
gcc -02 和-0都是代码优化指令,可以减少编译时间
-c 只编译,不连接成为可执行文件
-static 告诉编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件
-o 命名生成文件
动态库(linux)
构造创建共享库:
gcc -shared -fPIC -o libvector.so addvec.c multvec.c
参数解析:
-fPIC 指示编译器生成与位置无关的代码
-shared 指示链接器创建一个共享的目标文件
-o 命名生成文件
把.c文件编译成为.o文件,放入新建的共享库中,并且命名。
链接程序
gcc -o p2 main2.c ./libvector.so
创建一个可执行目标文件p2,在运行时可以和动态库libverctor.so链接。
第一章 计算机系统漫游
这一章介绍了一些基本的概念,是以后各章的总括,提到的内容都在之后的各章中拆开细讲,我认为这章最重要的就是一句话:
信息=位+上下文
计算机中的信息都是有二进制数字表达的,而因为这些二进制位所处的位置不同,是有符号数还是无符号数,是大端法还是小端法,由于具体的解释不同,造成的结果也不同。
之后的学习就是如何读写位,和上下文如何对应。
第二章 信息的表示和处理
这章里我觉得最容易混淆的是小端法和大端法。常用小端法,巧记方式是“高对高,低对低”,但是同时要注意字节在存放的时候的高低与我们惯常认知中的高低位的关系,以及一串数据中几位代表一个字节:
一个字节是8位,也就是两位十六进制数。
然后是整数中,有符号数和无符号数的表示,补码表示,位运算和逻辑运算,溢出、截断和扩展;
浮点数中,二进制小数,最重要的是IEEE小数:
IEEE浮点标准:
用V=(-1)^s X 2^E X M 来表示一个数:
符号:s决定这个数是正还是负。0的符号位特殊情况处理。
阶码:E对浮点数加权,权重是2的E次幂(可能为负数)
尾数:M是一个二进制小数,范围为1~2-ε或者0~1-ε(ε=1/2的n次幂)
编码规则:
单独符号位s编码符号s,占1位
k位的阶码字段exp编码阶码E
n位小数字段frac编码尾数M(同时需要依赖阶码字段的值是否为0)
两种精度
单精度(float),k=8位,n=23位,一共32位;
双精度(double),k=11位,n=52位,一共64位。
三种被编码情况
规格化的
非规格化的
特殊值
还有舍入方式:
1.向偶舍入(默认方法)
即:将数字向上或向下舍入,是的结果的最低有效数字为偶数。
能用于二进制小数。
2.向零舍入
即:把整数向下舍入,负数向上舍入。
3.向下舍入
正数和负数都向下舍入。
4.向上舍入
正数和负数都向上舍入。
默认的(即向偶舍入)方法可以得到最接近的匹配,其余三种可用于计算上界和下界。
第三章 程序的机器级表示
这一章和之前学习过的汇编很像,需要注意的就是寻址方式和几个操作,mov,push,pop,leal,还有跳转指令、控制转移指令等等。
几个比较新的容易遗忘的知识点是:
翻译循环
汇编中可以用条件测试和跳转组合起来实现循环的效果,但是大多数汇编器中都要先将其他形式的循环转换成do-while格式。
1.do-while循环
通用形式:
do
body-statement
while(test-expr);
循环体body-statement至少执行一次。
可以翻译成:
loop:
body-statement
t = test-expr;
if(t)
goto loop;
即先执行循环体语句,再执行判断。
2.while循环
通用形式:
while (test-expr)
body-statement
GCC的方法是,使用条件分支,表示省略循环体的第一次执行:
if(!test-expr)
goto done;
do
body-statement
while(test-expr);
done:
接下来:
t = test-expr;
if(!t)
goto done:
loop:
body-statement
t = test-expr;
if(t)
goto loop;
done:
归根究底,还是要把循环改成do-while的样子,然后用goto翻译。
3.for循环
for循环可以轻易的改成while循环,所以再依照上面的方法改成do-while再翻译即可。
寄存器使用惯例
程序寄存器组是唯一能被所有过程共享的资源。
这个惯例是为了防止一个过程P调用另一个过程Q时寄存器中的值被覆盖。惯例如下:
%eax,%edx,%ecx 调用者保存寄存器(Q可覆盖,P的数据不会被破坏)
%ebx,%esi,%edi 被调用者保存寄存器(Q在覆盖这些值前必须压入栈并在返回前回复他们)
%ebp,%esp 惯例保持
%eax用来保存返回值
帧栈结构
这里最基本的是要知道,针对每一句汇编语句,它都干了些什么,寄存器里的值如何变化,栈帧如何变化,内存中的值如何变化,更要明白的是每一行的变化都代表着什么。
我现在欠缺的就是这一点,解读每一句是对存储器或者寄存器做了什么动作不难,但是要明白为什么这么做,这么做对应着高级编程语言中的什么,就显得生疏,需要多加练习。
第四章 处理器体系结构
这章里学习的是一个相对简单的处理器Y86,指令集比起IA32省略了很多。学习中我觉得一个比较不容易掌握,做题时需要反复查阅的是:
指令的字节级编码
每条指令需要1-6个字节不等,每条指令的第一个字节表明指令的类型。
1.第一个字节
这个字节分为两个部分,每个部分4位:
高四位:代码部分,值域为0~0xB
第四位:功能部分,功能值只有在一组相关指令共用一个代码时才有用。
比如:课本第233页,Y86指令集的功能码:
整数操作里代码部分均为6,功能部分区分addl,subl,andl,xorl
分支指令里代码部分均为7
传送指令里代码部分均为2
这里要注意rrmovl归到了传送指令里,前面说过它们有相同的指令代码
8个程序寄存器中每个都有相应的0~7的寄存器标识符,程序寄存器存在CPU中的一个寄存器文件中,这个寄存器文件就是一个小的、以寄存器id作为地址的随机访问存储器。
当需要指明不应访问任何寄存器时,用ID值0xF表示
2.有的需要额外的字节
(1)附加的寄存器指示符字节
指定一个或两个寄存器,例如rA或者rB。
没有寄存器操作数的指令,例如分支指令和call指令,就没有寄存器指示符字节。
只需要一个寄存器操作数的指令(irmovl,pushl,popl)将另一个寄存器指示符设为0xF
(2)附加的4字节常数字
这个字的用处:
1.irmovl的立即数数据
2.rmmol和mrmovl的地址指示符的偏移量
3.分支指令和调用指令的目的地址
注意事项
1.分支指令和调用指令的目的地址是一个绝对地址
2.所有整数采用小端法编码
指令编码这一部分可以帮助我们理解机器码与汇编代码的对应关系,并且涉及到下一个我认为的难点:
Y86的顺序实现
这里的六个阶段和每个阶段进行的动作,得出的数据和对应的结果看起来似乎眼花缭乱,但是课本上已经给出了部分常用命令的具体过程,课后的作业题中也有过类似的作业,这要求我们的举一反三的能力。
至于HDL语言,在EDA课程中已经有过基础,看起来倒是比较轻松。
第六章 存储器层次结构
涉及到ROM,RAM,磁盘的几个计算,总线等等。概念理解起来比较简单,计算只需要套公式,注意不要漏算盘面或者柱面就好。
这一章我觉得最重要的是局部性。
局部性原理:
一个编写良好的计算机程序,常常倾向于引用临近于其他最近引用过的数据项的数据项,或者最近引用过的数据项本身。
分类:
时间局部性
空间局部性
量化评价一个程序中局部性的简单原则:
重复引用同一个变量的程序有良好的时间局部性
对于具有步长为k的引用模式的程序,步长越小,空间局部性越好
对于取指令来说,循环有好的时间和空间局部性。循环体越小,循环迭代次数越多,局部性越好。
还有存储器层次结构,这里最重要的思想是这句话:每层存储设备都是下一层的“缓存”。
这样就涉及到了缓存命中和不命中的情况,还有替换策略:
1.缓存命中
当程序需要第k+1层的某个数据对象d时,首先在当前存储在第k层的一个块中查找d,如果d刚好缓存在第k层中,就称为缓存命中。
该程序直接从第k层读取d,比从第k+1层中读取d更快。
2.缓存不命中
即第k层中没有缓存数据对象d。
这时第k层缓存会从第k+1层缓存中取出包含d的那个块。如果第k层缓存已满,就可能会覆盖现存的一个块
覆盖——替换/驱逐
替换策略:
随机替换策略-随机牺牲一个块
最近最少被使用替换策略LRU-牺牲最后被访问的时间距离现在最远的块。
3.缓存不命中的种类
(1)强制性不命中/冷不命中
即第k层的缓存是空的(称为冷缓存),对任何数据对象的访问都不会命中。
(2)冲突不命中
由于一个放置策略:将第k+1层的某个块限制放置在第k层块的一个小的子集中,这就会导致缓存没有满,但是那个对应的块满了,就会不命中。
(3)容量不命中
当工作集的大小超过缓存的大小时,缓存会经历容量不命中,就是说缓存太小了,不能处理这个工作集。
然后是高速缓存存储器。
高速缓存是一个高速缓存组的数组,它的结构可以用元组(S,E,B,m)来描述:
S:这个数组中有S=2^s个高速缓存组
E:每个组包含E个高速缓存行
B:每个行是由一个B=2^b字节的数据块组成的
m:每个存储器地址有m位,形成M=2^m个不同的地址
除此之外还有标记位和有效位:
有效位:每个行有一个有效位,指明这个行是否包含有意义的信息
标记位:t=m-(b+s)个,唯一的标识存储在这个高速缓存行中的块
组索引位:s
块偏移位:b
高速缓存的结构将m个地址划分成了t个标记位,s个组索引位和b个块偏移位。
四、收获
转眼间本学期已经过去了一半,觉得自己最大的收获就在于独立学习能力的提高。虽然从上学期的Java就开始进行每周自学写博客这种学习方式,但是明显感觉到这学期适应了很多,开学初看到这本比上学期还厚的《深入理解计算机系统》确实觉得难度很大,翻看了里面的内容,汇编、EDA、C语言......多种学科的混合,但我没有被这些困难吓住而止步不前,而是每周看老师给出的教学进程进行每周的学习,这样一周周下来,我惊喜地发现其实我的自学能力有了很大的提高,每周老师给出的重点题目都会认真去做,不太懂的题目会和周围的小伙伴讨论,通过课后习题的完成来熟悉本章节的内容。
通过这门课的学习,觉得自己抓重点的能力也有所提升,通常一周学习一章,而一周的内容通常都有几十页有时甚至上百页,这样多的学习任务乍一看觉得很难完成,但是如果通过课后题来看这章的重点的话就会轻松不少,也许好几页的课本只是围绕着一个公式进行讲解,通过课后题会用并理解就可以了。
这半学期的学习带给我的不仅仅是知识,而是更为重要的学习能力,希望在之后的半学期里我可以收获到更多~
五、不足
觉得最大的不足就在于不够深入地理解每一个知识点,每周的学习基本都是根据老师画的重点题目来的,有些题目里没有提到的知识点我也许会看也不看,重点题目也会看答案,了解一下是怎样的计算过程,也许有时都不会去追究为什么要这样做,更多的是去想老师会怎么考,这样是不利于知识点的掌握的,以后会多加注意,深入理解。
六、课程建议和意见
因为觉得自身动手实践能力比较薄弱,所以想说,老师可不可以在课堂上多增加一些实际操作演练环节,让我们看一看操作起来一步步要怎样去做,效果如何,这样可能会更直观一些,印象也会更深刻。而理论知识的讲解,就我个人而言,可能是学的不够透彻,有些并不能完全理解,所以我觉得转换成实际操作可能会更容易理解一些~