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

2021/6/3 10:21:36

本文主要是介绍阅读笔记--深入理解计算机系统,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

深入理解计算机系统
B站搬运工:https://www.bilibili.com/video/BV18D4y1d7s3

第一章 计算机系统漫游

一个程序从创建到运行需要经历预处理、编译、链接、执行4个过程

1.1 信息就是位+上下文

  • 源程序实际就是由0/1组成的位序列。8位被组成一组,称为字节。( 比如int类型为4字节,32位;char为1字节,4位;)
  • 系统中所有的信息–包括磁盘文件、内存中的程序、内存中存放的用户数据以及网上传送的数据都是由一串比特表示的。
    字、字节、比特、位之间的关系
    位 (bit)
    来自英文bit,音译为“比特”,表示二进制位。位是计算机内部数据储存的最小单位,11010100是一个8位二进制数。一个二进制位只可以表示0和1两种状态(21);两个二进制位可以表示00、01、10、11四种(22)状态;三位二进制数可表示八种状态(23)……。
    字节 (byte)
    字节来自英文Byte,音译为“拜特”,习惯上用大写的“B”表示。
    字节是计算机中数据处理的基本单位。计算机中以字节为单位存储和解释信息,规定一个字节由八个二进制位构成,即1个字节等于8个比特(1Byte=8bit)。八位二进制数最小为00000000,最大为11111111;通常1个字节可以存入一个ASCII码,2个字节可以存放一个汉字国标码。

    计算机进行数据处理时,一次存取、加工和传送的数据长度称为字(word)。一个字通常由一个或多个(一般是字节的整数位)字节构成。例如286微机的字由2个字节组成,它的字长为16;486微机的字由4个字节组成,它的字长为32位机。
    计算机的字长决定了其CPU一次操作处理实际位数的多少,由此可见计算机的字长越大,其性能越优越。

1.2 程序被其他程序翻译成不同的格式

(1) 预处理阶段
预处理器根据以字符#开头的命令,修改原始的C程序。比如对#include<stdio.h>的处理就是把“stdio.h”头文件完整的插入到程序文本中生成另一个C程序,该程序以.i为文件扩展名。也就是hello.i文件
(2) 编译阶段
通过汇编器 (ccl) 修改hello.i文件生成汇编程序hello.s,也就是把源程序编译为汇编程序。hello.s文件中的部分内容如下图所示:

(3) 汇编阶段
汇编器 (as) 将hello.s翻译成机器码。生成hello.o文件
(4) 链接阶段
比如hello文件调用了printf函数,printf函数存在于一个名为printf.o的单独的预编译好了的目标文件中,这种文件需要以某种方式合并到hello.o程序中,链接器就是负责这种合并。结果得到hello文件,这就是一个可执行目标文件。

通过预处理,编译,汇编,链接的操作过程,hello.c程序先后转变为hello.i, hello.s, hello.o, 最后生成hello可执行程序。
1.4 处理器读并解释存储在内存中的指令
1.4.1 系统的硬件组成
典型计算机系统的硬件组成主要包括4部分:总线、I/O设备、主存、处理器。

寄存器是CPU的内部组成单元,是CPU运算时取指令和数据的地方,速度很快,寄存器可以用来暂存指令、数据和地址
按照距离CPU的远近排序:寄存器、缓存、内存

1.5 高速缓存至关重要
程序的加载就是从磁盘到处理器,这个过程好需要经过主存。程序的执行可以说是一个不断复制的过程,复制的快慢决定了整个程序执行的开销。为了缩短处理器和主存、磁盘之间的速度差异,利用高速缓存 (缓存距离处理器的距离比较近) 提前将待执行的程序复制到缓存里进而缩短程序执行的开销。这就是高速缓存存在的意义。
比较新的系统一般是有3级高速缓存:L1、L2和L3。
为什么CPU缓存会分为一级缓存L1、L2、L3?有什么意义?
其实缓存的意义很广泛:电脑整机最大的缓存可以体现为内存条、显卡上的显存就是显卡芯片所需要用到的缓存、硬盘上也有相对应的缓存、CPU有着最快的缓存(L1、L2、L3缓存等),缓存就是数据交换的缓冲区(称作Cache)。缓存往往都是RAM(断电即掉的非永久储存),它们的作用就是帮助硬件更快地响应。
(1) CPU缓存是什么?
CPU缓存的定义为CPU与内存之间的临时数据交换器,它的出现是为了解决CPU运行处理速度与内存读写速度不匹配的矛盾——缓存的速度比内存的速度快多了。CPU缓存一般直接跟CPU芯片集成或位于主板总线互连的独立芯片上。(现阶段的CPU缓存一般直接集成在CPU上)CPU往往需要重复处理相同的数据、重复执行相同的指令,如果这部分数据、指令CPU能在CPU缓存中找到,CPU就不需要从内存或硬盘中再读取数据、指令,从而减少了整机的响应时间。
(2) 三级缓存(L1、L2、L3)是什么?
以近代CPU的视角来说,三级缓存(包括L1一级缓存、L2二级缓存、L3三级缓存)都是集成在CPU内的缓存,它们的作用都是作为CPU与主内存之间的高速数据缓冲区,L1最靠近CPU核心;L2其次;L3再次。运行速度方面:L1最快、L2次快、L3最慢;容量大小方面:L1最小、L2较大、L3最大。CPU会先在最快的L1中寻找需要的数据,找不到再去找次快的L2,还找不到再去找L3,L3都没有那就只能去内存找了。L1、L2、L3可以说是各有特点,下面我们就分开来讲一下。

  1. 一级缓存(L1 Cache)
    一级缓存这个名词出现应该是在Intel公司Pentium处理器时代把缓存开始分类的时候,当时在CPU内部集成的CPU缓存已经不能满足整机的性能需求,而制造工艺上的限制不能在CPU内部大幅提高缓存的数量,所以出现了集成在主板上的缓存,当时人们把CPU内部集成的CPU缓存称为一级缓存,在CPU外部主板上的缓存称为二级缓存。
    而一级缓存其实还分为一级数据缓存(Data Cache,D-Cache,L1d)和一级指令缓存(Instruction Cache,I-Cache,L1i),分别用于存放数据及执行数据的指令解码,两者可同时被CPU访问,减少了CPU多核心、多线程争用缓存造成的冲突,提高了处理器的效能。一般CPU的L1i和L1d具备相同的容量,例如I7-8700K的L1即为32KB+32KB。
  2. 二级缓存(L2 Cache)
    随着CPU制造工艺的发展,本来处于CPU外部的二级缓存也可以轻易地集成进CPU内部,这种时候再用缓存是否处于CPU内部来判断一二级缓存已经不再确切。集成进CPU的L2二级缓存运行速率渐渐可以跟上CPU的运行速度了,,其主要作用为当CPU在L1中没读取到所需要的数据时再把数据展示给CPU筛选(CPU未命中L1的情况下继续在L2寻求命中)。
    L2二级缓存比L1一级缓存的容量要更大,但是L2的速率要更慢,为什么呢?首先L2比L1要更远离CPU核心,L1是最靠近CPU核心的缓存,CPU需要读取L2的数据从物理距离上比L1要更远;L2的容量比L1更大,打个简单的比喻,在小盒子里面找东西要比在大房间里面找要方便快捷。这里也可以看出,缓存并非越大越好,越靠近CPU核心的缓存运行速率越快越好,非最后一级缓存的缓存容量自然是够用即可。
    L2二级缓存实际上就是L1一级缓存跟主内存之间的缓冲器,在2006年的时间点上,Intel和AMD当家在售的几款处理器可以看出他们对最后一级缓存不同的见解:Intel Core Duo不同于它的前辈Pentium D、EE,采用了双核心共享的2M L2二级缓存,是属于当时最先二级缓存架构,即“Smart Cache”共享缓存技术,这种技术沿用到以后的Intel推出的所有多核心处理器上;而AMD Athlon 64 X2处理器则是每个CPU核心都具备独立的二级缓存,Manchester核心的处理器为每核心512KB、Toledo核心为每核心1MB,两个核心之间的缓存的数据同步是通过CPU内置的SRI(系统请求接口)控制,这样的数据延迟及占用资源情况都要比Intel的Pentium D、EE核心要好,但还是比不上Core为代表的Smart Cache缓存共享。
  3. 三级缓存(L3 Cache)
    最初出现L3三级缓存的应该是AMD的K6-III处理器,当时受限于制造工艺,L3只能集成在主板上。然后Intel首次出现L3三级缓存的是Itanium安腾服务器处理器,接着就是P4EE和至强MP。L3三级缓存的出现其实对CPU性能提升呈一个爬坡曲线——L3从0到2M的情况CPU性能提升非常明显,L3从2M到6M提升可能就只有10%不到了,这是在近代CPU多核共享L3的情况下;当L3集成进CPU正式成为CPU内部缓存后,CPU处理数据时只有5%需要在内存中调用数据,进一步地减少了内存延迟,使系统的响应更为快速。
    同理,L3即为L2与主内存之间的缓冲器,主要体现在提升处理器大数据处理方面的性能,对游戏表现方面有较大的帮助。那么也许有人就会问了,是不是选择CPU的时候看准L3买,哪个CPU的L3大就买哪个?非也,只有同架构的情况下这种比较才具有意义,先举个比较久远的例子:Intel具备1MB L3的Xeon MP处理器仍然不是AMD没有L3的皓龙处理器对手,再来个现有的:Intel I7-8700K 12MB L3和AMD Threadripper 1950X 32MB L3相比,自然是32MB比12MB大,但是平均下来也是一个核心2MB L3,性能就见仁见智了。
    (4) CPU缓存是怎样帮助CPU工作
    由于数据的局限性,CPU往往需要在短时间内重复多次读取数据,内存的运行频率自然是远远跟不上CPU的处理速度的,怎么办呢?缓存的重要性就凸显出来了,CPU可以避开内存在缓存里读取到想要的数据,称之为命中(hit)。L1的运行速度很快,但是它的数据容量很小,CPU能在L1里命中的概率大概在80%左右——日常使用的情况下;L2、L3的机制也类似如此,这样一来,CPU需要在内存中读取的数据大概为5%-10%,其余数据命中全部可以在L1、L2、L3中做到,大大减少了系统的响应时间,总的来说,所有CPU读取数据的顺序都是先缓存再内存。

1.6 存储设备形成层次结构

1.7 操作系统管理硬件
操作系统就是应用程序和硬件之间的一层软件,应用程序都是通过操作系统来对硬件进行操作。

(1) 操作系统的两个基本功能:

  • 防止硬件被失控的应用程序滥用
  • 向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备
    (2) 以上两个基本功能是通过抽象来实现:进程、虚拟内存和文件。
  • 文件是对I/O设备的抽象表示
  • 虚拟内存是对主存和磁盘I/O设备的抽象表示
  • 进程是对处理器、主存和I/O设备的抽象表示

1.7.1 进程
(1) 什么是进程
进程是操作系统对一个正在运行的程序的一种抽象。从一个进程到另一个进程的转换是由操作系统内核管理。内核不是一个单独的进程,它是系统管理全部进程所用代码和数据结构的集合。
(2) shell进程和hello进程的上下文切换

注意Linux系统中没有线程的概念。Linux中的线程就是轻量级的进程
1.7.2 线程
线程是进程执行的一个实体。线程区别于进程主要是线程之间共享地址内存空间。
1.7.3 虚拟内存
虚拟内存是一个抽象的概念,它使得每个进程认为自己独占地使用内存。每个进程看到的内存都是一样的,即虚拟地址空间。在Linux系统中,地址空间最上面的区域是留给操作系统中的代码和数据的,对所有进程都一样;地址空间的地步区域存放用户进程定义的代码和数据。
内存管理
在C++中,内存分成5个区,他们分 别是堆、栈、自由存储区、全局/静 态存储区和常量存储区。

  1. 栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 (Linux下栈的大小默认是8M)
  2. 自由存储区,(自由存储区也是堆上分出的一块内存,只不过在C++中称为自由存储区)就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
  3. 堆,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
  4. 全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
  5. 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

1.7.4 文件
文件就是字节序列,仅此而已
1.8 系统之间通过网络通信
1.9 重要主题
1.9.1 Amdahl定律
Amdahl定律的主要观点——想对一个系统进行显著的加速,必须提升全系统中相当大的部分的速度。
1.9.2 并发和并行
如果有两个进程,两个CPU,两个这两个进程同时执行叫做并行。如果有3个进程,这三个进程交替执行叫做并发。
(1) 线程级并行

超级线程:允许一个CPU执行多个控制流的技术。
(2) 指令级并行
在较低的抽象层次上,现代处理器可以同时执行多条指令的属性称为指令级并行。
(3) 单指令、多数据并行
一条指令可以产生多个可并行执行的操作。
第一部分 程序结构和执行
第2章 信息的表示和处理
1字=2字节=16位(比特)
三种重要的数字表示:

  • 无符号数编码是基于传统的二进制表示法,表示大于等于0的数字
  • 补码编码表示有符号整数的最常见的方式,有符号整数就是可以为正或者为负的数字
  • 浮点数编码表示实数的科学计数法的以2为基数的版本
    2.1 信息存储
    机器级程序将内存视为一个非常大的字节数组,称为虚拟内存。内存的每个字节都由一个唯一的数字来标识,称他为地址,所有可能的集合称为虚拟地址空间。
    int a[] = {1,2};
    cout << a << endl; // 0x61fec8
    cout << a+1 << endl; // 0x61fecc

2.1.1 十六进制表示法
2.1.2 字数据的大小

int类型可以表示的数据范围是 − 2 31 ∼ 2 31 − 1 -2^{31} \sim 2^{31}-1 −231∼231−1(因为存在符号位)
1GB大概可以存储的字符数为 2 30 2^{30} 230

2.1.3 寻址和字节顺序
(1) 数据的存储
一个int类型的数据是由4个字节组成,多字节对象在计算机内是被存储为连续的字节序列。假如一个变量x地址为0x100,那么这个数据x将被存储在内存的0x100、0x101、0x102、0x103位置。
(2) 字节顺序:小端法和大端法

2.1.4 表示字符串
2.1.5 表示代码
2.1.6 布尔代数简介
2.1.7 C语言中的位级运算

  • | 或
  • & 与
  • ~ 取反
  • ^ 异或
    2.1.8 C语言中的逻辑运算
  • || 或
  • && 与
  • !非
    2.1.9 C语言中的位移运算
  • 右移 右移分为逻辑右移和算术右移;逻辑右移在左端补k个0,算术右移在左端补k个最高有效位。

  • << 左移

2.2 整数表示
https://www.jianshu.com/p/35cf507ebe7f
(1) 如何求正数、负数的补码
计算机中的有符号数是以补码的形式表示的。正数的补码就是其本身;负数的补码是原码取反加1
(2) int类型和char类型
https://blog.csdn.net/unix21/article/details/8606684?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control&dist_request_id=1332037.9671.16191643855811093&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control

2.2.1 整形数据类型

C/C++都支持无符号数和有符号数、但是Java只是支持有符号数

2.2.2 无符号数的编码

给定一个无符号的二进制数 x = [ x w − 1 , . . . , x 0 ] x = [x_{w-1}, ..., x_{0}] x=[xw−1​,...,x0​],那么其对应的十进制数为 B 2 U w ( x ) = ∑ i = 0 w − 1 x i 2 i B2U_{w}(x) =\sum_{i=0}^{w-1}x_{i}2^{i} B2Uw​(x)=i=0∑w−1​xi​2i
2.2.3 补码编码
有符号数的计算机表示形式就是补码。

https://blog.csdn.net/u012209626/article/details/44753185
2.2.4 有符号数和无符号数之间的转换

2.3 整数运算
(1) 正溢出和负溢出
两个正数相加如果相加的结果为负,则是发生了负溢出;
两个负数相加如果相加的结果为正,则是发生了正溢出;
2.4 浮点数
第三章 程序的机器级表示



这篇关于阅读笔记--深入理解计算机系统的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程