深入理解计算机系统笔记
- 格式:doc
- 大小:243.00 KB
- 文档页数:45
《深⼊理解计算机系统》学习笔记整理(CSAPP学习笔记)简介本笔记⽬前已包含 CSAPP 中除第四章(处理器部分)外的其他各章节,但部分章节的笔记尚未整理完全。
未整理完成的部分包括:ch3、ch11、ch12 的后⾯⼏⼩节;ch5 的⼤部分。
我在整理笔记时所考虑的是:在笔记记完后,当我需要查找某个知识点时,不需要到书中去找,只需查看笔记即可找到相关知识点。
因此在整理笔记时⼒求全⾯与简洁,能够在查找时快速找到并迅速理解。
在此基础上,笔记已整理的内容是对书中内容的提炼,包括了我在学习时所认为的书中所有有⽤的知识点,因此每章的笔记内容都⽐较多。
注:因为在整理笔记时尚处于学习阶段,因此对于书中知识的把握不够清晰,对部分知识点的重要性认识也不⾜,因此可能会有⼀些有⽤的知识点遗漏。
笔记适⽤对象看过⼀遍《深⼊理解计算机系统》,可以查看本笔记对学习内容进⾏复习和梳理。
正在看《深⼊理解计算机系统》,⾃⼰没有精⼒或不想记笔记,可以每看完⼀章书籍,再看⼀遍笔记以梳理学习内容。
需要查阅《深⼊理解计算机系统》内的知识点,可以通过本笔记查阅。
笔记不适合的对象本笔记不能完全成为《深⼊理解计算机系统》的代替品,笔记中仅包含对《深⼊理解计算机系统》中本⼈认为所需要整理的内容的提炼与部分代码⽰例。
⽬录备注第 11、12 章的笔记都不全,预计可能不会再补了。
第 11 章主讲⽹络编程,但是太简略了,如果要学的话,推荐直接去看《TCPIP⽹络编程》、《Linux⾼性能服务器编程》等书(中也可以找到相关的学习笔记)。
第 12 章主讲并发编程,以多线程编程为主,相关内容也可以在《TCPIP⽹络编程》、《Linux⾼性能服务器编程》中学习。
8.5.1信号术语1、传送一个信号到目的进程1)发送信号。
内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。
发送信号可以有如下两种原因:(1)内核检测到一个系统事件。
(2)一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程,一个进程可以发送信号给它自己。
2)接收信号。
当目的进程被内核强迫以某种方式的发送做出反应时,目的进程就接收了信号。
进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序的用户层函数不活这个信号。
2、一个只发出而没有被接收的信号叫做待处理信号。
在任何时刻,一种类型至多只会有一个待处理信号。
3、一个进程可以有选择性地阻塞接收某种信号。
当一种信号被阻塞时,他仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。
4、一个待处理信号最多只能被接收一次。
8.5.2发送信号1、进程组每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。
一个子进程和它的父进程同属于一个进程组,一个进程组可以通过使用setpgid 函数来改变自己或者其他进程的进程组。
2、用/bin/kill程序可以向另外的进程发送任意的信号。
3、从键盘发送信号外壳为每个作业创建一个独立的进程组。
4、进程通过调用kill函数发送信号给其他进程(包括它们自己)。
5、进程可以通过调用alarm函数向他自己发送SIGALRM信号。
8.5.3接收信号1、当内核从一个异常处理程序返回,准备将控制传递给进程P时,他会检查进程P的未被阻塞的处理信号的集合。
如果这个集合为空,那么内核将控制传递到P的逻辑控制流中的下一条指令;如果集合是非空的,那么内核选择集合中的某个信号K(通常是最小的K0,并且强制P接收信号K。
收到这个信号会触发进程的某种行为。
一旦进程完成了这个行为,那么控制就传递回P的逻辑控制流中的下一条指令。
2、每个信号类型都有一个预定的默认行为:(1)进程终止(2)进程终止并转储存储器(3)进程停止直到被SIGCONT型号重启(4)进程忽略该信号3、signal函数可以通过下列三种方法之一来改变和信号signum相关联的行为:(1)如果handler是SIG_IGN,那么忽略类型为signum的信号(2)如果handler是SIG_DFL,那么类型为signum的信号行为恢复为默认行为(3)否则,handler就是用户定义的函数的地址,这个函数成为信号处理程序,只要进程接收到一个类型为signum的信号,就会调用这个程序,通过把处理程序的地址传递到signal函数从而改变默认行为,这叫做设置信号处理程序。
大学计算机操作系统设计原理学习笔记一、引言计算机操作系统是计算机科学中的重要组成部分,它负责管理计算机的硬件资源并提供各种服务给应用程序。
学习计算机操作系统的设计原理对于理解计算机系统的工作原理以及提升编程能力都具有重要意义。
本文将通过分析性论述的方式,结合具体操作方法和实例,介绍学习计算机操作系统设计原理的步骤和技巧。
二、学习步骤1. 理解操作系统的基本概念在学习操作系统设计原理之前,首先需要了解操作系统的基本概念和功能。
操作系统作为计算机系统的核心,它负责管理计算机的硬件资源,包括内存、处理器、设备等,并提供各种服务给应用程序。
熟悉操作系统的基本概念和功能可以为后续的学习奠定基础。
2. 学习操作系统的结构和组成操作系统的结构和组成是理解其设计原理的关键。
操作系统通常由内核和外壳组成,内核负责管理硬件资源,而外壳则提供用户界面和应用程序接口。
了解操作系统的结构和组成可以帮助我们理解其工作原理和实现方式。
3. 研究操作系统的运行机制操作系统的运行机制是其设计原理的核心内容。
操作系统通过调度算法、内存管理、文件系统等机制来优化资源利用和提供服务。
研究操作系统的运行机制可以帮助我们理解其工作原理,并且可以通过实验和实践来加深理解。
4. 分析和比较不同操作系统的设计原理在学习操作系统设计原理的过程中,可以选择一些经典的操作系统来进行分析和比较。
例如,UNIX、Windows、Linux等操作系统都有各自独特的设计原理和实现方式。
通过分析和比较不同操作系统的设计原理,可以加深对操作系统的理解,并且为自己的设计提供参考。
三、具体操作方法举例1. 学习操作系统的基本概念在学习操作系统的基本概念时,可以选择一本权威的教材或者参考资料作为学习的基础。
这些教材通常会详细介绍操作系统的基本概念和功能,并且提供一些实例和案例来帮助理解。
同时,可以通过搜索引擎或者在线学习平台找到一些相关的课程或视频来进行学习。
2. 学习操作系统的结构和组成学习操作系统的结构和组成可以通过查阅相关的书籍和文献来进行。
⽬标⽂件符号《深⼊理解计算机系统》笔记(三)链接知识【附图】每⽇⼀贴,今天的内容关键字为⽬标⽂件符号该书中对链接的解释也不敷具体。
在章节最后,作者也否认:在计算机系统⽂献中并没有很好的记录链接。
因为链接是处在编译器、计算机体系结构和操作系统的交叉点上,他要求懂得代码⽣成、机器语⾔编程、程序实例化和虚拟存储器。
它恰好不落在某个平⽇的计算机系统领域中。
该章节报告Linux的X86系统,使⽤标准的ELF⽬标⽂件,⽆论是什么样的操作系统,细节可能不尽相同,但是观点是相同的。
读完这⼀章节后,对“符号”的观点很是模糊。
7.1编译驱动程序这⾥再说⼀下编译系统。
⼤多数编译系统提供编译驱动程序,它代表⽤户在需要的时候调⽤语⾔预处置、编译器、汇编器、和链接器。
我⾃⼰画了⼀个结构图。
7.2静态链接7.3⽬标⽂件⽬标⽂件有三种:可重定位⽬标⽂件、可执⾏⽬标⽂件和同享⽬标⽂件(即动态链接库),个个系统上对⽬标⽂件的叫法不⼀致,Unix叫a.out,Windows NT叫PE(Portable Executable)。
现代Unix使⽤ELF格式(EXecutable and Linkable Format 即可执⾏和可链接格式)。
下⾯具体介绍“可重定位⽬标⽂件”,下图最左边的⼀个图。
上图说明了,⼀个⽬标⽂件⽣成可执⾏⽂件,然后加载到内存后的映射等,三个步骤。
ELF头描述了⽣成该⽂件的系统的字的巨细和字节序。
ELF和节头部表之间每⼀个部份都称为⼀个节(section).text:已编译程序的机器代码.rodada:只读数据,⽐如printf语句中的格式串。
.data:已初始化的全局C变量。
局部变量在运⾏时保存在栈中。
即不再data节也不在bss节.bss:未初始化的全局C变量。
不占据实际的空间,仅仅是⼀个占位符。
所以未初始化变量不需要占据任何实际的磁盘空间。
C++弱化BSS段。
多是没有,也可能有。
.symtab:⼀个符号表,它存放“在程序中定义和引⽤的函数和全局变量的信息”。
C中return值得低32位放在eax寄存器中,高32位放在edx寄存器中。
Ebp表示帧指针,esp表示栈指针,eip寄存器是程序计数器,用来记录程序转变成的二进制码的位置。
2Leal 表示取地址的意思。
()表示的意思是将括号中的数最为地址,取出该地址中的数Eg:(%edx,%eax,2)的意思是加入寄存器eax中的数为x,寄存器edx中的数为y那么该指令表示:取出地址为(2*x+y)中所存储的数据;而leal(%edx,%eax,2)表示为取出地址为(2*x+y)中所存储的数据所对应的地址,即2*x+y3C语言允许对指针进行运算,而计算出来的值会根据该指针引用的数据类型的大小进行伸缩。
Eg:int A[i]对于数组名A表示的数组的起始地址,是一个指针,A[i]实际的地址是A+4*i,但是在C中可以表示为:A[i] = *(A+i)5IntA[5][3] 等同于:Typdefintrow3_t[3]Row3_t A[5]表明,对于C的数组定义的时候,先定义的是A[i][j]中的j,也就是说,C中在数组的存储上是按照j维度连续的,进一步说,C中的数组是按照数组中最后一个维度连续的。
这与Fortran恰恰相反。
C中A[2][1]表示二列第一行。
对于int A[i][j] 中A[i][j]的存储器地址为:A+4*(i*C+j)其中C是j的最大值也就是一列有多长。
6Comp和test两个操作不改变寄存器中的值,但会改变标志位中的值7在C结构体重间接引用指针表示为(*a).para1一定要加上括号另一种表示方式为:a->para17????struct和typdef:Typdef需要在新定义的数据类型前面加上已经存在的数据类型,Struct不需要,因为在struct内部都有定义;Struct在定义结束的大括号后面可以加上一个名字表示这个结构体叫做这个名字。
Eg:Struct prob {……} a;等同于:Struct prob {……}Struct prob a;这里struct prob可以看成一种数据类型,相当于int后边的a是具体的,数据类型为struct prob的数当间接引用指针的时候:struct prob *p*p = &a (注意,数组变量符号表示数组首地址,可是结构体不是所以要在a前面加上&)如果:Struct prob {……}最后的大括号后面以及下一行都没有定义一个为Struct prob类型的变量的话,则:struct prob *p这个间接引用的指针只指向上边这个结构体,因为只有这一个。
第9章9.1 物理和虚拟(1)一个使用物理寻址的系统:当CPU执行这条加载指令时,它会产生一个有效的物理地址,通过存储器总线,把它传递给主存。
主存取出从物理地址4处开始的4字节的字,并将它返回给CPU,CPU会将它存放在一个寄存器里。
(2) 一个使用虚拟寻址的系统:使用虚拟寻址时,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到存储器之前先转换成适当的物理地址。
(3)地址翻译:将一个虚拟地址转换为物理地址的任务。
存储器管理单元(MMU):利用存放在主存中的查询表来动态翻译地址。
9.2 地址空间(1)地址空间:一个非负整数地址的有序集合:{0,1,2,3,...}线性地址空间:地址空间的整数是连续的。
虚拟地址空间:在一个带虚拟存储器的系统中,CPU从一个N = 2n {0, 1, 2, 3, …, N-1}物理地址空间:{0, 1, 2, 3, …, M-1}(2)虚拟存储器的基本思想:允许每个数据对象有多个独立地址,其中每个地址都选自一个不同的地址空间。
(3)主存中的每个字节都有一个选自虚拟地址空间虚拟地址和一个选自物理地址空间的物理地址。
9.3 虚拟存储器作为缓存的工具(1)虚拟存储器(VM)被组织为一个有存放在磁盘上的N个连续的字节大小的单元组成的数组。
每个虚拟页的大小为P = 2p(2的p 次方)。
物理存储器杯分割为物理页(PP),大小也为P字节(物理页也称为页帧)(2)任意时刻,虚拟页面的集合都分成3个不相交的子集:未分配的:VM系统还有未分配(或创建)的页;不占任何磁盘空间缓存的:当前缓存在物理存储器中的已分配页。
未缓存的:没有缓存在物理存储器中的已分配页。
(3)DRAM缓存的组织结构:SRAM缓存:表示位于CPU和主存之间的L1、L2和L3高速缓存。
DRAM缓存:表示虚拟存储器系统的缓存,它在主存中缓存虚拟页。
(4)页表:页表就是一个页表条目(PTE)的数组。
每个PTE由一个有效位和一个n位地址字段组成的。
《深⼊理解计算机系统》读书笔记并发、并⾏和抽象并发concurrency是⼀个通⽤的概念:指同时具有多个活动的系统。
并⾏parallelism:指⽤并发使⼀个系统运⾏得更快。
并⾏可以在计算机系统的多个抽象层次上运⽤。
1. 线程级并发构建进程这个抽象,我们能够设计出同时执⾏多个程序的系统,这就导致了并发。
使⽤线程,我们能够在⼀个进程中执⾏多个控制流。
单处理器系统中,并发执⾏只是模拟出来的,是通过使⼀台计算机在他正在执⾏的进程间快速切换的⽅式实现的。
当构建⼀个由单操作系统内核控制的多处理器组成的系统时,就得到⼀个多处理器系统。
多处理器是将多个cpu集成到⼀个集成电路芯⽚上。
超线程(同时多线程)处理器是⼀项允许⼀个cpu执⾏多个控制流的技术。
如常规的处理器需要⼤约20000个时钟周期做不同的线程切换,⽽超线程处理器可以在单个周期的基础上进⾏切换。
这使得cpu可以更好的利⽤其处理资源。
多处理器的使⽤可以从两个⽅⾯提⾼系统性能。
⾸先,它减少了在执⾏多个任务时模拟并发的需要。
其次,它可以使应⽤程序运⾏的更快。
当然,这必须要求程序是以多线程⽅式来书写的,这些线程可以并⾏地⾼效执⾏。
2. 指令级并⾏在较低的抽象层次上,现代处理器可以同时执⾏多条指令的属性指令级并⾏。
基本思想是将⼀条指令所需要的活动划分为不同的步骤,将处理器的硬件组织成⼀系列的阶段,每个阶段执⾏⼀个步骤。
这些阶段可以并⾏的操作,⽤来处理不同的指令的不同部分。
其通过硬件设计,能够达到接近于⼀个时钟周期⼀条的指令的执⾏速率。
甚⾄更快,称为超标量处理器。
3. 单指令、多数据并⾏在最低的层次上,许多现代处理器拥有特殊的硬件,允许⼀条指令产⽣多个并⾏执⾏的操作,这种⽅式称为单指令、多数据SIMD并⾏。
如专门处理向量计算的处理器。
抽象。
抽象是计算机科学中最为重要的概念之⼀。
如⼀组函数的API等,类的封装,接⼝。
在处理器中,指令集结构提供了对实际处理器硬件的抽象。
《深⼊理解计算机系统》阅读总结与摘要前⾔《深⼊理解计算机系统》值得每位程序员⼀读,看完之后将会对整个计算机体系有⼀个直观的认识。
第⼀章计算机系统漫游只有ascii字符构成的⽂件称为⽂本⽂件,所有其它⽂件都称为⼆进制⽂件。
c语⾔是古怪的,有缺陷的,但同时也是⼀个巨⼤的成功,为什么会成功呢c语⾔与unix操作系统关系密切c语⾔⼩⽽简单c语⾔是为实践⽬的设计的有⼀些重要的原因促使程序员必须知道编译系统是如何⼯作的优化程序性能理解链接时出现的错误避免安全漏洞shell是⼀个命令⾏解释器,它提出⼀个提⽰符,等待输⼊⼀个命令⾏,然后执⾏这个命令。
如果该命令⾏的第⼀个单词不是⼀个内置的shell命令,那么shell就会假设这是⼀个可执⾏⽂件的名字,它将加载并运⾏这个⽂件。
贯穿整个系统的是⼀组电⼦管道,称作总线。
io设备是系统与外部世界的联系通道。
主存是⼀个临时存储设备,在处理器执⾏程序时,⽤来存放程序和程序处理的数据。
处理器,是解释或执⾏存储在主存中指令的引擎。
利⽤直接存储器存取,数据可以不通过处理器⽽直接从磁盘到达主存。
通过让⾼速缓存⾥存放可能经常访问的数据,⼤部分的内存操作都能在快速地⾼速缓存中完成每个计算机系统中的存储设备都被组织成了⼀个存储器层次结构。
操作系统有两个基本功能防⽌硬件被失控的应⽤程序滥⽤,向应⽤程序提供简单⼀致的机制来控制复杂⽽⼜通常⼤不相同的低级硬件设备。
操作系统通过基本的抽象概念(进程,虚拟内存和⽂件)来实现这两个功能。
⽂件是对i/o设备的抽象表⽰,虚拟内存是对主存和磁盘i/o设备的抽象表⽰,进程则是对处理器,主存和i/o设备的抽象表⽰。
进程是对操作系统对⼀个正在运⾏的程序的⼀种抽象。
进程并发运⾏,则是说⼀个进程的指令和另⼀个进程的指令是交错执⾏的。
操作系统实现这种交错执⾏的机制称为上下⽂切换。
操作系统保持跟踪进程运⾏所需的所有状态信息。
这种状态,也就是上下⽂。
当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进⾏上下⽂切换,即保存当前进程的上下⽂,恢复新进程的上下⽂,然后将控制权传递到新进程。
深入理解计算机系统读书笔记深入理解计算机系统读书笔记一、引言在现代社会中,计算机系统已经成为了我们生活中不可或缺的一部分。
计算机系统的理解对于我们每个人来说都至关重要。
而《深入理解计算机系统》这本书则为我们提供了一个深入探索计算机系统本质的绝佳机会。
通过对这本书的深入阅读和学习,我们可以更好地理解计算机系统的底层原理和工作机制,从而对计算机系统有一个更加全面和深入的认识。
二、深入理解计算机系统的重要性1. 计算机系统是现代社会的基石计算机系统贯穿了我们的日常生活的方方面面,从智能手机、电脑到网络和云计算,在各个领域都离不开计算机系统的支持。
深入理解计算机系统的原理和机制对于我们更好地理解现代社会及其发展趋势至关重要。
2. 对计算机系统的深刻理解有助于提升我们的技术能力对计算机系统的深刻理解可以帮助我们更好地掌握计算机编程、系统架构等技术,从而提升我们的技术能力和竞争力。
这对于从事与计算机相关的工作或行业的人来说尤为重要。
3. 深入理解计算机系统有助于培养我们的计算机思维深入理解计算机系统不仅可以帮助我们掌握计算机技术,还可以帮助我们培养计算机思维。
这种思维方式包括抽象思维、逻辑思维和系统思维等,这对于我们解决问题和创新都有着积极的影响。
三、《深入理解计算机系统》读书笔记经过对《深入理解计算机系统》的深入阅读和学习,我对计算机系统有了更深入的理解。
在阅读过程中,我对书中所提到的计算机系统结构、操作系统、存储器层次、编译技术等内容进行了详细的学习和思考。
以下是我对书中内容的一些深入理解和思考:1. 计算机系统结构书中详细介绍了计算机系统结构的各个方面,包括指令集体系结构、处理器体系结构、存储器层次结构等。
通过学习这些内容,我更深刻地理解了计算机系统的组成和运行原理,从而对计算机系统有了更清晰的认识。
2. 操作系统操作系统作为计算机系统的核心部分,对整个系统的运行起着至关重要的作用。
在书中,我深入学习了操作系统的原理和设计,包括进程管理、内存管理、文件系统等内容。
作为你的文章写手,我将根据你提供的主题“cmu深入理解计算机系统课程笔记”来撰写一篇深度和广度兼具的中文文章。
1. 了解CMU深入理解计算机系统课程CMU(卡内基梅隆大学)深入理解计算机系统课程是一门涵盖计算机硬件、操作系统和程序设计的经典课程,旨在帮助学生全面理解计算机系统的工作原理和设计思想。
该课程从底层硬件到顶层应用,涵盖了计算机系统的各个方面,为学生提供了深入理解计算机系统的机会。
2. 课程内容概述在深入理解计算机系统课程中,学生将学习到计算机系统的核心概念,包括机器级代码、处理器体系结构、存储器层次结构、信息和加载、异常控制流、系统级I/O、网络编程等内容。
通过对这些内容的学习,学生将能够深入理解计算机系统的运行原理和内部结构。
3. 深入理解计算机系统课程的重要性CMU深入理解计算机系统课程对于计算机科学与工程专业的学生来说至关重要。
它能够帮助学生建立对计算机系统整体的认识,从而为他们今后的学习和工作打下坚实的基础。
该课程涵盖了许多计算机系统领域的经典知识和最新进展,使学生能够跟上计算机科学和工程领域的发展脉络。
另外,通过深入理解计算机系统课程,学生还能够培养系统思维和创新能力,为他们将来的学术研究和工程实践提供有力支持。
4. 我对CMU深入理解计算机系统课程的个人观点作为一门充满挑战和机遇的课程,CMU深入理解计算机系统课程在传授知识的也能够激发学生的学习兴趣和求知欲。
通过深入理解计算机系统课程,我相信学生们将能够拓宽自己的学术视野,培养自己的团队合作和解决问题的能力,为今后的学习和工作打下坚实的基础。
5. 总结与回顾CMU深入理解计算机系统课程是一门具有重要意义的课程,它既能够帮助学生深入理解计算机系统的核心概念,又能够培养学生的系统思维和创新能力。
通过深入理解计算机系统课程,学生将能够全面、深刻和灵活地理解计算机系统,为未来的学习和工作奠定坚实的基础。
希望以上内容能够满足你的要求,如果有其他需要,随时告诉我。
深入理解计算机系统(1) 对于一个无符号数字x,截断它到k位的结果就相当于计算x mod 2^k.(2) 在大多数的机器上,整数乘法指令相当地慢,需要12或者更多的始终周期,然而其他整数运算-例如加法、减法、位移运算和移位-只需要1个时钟周期.因此,编译器使用的一项重要的优化就是试着使用移位和加法运算的组合来代替乘以常数因子的乘法.(3) 在大多数的机器上,整数除法要比整数乘法更慢-需要30或者更多的始终周期.除以2的幂也可以用移位运算来实现,只不过我们用的是右移,而不是左移.对于无符号和二进制补码数,分别使用逻辑移位和算术移位来达到目的.1.注意系统的分类:主流的IA32(也就是x86),以及x86-64(也就是x64),还有种Intel的与原32位系统不兼容的IA64。
2.编译系统由预处理器,编译器,汇编器和链接器组成。
3.单指令多数据并行称为SIMD并行,其扩展为SSE指令集。
4.x64上long为8字节,指针也为8字节。
5.无符号数右移必须采用逻辑右移,而有符号数一般采用算术右移。
6.有符号数遇见无符号数会默认强转为无符号数。
7.short转为unsigned时,是先扩展大小再符号转换。
8.补码非的计算:从左到右将第一个为1的位前的所有位取反。
9.负数的补码移位向下舍入。
10.正浮点数能使用整数排序函数来进行排序。
11.浮点加法和乘法不具备结合性,浮点乘法在加法上不具备分配性。
12.预处理器扩展源代码,然后编译器生成源代码的文本汇编代码,汇编器转成二进制汇编码,链接器生成exe或dll 或lib。
13.寄存器可以保存地址也可以保存值。
注意汇编中的加括号表示为取该地址指向的值,如(%eax)指%eax中保存的地址指向的值。
14.传送指令的两个操作符不能都指向存储器。
15.栈指针%esp保存着栈顶元素的值,%eax保存函数返回值。
16.栈从高地址往低地址分配,堆从低地址往高地址分配。
17.注意:lea,假设为leal 7(%edx, %eax, 4),则当%edx中保存的是地址时,lea为取有效地址,而当%edx中保存的是值时,lea为算术运算,即7 + %edx + %eax * 4。
这儿的%eax总保存值。
说白了,其实lea一直是在做计算,只是%edx影响了直观表达而已。
18.注意:处理有无符号值的操作是通过不同的汇编指令来区分的。
19.大多数汇编器根据一个循环的do-while形式来产生循环代码,逆向工程会用到。
20.指令无视操作数的长度。
21.因为有个条件传送的优化策略,所以(xp ? *xp : 0)这条语句其实两个选择分支都会执行。
22.32位系统中,大多数栈中信息的访问其位置都是基于帧指针的。
而64位系统中,栈的存储信息数已被弱化,所以无帧指针了。
23.访问某个局部变量的前提是该局部变量至少有个可引用的地址,所以局部变量被保存在了栈中。
24.为了防止从效率低的存储器读写值时,可能因数据未对齐而造成多次读写从而导致低性能,IA32要求数据一定要对齐。
编译器在编译时会强制对齐。
25.汇编指令leave等于俩pop,效果一样,选择随意。
pop和push在栈上分配空间的方式是直接栈指针减去或加上偏移量。
26.指针之差等于相差字节数 / 所指类型大小字节数。
27.寄存器不够用时会出现寄存器溢出,这时就必须有值被保存在栈上了,一般是将只读变量放入栈。
28.GCC会对局部char类型的缓冲区插入金丝雀保护代码。
29.在C中内联汇编代码只能针对某一类机器。
30.SSE2引入浮点数面向寄存器的指令集,而不用基于栈的方法。
31.x64能让汇编代码比x32少很多,但是实际性能提升不会很大。
但是从改进上来说性能应该提升很大很大啊,为什么。
难道是x32已经被优化的变态了???32.注意rep有时当空操作使。
33.x64中,栈空间向下128字节以内的区域仍可以被函数访问,该区域被ABI称为红色地带。
34.浮点相关:把存储模型,指令和传递规则组合称为浮点体系结构。
35.逻辑门只是简单的响应输入的变化而已。
36.HCL中,'='只是表示用一个名字来称谓一个表达式。
其类switch的表达中的”1:“等同于switch中的Default Case。
37.寄存器文件上的读或写端口都分别成对,一个传ID,一个传内容。
38.访存阶段读写存储器,写回阶段将结果写到寄存器文件。
39.寄存器文件和数据存储器等都是当前时钟随意读,下一时钟统一写入更新。
即时钟控制状态元素的更新。
40.流水线即保持各单元在时钟周期内忙碌不已,一套流水线硬件供多个流水线使用。
41.加载互锁和数据转发技术结合起来足以处理可能类型的数据冒险。
42.当流水线化的系统中出现多条指令引起的异常时,最深的指令被处理的优先级最高。
43.每个时钟周期执行多个操作称为超标量,而超线程是指一个核同时运行俩线程。
44.处理器功能单元的性能表示中,延迟指按照严格顺序执行完成合并运算所需要的最小周期数,而吞吐量指理论上最快完成一个操作所需周期数。
45.使用SSE可以降低吞吐量界限。
46.书写适合条件传送实现的”功能式“代码。
47.每个加载/存储单元每个时钟周期只能启动一条加载/存储操作。
48.性能提高技术:①.采用合适的算法和数据结构。
②.消除连续的函数循环调用,在可能时尽量将计算移到循环外;消除不必要的存储器引用,引入临时变量来保存中间结果;保持内层循环在存储器层面的局部性。
③展开循环;通过多个累计变量和重新结合等技术,提高指令级并行;用功能的风格重写条件操作,使得编译采用条件数据传送。
49.内存是硬盘的缓存。
50.core i7上所有的SRAM高速缓存存储器都在CPU芯片上。
51.对于性能来说,存储器访问总数和不命中率相比,不命中率影响要更大。
估计是因为存储器的写回缓存机制。
52.存储器性能注意:将注意力集中在内循环上,大部分计算和存储器访问都发生在这里;通过按照数据对象存储在存储器中的顺序,以步长为1来读数据,从而使得空间局部性最大;一旦从存储器中读入了一个数据对象,就尽可能多的使用它,从而使得时间局部性最大。
53.对于静态库的链接,只会链接程序中用到的该库(.lib)中的模块(.obj)。
这其实也解释为什么分别链接静态库版本和动态库版本的两程序大小相差不是那么悬殊。
54.当前指令处理完后,处理器才能去发现中断是否发生。
55.Linux系统调用的参数都是通过通用寄存器而不是栈传递的。
56.C++的try-catch是C种setjmp和longjmp的更加结构化的版本。
57.DRAM作为磁盘的缓存,不命中开销巨大。
58.磁盘上的交换文件同时又作为DRAM保存数据的缓存。
59.延迟私有对象中的拷贝最充分的利用了稀有的物理存储器,这是通过写时拷贝实现的。
60.一个系统中被所有进程分配的虚拟存储器的全部数量是受磁盘上交换空间的数量限制的。
61.造成堆利用率很低的主要原因是内存碎片的存在。
62.有时为了极个别的几个特殊情况而要去每次调用时都检查,还不如通过某些方式直接把特殊情况一般化,能用通用的方式去处理。
63.内存引用导致的崩溃要注意:引用坏指针/野指针,以及读未初始化的存储器。
64.UNIX信号是不排队的,若为考虑处理,则会直接丢弃。
65.函数内部的static变量也是线程间共享的。
66.注意并行和并发的区别,并行程序是一个运行在多个处理器上的并发程序。
67.线程数多过核数对效率反而会有影响,但影响不大。
68.注意:rand和ctime,localtime等函数时线程不安全的,慎用啊慎用!可用其可重入版本。
69.互斥锁记得相同顺序加锁解锁。
70.包装错误处理函数,是一个非常不错的做法。
//============================================2010.07.08深入理解计算机系统(1) 反汇编器一些特性说明: 1) IA32指令长度从1~15个字节不等.指令编码被设计成使常用的指令以及操作较少的指令所需的字节数少,二那些不太常用或操作数较多的指令所需字节数较多.2) 指令格式是按照这样一种方式设计的,从某个给定位置开始,可以将字节唯一地解码成机器指令.例如,只有指令pushl %ebp是以字节值55开头的.3) 反汇编器只是根据目标文件中的字节序列来确定汇编代码的.它不需要访问程序的源代码或汇编代码.4) 反汇编器使用的指令命名规则与GAS(Gnu ASembler)使用的有些细微的差别.5) 与code.s中的汇编代码相比,我们发现结尾多了一条nop指令.这条指令根本不会被执行(它在过程返回指令之后),即使执行了也不会有任何影响(所以称之为nop,是"no operation"的简写,同城读作"no op").编译器插入这样的指令是为了填充存储该过程的空间.(2) IA32加了一条限制,传送指令的两个操作数不能都指向存储器位置.将一个值从一个存储器位置拷到另一个存储器位置需要两条指令-第一条指令将源值加载到寄存器值写入目的位置.(4) 根据惯例,所有返回真书或指针值的函数都是通过将结果放在寄存器%eax中来达到目的的.(5) 加载有效地址(Load effective address)指令leal实际上是movl指令的变形.它的指令形式是从存储器读取数据到寄存器,但实际上它根本就没有引用存储器.它的第一个操作数看上去是一个存储器引用,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数(如寄存器).(6) 一元操作,只有一个操作数,既作源,也作目的.这个操作数可以是一个寄存器,也可以是一个存储器位置.比如说incl(%esp)会是栈顶元素加 1.这种语法让人想起C中的加1运算符(++)和减1(--).(7) 二元操作,第二个操作数既是源又是目的.这种语法让人想起C中向+=这样的赋值运算符.不过要注意,源操作数是第一个,目的操作数是第二个,这是不可交换操作特有的.例如,指令subl %eax, %edx使寄存器%edx的值减去%eax中的值.第一个操作数可以是立即数、寄存器或存储器位置.第二个操作数可以是寄存器或是存储器位置.不过同movl指令一样,两个操作数不能同时都是存储器位置.(8) divi指令执行无符号除法,通常会事先将寄存器%edx设置为0. //============================================2010.07.09深入理解计算机系统(1) 汇编语言中,直接跳转时给出一个标号作为跳转目标地;间接跳转的写法是"*"后面跟一个操作数指示符.如jmp *%eax 表示用寄存器%eax中的值作为跳转目标; jmp *(%eax) 表示已%eax中的值作为读地址,从存储器中读出跳转目标;(2) call指令有一个目标,指明被调用过程起始的指令地址. 同跳转一样,调用可以是直接的,也可以是间接的.在汇编代码中,直接调用的目标是一个标号,而间接调用的目标是*后面跟一个操作数指示符,其语法与movel指令的操作数的语法相同.(3) call指令的效果是将返回地址入栈,并跳转到被调用过程的起始处.返回地址是紧跟在程序中call后面的那条指令的地址.这样当被调用过程返回时,执行会从此继续.ret指令从栈中弹出地址,并跳转到那个位置.要正确使用这条指令,就要使栈准备好,栈指针要指向前面call指令存储返回地址的位置.leave指令可以用来使栈做好返回的准备.它等价于下面的代码序列: movl %ebp, %esp //set stack pointer to beginning of frame popl %ebp // restore saved %ebp and set stack ptr to end of call's frame 另外这样准备工作也可以通过直接使用传送和弹出操作来完成. 寄存器%eax可以用来返回值,如果函数要返回整数或指针的话.(4) 根据惯例,寄存器%eax, %edx, 和%ecx被划分为调用者保存(caller save)寄存器.当过程p调用Q时,Q可以覆盖这些寄存器,而不会被破坏任何P所需要的数据. 另外,寄存器%ebx, %esi和%edi被划分为被调用者保存(callee save)寄存器.这意味着Q必须在覆盖他们之前,将这些寄存器的值保存到栈中,并在返回前恢复他们,应为P(或某个更高层次的过程)可能会在今后的计算中需要这些值.此外,根据这里描述的惯例,必须保持寄存器%ebp和%esp.(5) 单操作数的操作符&和*可以产生指针和间接引用指针.也就是,对于一个表示某个对象的表达式Expr,&Expr表示一个地址.对于表示一个地址的表达式Addr-Expr,*Addr-Expr表示该地址中的值.因此,表达式Expr与*&Expr是等价的. 可以对数组和指针应用数组下标操作,如数组引用A[i]与表达式*(A+i)是一样的.它计算第i个数组元素的地址,然后访问这个存储器位置.(6) 数组元素在存储器中是按照"行优先"的顺序排列的,这就意味着先是行0的所有元素,后面是行1的所有元素,以此类推.(7) 一个联合的总的大小等于它最大域的大小.(8) 无论数据是否对齐,IA32硬件都能正确工作.不过,Intel还是建议要对齐数据以提高存储器系统的性能.Linux沿用的对齐策略是2字节数据类型(例如short)的地址必须是2的倍数,而较大的数据烈性(例如int, int*, float和double)的地址必须是4的倍数.注意,这个要求就意味着一个short类型对象的地址最低位必须等于0.类似地,任何int类型的对象或指针的地址的最低两位必须都是0. //============================================2010.07.13深入理解计算机系统(1) 编写高效程序需要两类活动: 第一、我们必须选择一组最好的算法和数据结构; 第二、我们必须编写出编译器能够有效优化以转换成高效可执行代码的源代码.(2) 优化程序性能的基本策略: 1) 高级设计.为手边的问题悬着适当的算法和数据结构.要特别警觉,避免使用会渐进的产生糟糕性能的算法或编码技术.2) 基本编码原则. 避免限制优化的因素,这样编译器就能产生高效的代码. 消除连续的函数调用.在可能时,将计算移到循环之外.考虑有选择的妥协程序的模块性以获得更大的效率. 消除不必要的存储器引用.引入临时变量来保存中间结果.只有在最后的值计算出来时,才将结果存放到数组或全局变量中.3) 低级优化. 尝试各种与数组代码相对的指针形式. 通过展开循环降低循环开销. 通过诸如迭代分隔之类的技术,找到使用流水线的功能单元的方法. 最后的忠告,要小心避免花费精力在令人位家的结果上.一项有用的技术是.在优化代码时使用检查代码(checking code)来测试代码的每个版本,以确保在这一过程中没有引入错误,检查代码将一系列测试应用到程序上,确保它得到期望的结果.(3) 量化评价一个程序中局限性的简单原则: 重复引用同一个变量的程序有良好的时间局限性. 对于具有步长为k的引用模式的程序,步长越小,空间局限性越好.具有步长为1的引用模式的程序有很好的空间局限性.在存储器中以大步长跳来跳去的程序空间局限性会很差. 对于取指令来说,循环有好的时间和空间局限性.循环体越小,循环迭代次数越多,局部性越好.(4) 推荐以下技术: 将你的注意力集中在内部循环上,大部分计算和存储器访问都发生在这里. 通过按照数据对象存储在存储器中的顺序来读取数据数据,从而使得你的程序中的空间局部性最大. 记住,不明中率只是确定你代码性能的一个因素(虽然是重要的).存储器访问数量也扮演着重要角色,有时需要在两者之间做一下折中.//============================================2010.07.19深入理解计算机系统(1) 学习链接只是的原因: 理解连及其将帮助你构造大型程序. 理解连接将帮助你避免一些危险的编程错误. 理解连将帮助你理解其他重要的系统概念. 理解链接将使你能够开发共享库.(2) 一个典型的ELF可重定位目标文件包含下面几个节: .text:以编译程序的机器代码. .rodata:只读数据,如printf语句中的格式串和开关(switch)语句的跳转表. .data:以初始化的全局c变量.局部C变量在运行时被保存在栈中,既不出现在.data中,也不出现在.bss节中. .bss:未初始化的全局变量.在目标文件中这个节不占据实际的空间,它仅仅是一个占位符.目标文件格式区分初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间. .symtab:一个符号表(symbol table),他存放在程序中被定义和引用的函数和全局变量的信息. .rel.text:当连接器把目标文件和其他文件结合时,.text节中的许多位置都需要修改.一般而言,任何调用外部函数或者引用全局变量的指令都需要修改.另一方面,调用本地函数的指令则不需要修改.注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非使用者显式地指示连接器包含这些信息. .rel.data:被模块定义或引用的任何全局变量的信息.一般而言,任何已初始化全局变量的初始值是全局变量或者外部定义函数的地址都需要被修改. .debug:一个调试符号表,其中有些表目是程序中定义的局部变量和类型定义,有些表目是程序中定义和引用的全局变量,有些是原始的C源代码.只有以-g选项调用编译驱动程序时,才会得到这张表. .line:原始C源程序中的行号和.text节中机器指令之间的映射.只有以-g选项调用编译驱动程序时才会得到这张表. .strtab:一个字符串表,其内容包括.systab和.debug节中的符号表,以及节头部中的节名字.字符串表就是以null结尾的字符串序列.(3) 利用static属性隐藏变量和函数的名字. C程序员使用static属性在模块内部隐藏变量和函数声明,就像你在jave和c++中使用public和private声明一样.C源代码文件扮演模块的角色.任何声明带有static属性的全局变量或者函数都是模块私有的.类似的,任何声明为不带static属性的全局变量和函数都是公开的,可以被其他模块访问.尽可能用static书香来保护你的变量和函数时很好的编程习惯.(4) 函数和已经初始化的全局变量是强符号,未初始化的全局变量是弱符号.(5) 根据强弱符号的定义,unix连接器使用下面的规则来处理多出定义的符号: 规则1:不允许有多个强符号. 规则2:如果有一个强符号和多个弱符号,那么选择强符号. 规则3:如果有多个弱符号,那么从这些弱符号中任意选择一个.//============================================2010.07.20深入理解计算机系统(1) 共享库(shared library)是致力于解决静态库缺陷的一个现代创新产物. 共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并在存储器中和一个程序连接起来,这个过程称为动态链接(dynamic linking),是由一个叫动态连接器(dynamic linker)的程序来执行的.(2) 共享库的"共享"在两个方面有所不同.首先,在任何给定的文件系统中,对于一个库只有一个.so文件.所有引用该库的可执行目标文件共享这个.so文件中的代码与数据,而不像静态库的内容那样被拷贝和嵌入到引用它们的可执行的文件中.其次,在存储器中,一个共享库的.text节只有一个副本可以被不同的正在运行的进程共享.(3) java定义了一个标准调用规则,叫做jave本地接口(jave native interface, JNI),它允许java程序调用"本地的"c和C++函数,JNI的基本思想是将本地的C函数,比如说foo,编译到共享库中,如foo.so.当一个长在运行的java程序试图调用函数foo时,java解释程序利用dlopen接口(或者某个类似于此的东西)动态链接和加载foo.so,然后再调用函数foo.(4) 理解ECF很重要的原因: 理解ECF将帮助你理解重要的系统概念. 理解ECF将帮助你理解应用程序是如何与操作系统交互的. 理解ECF将帮助你编写有趣的新应用程序. 理解ECF将帮助你理解软件异常如何工作.(5) 当异常处理程序完成处理后,根据引起一场的事件的类型,会发生一下三种情况中的一种:1. 处理程序将控制返回给当前指令Icurr(当事件发生时正在执行的指令).2. 处理程序将控制返回给Inext(如果没有发生异常将会执行的下一条指令).3. 处理程序终止被中断的程序.(6) 异常的类型有:中断(interrupt), 陷阱(trap), 故障(fault)和终止(abort).(7) 任何逻辑流在时间上和另外的逻辑流重叠的进程被称为并发进程(concurrent process). 而这两个进程就被称为并发运行. 进程和其他进程轮换运行的概念称为多任务(multitasking).一个进程执行它的控制流的一部分的每一时间段叫做时间片(time slice).因此,多任务也叫做时间分片(time slicing).(8) 进程的三种状态: 运行.进程要么在CPU上执行,要么在等待被执行且最终会被调度. 暂停.进程的执行被挂起(suspended),且不会被调度. 终止.进程永远地停止.进程因为三种原因终止:收到一个信号,该信号的默认行为是终止进程;从主程序返回;调用exit函数.(9) 一个终止了但还未被回收的进程成为僵死进程(zombie). //============================================2010.07.21深入理解计算机系统(1) 任意时刻,虚拟页面的集合都分为三个不相交的子集: 未分配的: VM系统还未分配(或者创建)的页.未分配的块没有任何数据和他们相关联,因此也就不占用任何磁盘空间. 缓存的: 当前缓存在物理存储器中的已分配页. 未缓存的: 没有缓存在物理存储器中的已分配页.(2) 显示分配器的一些相当严格的约束条件: 处理任意请求序列. 立即响应请求. 只使用堆. 对齐块(对齐要求). 不修改已分配的块.(3) 与套接字限定相关的流限定: 限定一:输入函数跟在输出函数之后.如果中间没有插入对fflush, fseek, fsetpos或者rewind的调用,一个输入函数不能跟在一个输出函数之后.fflush函数清空与流相关的缓冲区. 限定二:输入函数跟在输入函数之后. 如果中间么有插入对fflush, fseek, fsetpos或者rewind的调用, 一个输出函数不能跟随在一个输入函数之后,除非该输入函数遇到了一个文件结束. 对I/O流的第一个限定能够通过采用在每个输入操作前刷新缓冲区这样的规则来保证实现. 保证实现第二个限定的唯一办法是,对同一个打开的套接字描述符打开两个流,一个用来读,一个用来写; FILE *fpin, *fpout; fpint = fopen(sockfd, "r"); fpout = fopen(sockfd, "w"); 但是这样做也有问题,因为它要求应用程序在两个流上都要调用fclose,这样才能释放与每个流相关联的存储器资源,避免存储器xie lou(bd太龌龊了). fclose(fpin);fclose(fpout);//============================================2010.08.05深入理解计算机系统(1) 四类线程不安全函数. 第一类: 不保护共享变量的函数; 第二类: 保持跨越多个调用的状态的函数; 第三类: 返回指向静态变量的指针的函数; 第四类: 调用线程不安全函数的函数;(2) 死锁. 程序员使用p和v操作顺序不当,以至两个信号量的禁止区域(forbidden region)重叠.重叠的禁止区域引起了一组称为死锁区域(deadlock region)的状态. 死锁是一个相当困难的问题,因为它不总是可预测的.(3) 使用简单而有效的规则来避免死锁. 互斥锁加锁顺序规则: 如果对于程序中每对互斥锁(s, t), 每个既包含s也包含t的线程都按照相同的顺序同时对他们加锁,那么这个程序就是无死锁的.第一章:计算机系统漫游这本书是为这样一些程序员而写的,他们希望通过了解这些部件如何工作以及如何影响程序的准确性和性能,来提高自身的技能.系统中所有的信息--包括磁盘文件,存储器中的程序,存储器中存放的用户数据以及网络上传送的数据,都是由一串比特表示的.c语言是贝尔实验室的Dennis Ritchie于1969-1973年间创建的.美国国家标准化组织在1989年颁布了ANSI C的标准.该标准定义了C语言和一系列函数库,即所谓的标准C库.c和unix操作系统关系密切.c从开始就是作为一种用于unix系统的程序语言开发出来的.unix内核的大部分,以及所有它支持的工具和函数库都是用c语言编写的.c是一个小而简单的语言,c语言的设计是由一个人完成的,其结果就是这是一个简洁明了,没有什么冗赘的设计. c是为实践目的设计的.c是设计用来实现unix操作系统的.它是系统编程的首选.同时它非常适用于应用级程序的编写.预处理器,编译器,汇编器和连接器一起构成了编译系统.汇编语言是非常有用的,因为它为不同的高级语言的不同编译器提供了通用的输出语言.GNU环境包括EMAC编译器,GCC编译器,GDB调试器,汇编器,连接器,处理二进制文件的工具以及其他一些部件.shell是一种命令行解释器,它输出一个提示符,等待你输入一行命令,然后执行这个命令.如果该命令的第一个单词不是一个内置的shell命令,那么shell就会假设这是一个可执行文件的名字,要加载和执行该文件.每个I/O设备都是通过一个控制器或适配器于I/O总线连接起来的.控制器和适配器之间的区别主要在它们的组成方式.控制器是I/O设备本身中或是主板上的芯片组,而适配器则是一块插在主板插槽上的卡.主存是临时存储设备,在处理器执行程序时,它被用来存放程序和程序处理的数据.物理上来说,主存是由一组DRAM芯片组成的,逻辑上来说,存储器是由一个线性的字节数组组成的.中央处理单元简称处理器,是解释存储在主存中指令的引擎.处理器的核心是一个被称为程序计数器的字长大小的存储设备.在任何一个时间点上,PC都指向主存中的某条机器语言指令.从系统通电开始,直到系统断电,处理器一直在不假思索的重复执行相同的基本任务:从程序计数器指向的存储器处读取指令,解释指令中的位,执行指令中的简单操作,然后更新程序计数器指向下一条指令,而这条指令并不一定在存储器中和刚刚执行的指令相邻.利用称为DMA(直接存储器存取)的技术,数据可以不通过处理器而直接从磁盘到达主存.常字符串的显示过程:存储器到寄存器文件,再从寄存器文件到显示设备.一个典型的寄存器文件只存储几百字节的信息,主存里可存放几百万字节.然而,处理器从寄存器文件中读数据比从主存中读取要快几乎100倍.。