man <command_name>
手册通常被分为8个区段,要查看相应区段的内容,就在 man 后面加上相应区段的数字即可:
1 一般命令 2 系统调用 3 库函数,涵盖了C标准函数库 4 特殊文件(通常是/dev中的设备)和驱动程序 5 文件格式和约定 6 游戏和屏保 7 杂项 8 系统管理命令和守护进程
man有一个-k 选项用起来非常好,这个选项让你学习命令、编程时有了一个搜索引擎,可以举一反三。结合后面学习的grep 命令和管道,可以多关键字查找:
man -k key1 | grep key2 | grep key3 | ...
管道是一种通信机制,通常用于进程间的通信(也可通过socket进行网络通信),它表现出来的形式就是将前面每一个进程的输出(stdout)直接作为下一个进程的输入(stdin)。
grep命令的一般形式为:
grep [命令选项]... 用于匹配的表达式 [文件]...
使用grep命令查找宏
grep -nr /usr/include
“作弊命令”,简单备忘单
它提供显示Linux命令使用案例,包括该命令所有的选项和简短但尚可理解的功能。
查看用户
$ who am i
创建用户
要创建用户需要 root 权限,要用到 sudo 这个命令。使用这个命令有两个大前提,一是要知道当前登录用户的密码,二是当前用户必须在 sudo 用户组。
$ sudo adduser 用户名
查看用户属于的用户组:
$ groups 用户名 或 $ cat /etc/group | sort
将其它用户加入 sudo 用户组:
$ su 当前用户 //获得root权限 $ groups 待添加用户 $ sudo usermod -G sudo 待添加用户 //将用户添加到sudo用户组 $ groups 待添加用户
删除用户
$ sudo deluser 待删除用户 --remove-home
FHS(英文:Filesystem Hierarchy Standard 中文:文件系统层次结构标准),定义了系统中每个区域的用途、所需要的最小构成的文件和目录同时还给出了例外处理与矛盾处理。
FHS 定义了两层规范,第一层是, / 下面的各个目录应该要放什么文件数据,例如 /etc 应该要放置设置文件,/bin 与 /sbin 则应该要放置可执行文件等等。
第二层则是针对 /usr 及 /var 这两个目录的子目录来定义。例如 /var/log 放置系统登录文件、/usr/share 放置共享数据等等。
新建
$ touch 文件名
$ mkdir 目录名
使用 -p 参数,同时创建父目录(如果不存在该父目录): $ mkdir -p father/son/grandson
复制
使用cp(copy)命令复制一个文件或目录到指定目录。
$ cp 文件名 指定目录
要成功复制目录需要加上-r或者-R参数,表示递归复制:
$ cp -r father family
使用rm(remove files or directories)命令,删除一个文件或目录:
$ rm test
使用mv(move or rename files)命令,移动文件(剪切)。
$ mv 源目录文件 目的目录
重命名文件
$ mv 旧的文件名 新的文件名:
正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。
一个正则表达式通常被称为一个模式(pattern),为用来描述或者匹配一系列符合某个句法规则的字符串。
选择:|竖直分隔符表示选择,例如"boy|girl"可以匹配"boy"或者"girl"
数量限定
范围和优先级:()圆括号可以用来定义模式字符串的范围和优先级,这可以简单的理解为是否将括号内的模式串作为一个整体。
字符 描述
\ 将下一个字符标记为一个特殊字符、或一个原义字符。
^ 匹配输入字符串的开始位置。
$ 匹配输入字符串的结束位置。
{n} n是一个非负整数。匹配确定的n次。 {n,} n是一个非负整数。至少匹配n次。 {n,m} m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。 * 匹配前面的子表达式零次或多次。 + 匹配前面的子表达式一次或多次。 ? 匹配前面的子表达式零次或一次。 [^xyz] 排除型(negate)字符集合。匹配未列出的任意字符。 [a-z] 字符范围。匹配指定范围内的任意字符。 [^a-z] 排除型的字符范围。匹配任何不在指定范围内的任意字符。
在普通模式中,用的编辑器命令,比如移动光标,删除文本等等。这也是Vim启动后的默认模式。这正好和许多新用户期待的操作方式相反(大多数编辑器默认模式为插入模式)。
在普通模式中,有很多方法可以进入插入模式。比较普通的方式是按a(append/追加)键或者i(insert/插入)键。
在这个模式中,大多数按键都会向文本缓冲中插入文本。大多数新用户希望文本编辑器编辑过程中一直保持这个模式。
在插入模式中,可以按ESC键回到普通模式。
在命令行模式中可以输入会被解释成并执行的文本。例如执行命令(:键),搜索(/和?键)或者过滤命令(!键)。在命令执行之后,Vim返回到命令行模式之前的模式,通常是普通模式。
按键 说明
h 左 l 右(小写L) j 下 k 上 w 移动到下一个单词 b 移动到上一个单词
在普通模式下使用下面的键将进入插入模式,并可以从相应的位置开始输入
命令 说明
i 在当前光标处进行编辑 I 在行首插入 A 在行末插入 a 在光标后插入编辑 o 在当前行后插入一个新行 O 在当前行前插入一个新行 cw 替换从光标所在位置后到一个单词结尾的字符
从普通模式输入:进入命令行模式,输入w回车,保存文档。输入:w 文件名可以将文档另存为其他文件名或存到其它路径下
从普通模式输入:进入命令行模式,输入wq回车,保存并退出编辑
命令 说明
:q! 强制退出,不保存 :q 退出 :wq! 强制保存并退出 :w <文件路径> 另存为 :saveas 文件路径 另存为 :x 保存并退出 :wq 保存并退出
普通模式下输入Shift+zz即可保存退出vim
进入普通模式,使用下列命令可以进行文本快速删除
命令 说明
x 删除游标所在的字符
X 删除游标所在前一个字符
Delete 同x
dd 删除整行
dw 删除一个单词(不适用中文)
d$或D 删除至行尾 d^ 删除至行首 dG 删除到文档结尾处 d1G 删至文档首部
在命令之前加上数字,表示一次删除多行,如:2dd表示一次删除2行
-c 只编译不链接,生成目标文件.o -S 只编译不汇编,生成汇编代码 -E 只进行预编译,不做其他处理 -g 在可执行程序中包含标准调试信息 -o file 将file文件指定为输出文件 -v 打印出编译器内部编译各过程的命令行信息和编译器的版本 -I dir 在头文件的搜索路径列表中添加dir目录
使用GCC编译时要加“-g”参数
行断点
b [行数或函数名] <条件表达式>
函数断点
b [函数名] <条件表达式>
条件断点
b [行数或函数名] <if表达式>
临时断点
tbreak [行数或函数名] <条件表达式>
makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至进行更复杂的功能操作。
makefile带来的好处就是——“自动化编译”,makefile文件需要按某种语法进行编写,文件中需要说明如何编译各个源文件并链接生成可执行文件,要求定义源文件之间的依赖关系。
Makefile的一般写法:
test(目标文件): prog.o code.o(依赖文件列表) tab(至少一个tab的位置) gcc prog.o code.o -o test(命令) .......
Makefile还可以定义和使用宏(也称做变量),从而使其更加自动化,更加灵活,在Makefile中定义宏的格式为:
macroname = macrotext
使用宏的格式为:
$(macroname)
缓冲区溢出漏洞:
计算机的表示法是用有限数量的位来对应一个数字编码,当结果太大不能表示时就会发生溢出。人为的溢出是有一定企图的,攻击者写一个超过缓冲区长度的字符串,植入到缓冲区这时可能会出现两种结果:一是过长的字符串覆盖了相邻的存储单元,引起程序运行失败,严重的可导致系统崩溃;另一个结果就是利用这种漏洞可以执行任意指令,甚至可以取得系统root特级权限。
最低有效字节在最前面的方式称为小端法,最高有效字节在最前面的方式称为大端法。字节顺序是网络编程的基础,小端是“高对高、低对低”,大端与之相反。
布尔代数起源于数学领域,是一个用于集合运算和逻辑运算的公式:〈B,∨,∧,¬ 〉。其中B为一个非空集合,∨,∧为定义在B上的两个二元运算,¬为定义在B上的一个一元运算。
通过布尔代数进行集合运算可以获取到不同集合之间的交集、并集或补集,进行逻辑运算可以对不同集合进行与、或、非。
所有逻辑运算都可以用与、或、非表达(最大式、最小式),而与或非可以用“与非”或“或非”表达,所以,只要一个与非门,就可以完成所有的逻辑运算。
|| OR && AND ! NOT
逻辑运算认为,所有非零参数都为TRUE,参数0为FALSE,返回值分别为1和0。
逻辑运算符和对应的位运算之间的重要区别是,如果对第一个参数求值就能确定表达式的结果,那么就不会对第二个参数求值,避免运算结果的错误。
对于一个位表示为[xn-1,xn-2,…,x0]的操作数x,C表达式x<<k会生成一个值,其位表示为[xn-k-1,xn-k-2,…,x0,0,…,0]。也就是说,x向左移动k位,丢弃最高的k位,并在右端补k个0。移位量应该是一个0~n-1之间的值。移位运算是从左至右可结合的,所以x<<j<<k等价于(x<<j)<<k。 有一个相应的右移运算x="">>k,但是它的行为有点微妙。一般而言,机器支持两种形式的右移:逻辑右移和算术右移。逻辑右移在左端补k个0,得到的结果是[0,…,0,xn-1,xn-2,…,xk]。算术右移是在左端补k个最高有效位的值,得到的结果是[xn-1,…,xn-1,xn-1,xn-2,…,xk]。
假设一个整数数据类型有w位。我们可以将位向量写成x→,表示整个向量,或者写成[xw-1 ,xw-2,…,x0],表示向量中的每一位。把x→看做一个二进制表示的数,就获得了x→的无符号表示。
无符号二进制有一个很重要的属性,就是每个介于0~2^w-1之间的整数都有唯一一个w为的值编码,函数为一个双射。
最常见的有符号数的计算机表示方式就是补码形式。在这个定义中,将字的最高有效位解释为负权。
所能表示的数值范围[-2^(w-1)~2^(w-1)-1],在可表示的范围内每个数字 都有一个唯一的w位的补码编码,函数为一个双射。
反码:除了最高有效位的权是-(2w-1-1)而不是-2w-1,它和补码是一样的
原码:最高有效位是符号位用来确定剩下的位应该取负权还是正权。
将一个w位的数假设我们不用额外的位来扩展一个数值,而是减少表示一个数字的位数。x=[xw-1 ,xw-2,…,x0]截断为一个k位的数字时,会丢弃高w-k位,得到一个位向量[xk-1 ,xk-2,…,x0],截断一个数字可能会改变他的值——溢出的一种形式。
考虑两个非负整数x和y,满足0≤x, y≤2w-1。每个数都能表示为w位无符号数字。然而,如果计算它们的和,我们就有一个可能的范围0≤x + y≤2w+1-2。表示这个和可能需要w + 1位。这种持续的“字长膨胀”意味着,要想完整地表示算术运算的结果,要对字长做限制。
无符号运算可以被视为一种模运算形式。无符号加法等价于计算和模上2w。可以通过简单的丢弃x + y的w + 1位表示的最高位,来计算这个数值。
范围在0≤x, y≤ 2w-1内的整数x和y可以表示为w位的无符号数,但是它们的乘积x · y的取值范围为0到(2w-1)2 = 22w-2w+1+1之间。这可能需要2w位来表示。不过,C语言中的无符号乘法被定义为产生w位的值,就是2w位的整数乘积的低w位表示的值。可以看作等价于计算乘积模2w。
浮点表示对形如V = x×2y的有理数进行编码。它对执行涉及非常大的数字(|V |>>0)、非常接近于0(|V |<<1)的数字,以及更普遍地作为实数运算的近似值的计算,是很有用的。
IEEE浮点标准用V = (-1)^s × M × 2^E的形式来表示一个数:
符号:s决定这个数是负数(s=1)还是正数(s=0),而对于数值0的符号位解释作为特殊情况处理。
尾数:M是一个二进制小数,它的范围是1~2-ε,或者是0~1-ε。
阶码:E的作用是对浮点数加权,这个权重是2的E次幂(可能是负数)。
将浮点数的位表示划分为三个字段,分别对这些值进行编码:
一个单独的符号位s直接编码符号s。
k位的阶码字段exp = ek-1…e1e0编码阶码E。
n位小数字段frac = fn-1…f1 f0编码尾数M,但是编码出来的值也依赖于阶码字段的值是否等于0。
1 DOS时代的平坦模式,不区分用户空间和内核空间,很不安全
2 8086的分段模式
3 IA32的带保护模式的平坦模式
Linux使用平坦寻址方式,使程序员将整个存储空间看做一个大的字节数组。
指令集体系结构,机器级程序的格式和行为,它定义了处理器状态、指令的格式以及每条指令对状态的影响。
程序计数器(通常称为PC,用%eip表示),指示将要执行的下一条指令在存储器中的地址。
整数寄存器文件:存储地(对应于C语言的指针)或整数数据。
条件码寄存器:保存着最近执行的算数或逻辑指令的状态信息,用来实现控制或者数据流中的条件变化。
浮点寄存器:用来存放浮点数据。
C预处理器插入宏和头文件:gcc -E xxx.c -o xxx.i
编译器产生源代码的汇编代码:gcc -S xxx.i -o xxx.s
汇编器化成二进制目标代码:gcc -c xxx.s -o xxx.o
链接器生成最终可执行文件:gcc xxx. -o xxx
用objdump -d xxx.o -o xxx.s 反汇编
一个IA32中央处理单元(CPU)包含一组8个存储32位值的寄存器。用来存储整数数据和指针。
%eax %ax (%ah %al) 通用寄存器 %ecx %cx (%ch %cl) 通用寄存器 %edx %dx (%dh %dl) 通用寄存器 %ebx %bx (%bh %bl) 通用寄存器 %esi %si 用来操纵数组 %edi %di 用来操纵数组 %esp %sp 操纵栈帧 %ebp %bp 操纵栈帧
根据操作数的不同类型,寻址方式可分为以下三种:
立即数寻址方式:操作数为常数值,写作$后加一个整数。
寄存器寻址方式:操作数为某个寄存器中的内容
存储器寻址方式:根据计算出来的地址访问某个存储器的位置
寻址模式:一个立即数偏移Imm,一个基址寄存器Eb,一个变址寄存器Ei,一个比例因子s(必须为1,2,4,8)有效地址计算为:Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
MOV相当于C语言的赋值'='
mov S,D S中的字节传送到D中
push和pop:
pushl S R[%esp] ← R[%esp]-4 M[R[%esp]] ← S popl D D ← M[R[%esp]] R[%esp] ← R[%esp]+4
注意:
leal,从存储器读数据到寄存器,而从存储器引用的过程实际上是将有效地址写入到目的操作数。目的操作数必须是一个寄存器。
一元操作:只有一个操作数,既是源又是目的,可以是一个寄存器或者存储器。
二元操作:第二个操作数既是源又是目的,两个操作数不能同时是存储器。
先给出位移量,然后是位移的数值,可进行算数和逻辑右移。移位操作移位量可以是立即数或%cl中的数。
描述最近的算数或者逻辑操作的属性,可以检测这些寄存器来执行条件分支指令。
CF:进位标志,最近操作使高位产生进位,用来检测无符号操作数的溢出
ZF:零标志,最近操作得出的结果为0 SF:符号标志,最近操作得到的结果为负数 OF:溢出标志,最近操作导致一个补码溢出-正溢出或负溢出。
注意:
数据传递、局部变量的分配和释放通过操纵程序栈来实现。
为单个过程分配的栈叫做栈帧,寄存器%ebp为帧指针,而寄存器指针%esp为栈指针,程序执行时栈指针移动,大多数信息的访问都是相对于帧指针。
栈向低地址方向增长,而栈指针%esp指向栈顶元素。
call:目标是指明被调用过程起始的指令地址,效果是将返回地址入栈,并跳转到被调用过程的起始处。
ret:从栈中弹出地址,并跳转到这个位置。
函数返回值存在%eax中
Y86程序中的每条指令都会读取或修改处理器状态的某些部分,称为程序员可见状态。其中包括:
8个程序寄存器:%eax,%ecx,%edx,%ebx,%esi,%edi,%esp和%ebp。
条件码:ZF(零)、SF(符号)、OF(有符号溢出)
程序计数器(PC):存放当前正在执行的指令的地址
存储器:很大的字节数组,保存着程序和数据。Y86系统用虚拟地址来引用存储器的位置,硬件和操作系统软件联合起来将虚拟地址翻译成实际或者物理地址。
状态码(stat):表明程序执行的总体状态。(异常处理)
movl:irmovl、rrmovl、mrmovl、rmmovl,分别 显式地指明源和目的地的格式。第一个字母表明源的类型,i(立即数)、r(寄存器)或m(存储器 ),第二个字母代表目的,可以是r或者m。
OPl(整数操作指令):addl、subl、andl和xorl。只对寄存器数据进行操作,同时还设置条件码。
jXX(跳转指令):jmp、jle、jl、je、jne、jge、jg,根据分支指令的类型和条件码的设置来选择分支。
cmovXX(条件传送指令):cmovle、cmovl、cmove、cmovne、cmovge和comvg,与寄存器-寄存器传送指令rrmovl一样,但只有当条件码满足所需要的约束时才会更新目的寄存器的值。
call和ret:call指令将返回地址入栈,然后跳转到目的地址。ret指令从这样的过程调用中返回。
pushl和popl:入栈和出栈。
halt:停止指令的执行。
指令的字节级编码规则:高4位为代码部分,低四位为功能部分,功能值只有在一组相关指令共用一个代码时才有用。
8个程序寄存器当中,每个都有相应的0~7的寄存器标识符。程序寄存器存在CPU中的一个寄存器文件中,这个文件就是一个小的、以寄存器ID作为地址的随机访问存储器。
附加寄存器指示符字节:指定一个或者两个寄存器。
附加4字节的常数字:作为irmovl的立即数数据,rmmovl和mrmovl的地址指示符的偏移量,以及分支指令和调用指令的目的地址。
注意:分支指令和调用指令的目的地址是一个绝对地址。所有整数采用小端法编码。
状态码:描述程序执行的总体状态。
值 名字 含义
1 AOK 正常操作
2 HLT 处理器执行halt指令(指令停止)
3 ADR 遇到非法地址
4 INS 遇到非法指令
Y86中,任何AOK以外的代码都会使处理器停止执行指令,而没有异常处理程序。
实现一个数字系统需要的三个组成部分:计算对位进行操作的函数的组合逻辑、存储位的存储器元素,以及控制存储器元素更新的时钟信号。
多路复用器:(s&&a)||(!s&&b)(控制信号为输入位S,S为1时输出a,s为0时输出b)
字级的组合电路:HCL中,所有字级的信号都声明为int,不指定字的大小。
多路复用函数是用情况表达式来描述的。
[
select1 : expr_1
select2 : expr_2
......
selectk : expr_k
]
算数/逻辑单元(ALU):三个输入分别为标号为A和B的两个数据输入,和一个控制输入。(注意减法的操作数顺序)
集合关系:判断集合关系的通用格式是
iexpr in {iexpr1,iexpr2,....iexprk} (其中被测试和待匹配的值均为整数表达式。)
为了产生时序电路,必须引入按位存储信息的设备,存储设备由同一个时钟信号控制。
随机访问存储器:存储多个字,用地址来选择应该读写哪个字。
时钟寄存器:存储单个位或字,时钟信号控制寄存器加载输入值。
寄存器首先保持稳定(输出等于当前状态),时钟上升沿来到时,加载输入信号。Y86处理器用时钟寄存器保存程序计数器(PC),条件代码(CC)和程序状态(Stat)。
寄存器文件有两个读端口(A和B),一个写端口(W),允许同时进行多端口读写操作。
取指:从存储器读取指令字节,地址为程序计数器(PC)的值。指令指示符字节两个四位部分,称为icode(指令代码)和ifun(指令功能)。vaIP(下一条指令的地址)=PC+已取出指令的长度。
译码:从寄存器文件读入最多两个操作数,得到valA和/或valB。
执行:算数逻辑单元(ALU)根据ifun的值执行指令指明的操作,计算存储器引用的有效地址,或者增加或减少栈指针。得到的值称为valE。也可根据条件码执行跳转。
访存:将数据写入存储器,或者从存储器读出数据。读出的值为valM。
写回:最多可以写两个结果到寄存器文件。
更新PC:将PC设置成下一条指令的地址。
Y86指令集的计算原则:处理器从来不需要为了完成一条指令的执行而去读由该指令更新了的状态。
取指阶段:包括指令存储器硬件单元。
译码和写回阶段:寄存器文件,支持同时进行两个读和两个写,每个端口有一个地址连接(寄存器ID)和一个数据连接(32根线路),既可以作为寄存器文件的输出字,又可以作为他的输入字。
执行阶段:算数逻辑单元(ALU),输出为valE信号。
访存阶段:读或者写程序数据,两个控制块产生存储器地址和存储器输入数据的值。另外两个块产生控制信号表明应该执行读还是写操作。当执行读操作时,数据存储器产生valM。
更新PC阶段:产生程序计数器的新值,依据指令的类型和是否要选择分支,新的PC可能是valC、valM或者valP。
静态RAM(SRAM):用来作为高速缓存存储器,每个位存储在一个双稳态的存储器单元里。双稳态:电路可以无限期的保持在两个不同的电压配置或者状态之一。只要供电,就会保持不变。
动态RAM(DRAM):用来作为主存以及图形系统的帧缓冲区。将每个位存储为对一个电容的充电,当电容的电压被扰乱之后,他就永远都不会再恢复了。暴露在光线下会导致电容电压改变。
SRAM与DRAM的对比:
SRAM DRAM 不需要刷新 以纳秒为周期刷新 存取速度快 存取速度慢 对光电噪声不敏感 光电因素易导致电压改变 晶体管多密集度低 电容小,密集度高 功耗贵代价高 功耗低
传统的DRAM:芯片中的单元(位)被分成了d个超单元,每个超单元都由w个DRAM单元组成, 一个d*w的DRAM共存储dw位信息。超单元被组织成一个r行c列的长方形阵列,rc=d。每个超单元的地址用(i,j)来表示(从零开始)。设计成二维矩阵是为了降低芯片上地址引脚的数量。
访问主存
总线:数据流通过称为总线的共享电子电路在处理器和DRAM之间传送。是一组并行的导线,能携带地址、数据和控制信号。
总线事务:CPU和主存之间的数据传送的过程。读事务,从主存传数据到CPU;写事务,从CPU传数据到主存。
I/O桥:将系统总线的电子信号翻译成存储器总线的电子信号。系统总线连接CPU和I/O桥,控制总线连接I/O桥和主存。
如果断电,DRAM和SRAM都会丢失信息,非易失性存储器——只读存储器:ROM。ROM是以他们能够被重编程的次数和对他们重编程的机制来区分的。
可编程ROM(PROM):只能被编程一次。PROM每个存储单元有一种熔丝,只能用高电流熔断一次。
可擦写可编程ROM(EPROM):紫外线光照射过窗口,EPROM就被清除为0,被擦除和重编程的次数为1000次。
电子可擦除ROM(EEPROM):不需要一个物理上独立的编程设备,因此可以直接在印制电路卡上编程,能够编程的次数为10^5。
闪存:基于EEPROM,为大量的电子设备提供快速而持久的非易失性存储。
磁盘构造:磁盘由盘片构成,表面覆盖着磁性记录材料,中央有一个可以旋转的主轴 ,旋转速率大约为5400-15000每分钟。磁盘的每个表面是一组称为磁道的同心圆组成,每个磁道被划分为一组扇区,扇区之间由一些间隙隔开,间隙存储用来标识扇区的格式化位。
磁盘容量由以下技术因素决定:
记录密度(位/英寸):磁道一英寸的段中可以放入的位数。
磁道密度(道/英寸):从盘片中心出发半径上一英寸的段内可以有的磁道数
面密度(位/平方英寸):记录密度与磁道密度的乘积。
磁盘操作:磁盘用读/写头来读写存储在磁性表面的位,而读写头连接到一个传动臂 一端,通过移动转动臂将读写头定位在磁道上的机械运动称为寻道。磁盘以扇区大小的块来读写数据,对扇区的访问时间有三个主要的组成部分:
寻道时间:转动臂将读/写头定位到包含目标扇区的磁道上所需时间。
旋转时间:驱动器等待目标扇区的第一个位旋转到读/写头下的时间。最大为1/RPM,平均旋转时间是Tmax的一半。
传送时间:读写并传送该扇区内容的时间。平均传送时间为:1/RPM*1/平均扇区数
逻辑磁盘块:现代磁盘将盘面的构造视为一个B个扇区大小的逻辑块序列,磁盘控制器维护着逻辑块号和实际磁盘扇区之间的映射关系。逻辑块号可识别为一个盘面、磁道、扇区三元组,唯一的标识了相对应的物理扇区。内存可以看成字节数组、磁盘可以看成块数组。
连接到I/O设备:所有的I/O设备都是通过I/O总线连接到CPU和主内存。有三种不同类型:
通用串行总线:一个广泛的使用标准,用于连接各种外围I/O设备。
图形卡(或适配器):包含硬件和软件逻辑,代表CPU在显示器上画像素。
主机总线适配器: 将一个或者多个磁盘连接到I/O总线,使用一个特别的主机总线接口定义的通信协议。
访问磁盘:CPU使用一种称为存储器映射I/O的技术向I/O设备发出命令,地址空间中为I/O设备通信保留的地址称为I/O端口。
固态硬盘是一种基于闪存的存储技术。一个硬盘包由一个或者多个闪存芯片和内存翻译层组成,闪存芯片替代旋转磁盘中的机械驱动器,而闪存翻译层将对逻辑块的请求翻译成对底层物理设备的访问
局部性原理:一个编写良好的计算机程序倾向于引用邻近于其他最近引用过的数据项,或者最近引用过的数据项本身。有良好局部性的程序比局部性差的程序运行的更快,在硬件层引入高速缓存存储器就体现了局部性原理。
时间局部性(temporal locality):被引用过一次的存储器位置在未来会被多次引用(通常在循环中)。
空间局部性(spatial locality):如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。
一个连续向量中,每隔k个元素进行访问,被称为步长为k的引用模式,具有步长为1的引用模式称为顺序引用模式,随着步长增加空间局部性下降。
双重嵌套循环按照行优先顺序读取数组元素。(因为C数组在存储器中是按照行顺序来存放的)
程序指令是存放在存储器中的,CPU读取这些指令的过程中评价一个程序关于取指令的局部性。
代码区别与程序数据的一个重要属性就是在运行时指令是不能被修改的。
重复引用同一个变量的程序有良好的时间局部性
对于具有步长为k的引用模式的程序,步长越小,空间局部性越好。
对于取指令来说,循环具有良好的时间和空间局部性。循环体越小,迭代次数越多局部性越好。
高速缓存是一个小而快速的存储设备,作为存储在更大、更慢的设备中的数据对象的缓冲区域。每一层存储器被划分成连续的数据对象片,称为块,每个块都有唯一的对象和名字。数据总是以块大小为传送单元在第k层和第k+1层之间来回拷贝。
缓存命中:当程序需要第k+1层的某个数据对象d时,首先在当前存储的第k层的一个块中查找d,如果d刚好在第k层中,则称为缓存命中。
缓存不命中:如果k层中没有缓存数据d,则称为缓存不命中,此时要从k+1层取出包含d的块,可能会覆盖(替换/驱逐)现在的一个块(牺牲块)。决定该替换哪个快是缓存的替换策略来控制的。(例如,随机替换策略/LRU策略)
缓存不命中的种类
强制性不命中/冷不命中:第k层缓存是空的(冷缓存),只是短暂的状态,不会在反复访问存储器使得缓存暖身之后的稳定状态出现。
冲突不命中:第k+1层的第i块,必须放置在第k层的块(i mod 4)中,这种限制性的放置策略引起冲突不命中。
利用时间局部性: 一旦一个数据在第一次不命中时被拷贝到缓存中,我们就会期望后面对该目标有一系列的访问命中。
利用空间局部性:块通常包含多个数据对象,我们通常期望后面对该块中其他对象的访问能够补偿不命中后拷贝该块的花费。
一个计算机系统每个存储地址有m位,形成M=2^m个不同的地址。
高速缓存被组织成一个有S=2^s个高速缓存组的数组,每个组包含E个高速缓存行,每个行是由一个B=2^b字节的数据块、一位有效位以及t=m-(b+s)个标记位组成,唯一标识存储在这个高速缓存行中的块。
高速缓存的结构用元组(S,E,B,m)来描述,高速缓存的大小C = S * E * B。
地址: t位 s位 b位
标记 组索引 块偏移
s个组索引位:一个无符号整数,说明字必须存储在哪个组中。
t个标记位:组中的哪一行包含这个字。
b个块偏移位:在B个字节的数据块中的字偏移。
每组只有一行(E=1)的高速缓存称为直接映射高速缓存。高速缓存确定一个请求是否命中,然后抽取出被请求字的过程分为三步:1)组选择,2)行匹配,3)字抽取。
直接映射高速缓存中的组选择:高速缓存从要抽取的字的地址中抽取出S个组索引位,这些位被解释成一个对应于一个组号的无符号整数。
直接映射高速缓存中的行匹配:当且仅当设置了有效位,而且高速缓存行标记与w的地址中的行标记相匹配时,这一行中包含w的一个拷贝。
直接映射高速缓存中的字抽取:块偏移位提供了所需要的字的第一个字节的偏移。
直接映射高速缓存中不命中时的行替换:需要从存储器层次结构中的下一层取出被请求的块,然后将新的块存储在组索引位指示的组中的一个高速缓存行中。
不命中率:不命中数量/引用数量
命中率:1-不命中率
命中时间:从高速缓存传送一个字到CPU所需的时间,包括组选择,行匹配,字抽取的时间。
不命中处罚:由于不命中所需要的额外时间。
影响性能的因素
高速缓存大小的影响:较大的高速缓存可能会提高命中率,但使大存储器运行的更快是更难一些的。
块大小的影响:较大的块能利用程序中可能存在的空间局部性,帮助提高命中率;但块越大意味着高速缓存行较少,损害时间局部性。
相联度的影响:相联度较大(E值较大)优点是降低了高速缓存由于冲突不命中出现抖动的可能性,但成本较高。
写策略的影响:直写高速缓存易实现,而且能使用独立于高速缓存的写缓冲区,用来更新存储器,不命中开销小。写回高速缓存引起的传送比较少,允许更多的到存储器的宽带用于执行DMA的I/O设备。越下层越可能使用写回。
静态库:将所有相关的目标模块打包成一个单独的文件,称为静态库。在unix系统中,静态库以一种称为存档的特殊文件格式存放在磁盘中。
为了创建该库,使用AR工具:
ar rcs libvector.a addvec.o multvec.o
为了创建可执行文件,需要编译链接输入文件*.o 和libvector.a:
gcc -O2 -c .c gcc -static -o p2 .o ./libvector.a
加载可执行目标文件
./p
共享库:一个目标模块,在运行时可以加载到任意的存储器地址,并和一个在存储器中的程序链接起来。这个过程称为动态链接,是由一个叫做动态链接器的程序来执行的。
创建动态库:
gcc -shared -fPIC -o libvector.so addvec.c multvec.c
将动态库链接到程序中:
gcc -o p2 *.c ./libvector.so
前半学期已经不知不觉得竟然已经结束了。这个学期学习这门课的方式与之前我们上的课学习的方式确实有着极大的不同,这门课主要是靠自己在课下自学自己查找问题进行提问之后巩固自己的所学知识,做到真正学会记在脑袋里应用到日后实践中去然后课上进行考试发现自己的不足并根据自己的考试成绩进行查缺补漏。这样一来确实有助于调动我们课下学习的积极性,让我们做到自己所学知识不再是死知识或者是只是用来应试考试的知识,避免了知识学会但是只是跟课本上教材上的概念背下来,而在实际使用上还是一点不懂的情况的发生,二来这样所学习的知识我觉得是不会忘记的是真正意义上的那种掌握不再只是课本上的概念熟记于心的学会而更多的是手指在键盘上随心敲打得到成果对知识的使用的真正学会。在学会了课本的纸上的知识的同时,又让我们将知识用到实践中让知识提升了一个档次,对将来面对真正的工作确实很有帮助。
学习不足
这门课现在已经让我们养成了一种下课学习的习惯,之前我们的学习模式大多是上课听,下课不复习,期末突击,这样就造成虽然考试过了但是知识也跟着考试一起不见了。也正是因为这门课之前我的学习方法不正确不得要领,总是想按照之前的老套路考前突击一下得到好的成绩,所以导致前面的学习并不扎实知识也是走马观花学完就忘了,成绩也很不理想。现在经过半学期的学习我了解了平时学习的重要性,这认识到要学好这门课不能在生搬硬套用老方法,要做到平时学习不要突击,在日积月累中学习点滴知识将知识记牢记扎实。
再就是之前因为基础不是特别好学习知识不扎实所以导致觉得任务量大,许多知识是一个知识点,但是却因为之前的不会就要再回头学习重新学习一边让效率很底下也是一个我的问题,在日后我要有空闲时间多多回顾之前走马观花没学牢的知识,为以后更高效更好的学好知识
参照课本以前博客以及齐岳博客园