阶段性阅读 《深入理解计算机系统》(CSAPP)(一)

Posted by Sutdown on September 11, 2023

前言:

声明:以下内容是通过看些视频和书籍综合的知识点,所以一般不是书籍某一章节完整的笔记,仅凭借个人兴趣看某一节的内容得到。

前言:我是在一边看南京大学袁春风老师的计算机系统基础这门课程的同时阅读这本书的,本篇仅仅记录这段时间产生的对于计算机系统基础的问题和一些自己看书后对自己的回答,水平有限,本意是用作督促自己的学习。

对于书上的内容,会在章节中优先选择性的去看与我目前学习有关的部分,对于某些难以理解的概念,在前面会先选择性跳过,在这之后再新学到其它内容能看懂时后会进行补充。

其中的视频推荐是指在学习某个部分时,单独看书过于难懂时查阅的合适的辅助学习视频。

1.操作系统的基本抽象概念

(此问题1学习水平有限,为免理解偏差,部分参考chatgpt回答)

操作系统可以理解为应用程序对硬件操作的中间商,它存在两个基本功能

1)防止硬件被失控的应用程序滥用

2)向应用程序提供简单一直的机制来控制复杂而又通常大不相同的低级硬件要求。

操作系统有几个基本的抽象概念,分别是进程,虚拟内存,文件。

图一:计算机系统抽象层的转换

image-20230831113942118

进程

进程是操作系统中的一个基本概念,它代表了正在运行的程序的实例。每个进程都有自己的独立执行环境,包括内存空间、寄存器状态、打开的文件等。操作系统通过进程管理器来创建、调度、暂停、恢复和终止进程。多个进程可以并发运行(并发运行指的是一个进程的指令和另一个进程的指令交错执行),无论在单核处理器还是多核处理器中,操作系统通过分配时间片来实现进程之间的切换,从而实现多任务。

虚拟内存

虚拟内存是一种内存管理技术,它允许一个进程访问超出其物理内存限制的内存空间。每个进程认为自己有一个连续的虚拟地址空间,而实际上这些地址可能映射到物理内存中的不同位置,甚至可以映射到硬盘上的一部分(称为页面交换)。虚拟内存的好处包括更好的内存利用率、更大的可用地址空间和更好的内存隔离。操作系统负责虚拟地址到物理地址的映射,以及数据的页面调度和页面置换。

虚拟内存的运作需要硬件和操作系统软件之间精密复杂的交互,包括对处理器的生成的每个地址的硬件翻译。基本思想是把一个进程虚拟内存的内容存储在磁盘上,然后用主存作为一个磁盘的高速缓存。

图二:进程的虚拟地址空间

进程的虚拟地址空间

文件

在操作系统中,文件是存储在永久性存储介质(如硬盘、固态硬盘)上的数据集合,也就是字节序列。文件可以包含文本、图像、音频、视频等各种类型的数据。操作系统通过文件系统管理文件,提供了访问、创建、删除、修改文件的接口。文件系统还负责文件的组织、保护和共享。在许多操作系统中,文件是通过文件描述符或句柄来表示的,它是一个抽象的数据结构,用于跟踪文件的打开、读取、写入等操作。

2.针对存储器的层次结构

计算机存储系统

该图为计算机的存储系统,我们再以另一种图观察,

存储器结构的中心思想

存储器层次结构

这是存储器的层次结构。

(注解:

ALU是算术逻辑单元,一般可用于存储临时地址和计算;register是寄存器,可见上一篇汇编语言中的详细介绍;

cache高速缓存器;Memory主存储器;HardDisk硬盘;

TLB转换旁路缓存;MMU内存管理单元;这两者设计虚拟地址和物理地址的转换,下一个问题讨论

需要理解,从Register,cache,Memory,HardDisk依次离CPU越来越远,受到CPU的控制越来越弱,所以存储空间越来越大,读取速度越来越低。

中心思想:当我们需要取第k+1层的数据时,位于k层的更快更小的设备作为位于k+1层的更大更慢的存储设备的缓存;也就是层次结构中的每一层都缓存来自上一层的数据对象。

比如当我买需要第k层的某个数据对象d时,我们会现在上一层中寻找(上一层相当于这一层的子集,上一层是更接近CPU的一层),如果刚好找到,这可以称为缓存命中,因此可以加快读取时间;否则称为缓存不命中,此时,会直接在第k层寻找,然后传给上一层,再逐渐传给CPU,那如何传给上一层?传给上一层的数据放在哪里?

每一层的位置并不是空的,可以说,上一层是第k层的子集,而当传递数据时到上一层时只能覆盖上一层原有的数据,上一层被覆盖的数据称为牺牲快,其中的过程称为替换或者驱逐,那时如何覆盖的?有两种策略,一是随机覆盖,速度很快,但是不确定性很高,难以定位;二是映射的思想,第k层的哪些数据对应上一层的哪个快的部分,比如上一层是1 4 8,下一层是 2 3 5 6 7 9,那找数据4时,可以缓存命中直接在上一层找到,找数据3时,在下一层找到,可以假设2 3对应1,然后3覆盖上一层的1(只是这样子抽象举例)。

局部性原理

但对于一个良好的计算机程序而言,通常具有良好的局部性,也就是当它们倾向于使用临近于其它最近引用过的数据项的数据项或者最近引用的数据项本身,这种倾向称为局部性原理。在硬件层,该原理允许通过引入cache的小而迅速的存储器保存最近引用的指令和数据项,从而提高对于主存的访问速度。局部性原理在操作系统的各个层次运用广泛。

局部性通常分为时间局部性和空间局部性,时间局部性可以简单理解成一个相同位置的变量被引用多次;空间局部性就是对于步长为k的引用模式的程序,步长越小,空间局部性越小。

对于空间局部性,我们可以举例,一个二维数组m行n列,双层for循环找到其中每一个数的值。很自然有两种写法,一是以行为单位,一行行找;另一种是一列列找;数组中虽说是二维,但在计算机中仍然是以一行连续的内存单元存储,所以每行找时它的步长为1,而以列找时步长为n,速度差异显而易见。

高速缓存存储器(cache)

C:\Users\向菲\Pictures\计算机科学与技术

最初的存储器层次结构只有三层,寄存器,主存储器和磁盘存储,不过随着CPU和主存间的举例逐渐增大,二者这件被迫增加了L1高速缓存,而由于CPU和主存间性能差距继续增大,因此插入了L2高速缓存。有些现代系统还包括了L3高速缓存。

通用结构

高速缓存结构

(a)高速缓存大小C=每个高速缓存的字节数B(Byte)×每个组的行数E×组数S(Series);

(b)高速缓存结构:地址位m=标记位t+组索引s+快位移b; \((B=2^b,S=2^s)\)

直接映射高速缓存(E=1)

假设只有寄存器,L1高速缓冲,和主存时

CPU向内存寻求w这个字,首先在L1中确认是否有这个字,若有则缓冲命中;若没有则缓冲不命中,L1向主存寻求w的副本并且存储到它的高速储存行再返回到CPU。在L1中确定是否命中的过程就是直接映射高速缓存的体现。

字w中如上通用结构中,

1)通过组索引确认在哪一组

2)先后通过标记位和快偏移得到准确位置

组相联映射高速缓存

这个与直接映射高速缓存的最大不同在于E,组相联放松了这个限制,E可以大于1,当每个组有n行时,称为n路组相联映射高速缓存。较高的关联度(E的值越大),可以降低高速缓存中由于冲突不命中出现的抖动的可能性。

同样,当缓存不命中出现需要替换的行时,有两种策略:

1.随机选择要替换的行

2.利用局部性原理,使其选择在比较近的将来引用被替换的行的概率较小

全相联高速缓存

特点:只有1组。

由于该方式需要并行的搜索许多相匹配的标记,构造一个又大又快的相联高速缓存很困难,而且很昂贵。因此这种方法只适合于较小的高速缓存,例如虚拟内存系统中的快表(TLB),它缓存页表项等。

关于写的相关思路

首先把要写入的字w的副本放在高速缓存器中,有两种方案进行接下来的处理: 1.(主流),利用写回写分配的思路,

写回:在替换算法要更新这个驱逐的快时,才把它写入紧接的低一层中。减少了总线流量,增加了复杂性

写分配:加载相应的低一层中的块到高速缓存中,然后更新这个高速缓存快,不命中会导致一个块从低一级到达高速缓存。

2.直写和非写分配。

如何写一个高速缓存友好的代码

首先,我们要了解从哪个方面入手:

从组相联映射高速缓存中我们不难看出,利用高速存储区的结构和访问数据的思路,让最常见的情况运行的快;

然后同时也要减少每个循环内部的不命中数量。

在代码上的改进有:

一是对局部变量的反复引用是时间局部性好的体现;

二是步长为1的引用模式是空间局部性好的体现。

注:视频推荐:

1.B站理解分块和cache:【一张图解决主存和cache的映射问题】 https://www.bilibili.com/video/BV1h3411h7kV/?share_source=copy_web&vd_source=3fb5d6e30320f23cfaa7814e883f9b2f

2.B站up洛城花客视频:【《C专家编程》7. 对内存的思考(存储器的层次结构)】 https://www.bilibili.com/video/BV1Ed4y1Y7HN/?share_source=copy_web&vd_source=3fb5d6e30320f23cfaa7814e883f9b2f

3.虚拟内存

对其概念的基本问题:虚拟内存是如何产生的?虚拟内存是什么?什么时候涉及虚拟内存?虚拟内存有什么用?C语言中的malloc和free的动态内存分配和虚拟内存有什么关系?虚拟内存和物理内存有什么关联吗?

基本概念

虚拟内存是硬件异常,硬件地址翻译,主存,磁盘文件和内核软件的完美交互,它为每一个进程提供了一个大的,一致的和私有的地址空间。早期PC大多采用物理地址寻址,现在大多是CPU通过生成虚拟地址经过地址翻译后访问主存。

内存是用来存放正在进行或者将要进行的进程内容;

磁盘是用来存放需要存储的内容。

物理寻址:CPU通过内存总线,传递给主存,主存取出地址,返回给CPU,CPU将其存放在一个寄存器里。

虚拟寻址:CPU通过经过地址翻译(将虚拟地址转换成为物理地址),再送到主存。

DRAM(Dynamic Random Access Memory,动态随机存取存储器)是一种基于电容的内存,它使用电容来存储和表示数据。每个存储单元由一个电容和一个访问晶体管组成。电容在存储器中充电或放电来表示数据的0和1。由于电容会逐渐漏电,DRAM需要定期刷新以保持数据的正确性。DRAM缓存的优点是它可以提高地址翻译的速度,简化链接和加载,实现代码和数据共享,以及保护每个进程的地址空间不被其他进程破坏。故常用来表示虚拟内存系统的缓存

SRAM(Static Random Access Memory,静态随机存取存储器)是一种基于触发器的内存,它使用稳定的存储电路来存储和保持数据。每个存储单元由一个存储器单元和控制电路组成,其中存储器单元由多个触发器构成,能够存储比特数据。由于采用了触发器结构,SRAM在不断刷新的过程中保持数据的稳定性。常用SRAM缓存表示CPU和主存之间的L1,L2,L3高速缓存

页表也就是页表条目(Page Table Entry)其中包含有效位和地址信息。

虚拟页面的集合也包括三个部分,未分配的(未分配和创建,没有任何磁盘空间),缓存的(已经缓存在物理系统中的已分配页),未缓存的(未缓存在物理内存中的已分配页)。

虚拟内存的两个主要功能

一是将主存看作存储在磁盘上地址空间的高速缓存,再主存中只保留活动区域,并且根据需要在主存和磁盘之中传递数据,将主存中不常用的存入磁盘,将磁盘中的需要内容传给主存,以此高效利用主存。

二是在多进程的情况下,为每个进程提供一致的地址空间,并且保护其不受破坏,简化内存管理。

注:虚拟地址和物理地址都有相应的地址空间。也就是说,每个数据对象,每个数据对象有多个独立空间的地址,每个地址选自不同的地址空间,从而保证进程的独立性。但同时,虚拟地址所需的地址空间也会占内存(后面会有相应的解决方案)。

虚拟内存系统(VM)的大概使用

当进程过多时,内存中容易发生内存泄露,部分内存无法被释放,逐渐堆积容易造成内存溢出。

VM的出现可以解决这一问题(DRAM是全相联)。

我们要认清,一般认为虚拟地址空间是一段连续的内存空间,但实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。页表和物理内存一般在DRAM中,虚拟内存在磁盘中。

页表,物理内存,虚拟内存是并行的。CPU首先引用虚拟内存中的某个地址,然后在页表中读取它的状态,

情况一:页命中。通过页表中的有效位判断发生命中,再根据地址信息找出物理地址的位置即可。

情况二:缺页。(也可以理解成缓存不命中)通过页表的有效位发现不命中,然后当物理内存中是满的时候,需要选择一个牺牲页被我们所需要的地址替换。选择牺牲页是,一由于DRAM中是全相联,可以选择任意个;二为降低不命中率,只要局部性原理用的好,可以充分提高运行时的效率,局部性原理能够保证程序将趋向于在一个较小的活动页面的集合上工作,将工作集页面调度到内存之后,接下来对这个工作集的引用将直接命中,而不会产生额外的磁盘流量。也对应基本概念中虚拟内存两个主要功能之一。

地址翻译

地址翻译:虚拟地址空间和物理地址空间的映射。

一:从地址角度上看

自行理解图片。

二:计算机中对于虚拟地址的执行过程

a.页面命中

1)处理器首先生成虚拟地址(Virtual Adress)给内存管理单元(Memory Manage Unit);MMU利用虚拟地址中中的虚拟页号(Virtua Page Number)确立适当的页表项地址(Page Table Entry Adress),将PTEA传送给高速缓存/存储器然后高速缓存/存储器将页表项(Page Table Entry)给到MMU;

2)MMU根据PTE中的物理页号(Physical Page Number)和虚拟地址中的虚拟地址偏移量(虚拟地址偏移量和物理地址偏移量相同),得到最终的物理地址(Physical Adress),再将其传送给高速缓存/存储器,高速缓存/存储器将该数据字给处理器。

注(优化):在图中的②位置中,如果MMU频繁的和内存取拿数据,每次取数据可能会有几十到上百周期时间的代价,因此为减少这样的开销,在MMU中有一个TLB(Translation Lookaside Buffer)块表,这其中包含多项单个PTE的值,这样所有的地址翻译步骤都在芯片上的MMU中执行,提高运行速度。

b.缺页

第一步和以上相同,但是在得到PTE时,发现其中的有效位为0,发生缺页的情况,此时发生异常,缺页异常处理程序启功,将牺牲页和新页在高速缓存/存储器和洗盘上交换位置再重新更新PTE的位置传给MMU,后面过程也与页面命中情况一致。

SRAM中的高速缓存和DRAM中的虚拟内存的结合

主要思想:地址翻译发生在高速缓存查找之前。

如右图:

注(优化):我们在上文提到,虚拟系统的存在在内存空间中占的空间较多,即使在时间性能上进行了优化也没有直接的解决这个问题。看虚拟内存系统,其中的虚拟地址大多在硬盘中,主要关注点在于页表。故此有压缩页表的想法被提出,也就是使用层次结构。这样有两个好处:

一是如果一级页表的PTE是空的,那么二级页表就不存咋,不会为其分配空间。而不是将所有空间都先分配出来。

二是我们只将一级页表和常用的二级页表放在主存中,其余情况只在需要时创建,调入或者调出二级页表即可,这样能降低主存的压力。

进程和虚拟内存

内存映射

在Linux上通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域上的内容,这个过程称为内存映射。

它的方式有两种:1)Linux系统中的普通文件;2)匿名文件。

此方法可以初始化虚拟页面,在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数。

共享对象

在进程中,一个对象被映射到虚拟内存的一个区域,要么作为共享对象,要么作为私有对象。

共享对象是如果一个进程将一个共享对象映射到它的虚拟地址空间的一个区域内,那么这个进程对这个区域内的任何写操作,对于哪些也把这个共享对象映射到它们虚拟内存的其它进程而言,也是可见的。并且,页表的部分相同也是共享对象成立的条件。

私有对象将对象作为私有的重要技术是叫做“写时复制”,也就是在原区域中试图修改内容时,回触发故障处理程序,故障处理程序检测到保护异常是由于进程试图写私有文件时,会在该物理内存中创建新的父母,更新页表条目指向新的副本,然后恢复这个页面的可写权限,如下图:

虚拟内存的作用

虚拟内存是一种内存管理技术,它可以为每个程序提供一个独立的虚拟地址空间,使得程序不需要考虑物理地址的分配和重定位等问题,而只需要使用相对地址或者逻辑地址来表示代码和数据的位置。

1)简化链接。

链接是指将多个目标文件或者库文件连接成一个可执行文件的过程,它可以分为静态链接和动态链接两种方式。静态链接是指在编译时或者加载时,将所有的目标文件或者库文件合并成一个可执行文件,这个可执行文件包含了所有需要的代码和数据,可以独立运行。动态链接是指在运行时,根据需要动态地加载和连接目标文件或者库文件,这些文件不是包含在可执行文件中,而是存储在外部的共享库中,可以被多个程序共享。

虚拟内存对于静态链接来说,可以简化加载和重定位的过程,因为程序可以使用相对地址或者逻辑地址来表示代码和数据的位置,而不需要修改为绝对地址或者物理地址。虚拟内存对于动态链接来说,可以简化共享库的管理和访问的过程,因为程序可以使用虚拟内存映射来加载和连接共享库,而不需要复制或者移动共享库的代码和数据。

2)简化加载。

加载是指将可执行文件从磁盘或者网络加载到内存中,并准备运行的过程,它包括分配内存空间、解析符号、重定位地址、初始化数据等步骤。

Linux加载器为代码和数据段分配虚拟页,把他们标记为无效,将页表条目指向目标文件中适当的位置。加载器不需要从磁盘到内存中复制任何数据。在每个页面被初次引用时,一本是被CPU取指令时引用或者一条正在执行的指令引用一个内存位置时引用,虚拟系统会自动调入数据页。

3)简化共享。

独立地址空间为操作系统提供了一个管理用户进程和操作系统之间共享的一致机制。操作系统创建页表,将相应的虚拟页映射到不连续的物理页面。页面的一致性使得数据之间可以共享。

4)简化内存分配。

减少内存分配和回收的开销:虚拟内存可以通过按需加载技术,使得程序不需要一次性将所有的代码和数据都加载到内存中,而只需要在需要时按需加载所需的部分。这样可以节省内存空间,减少加载时间,以及避免不必要的内存回收操作。

缓解物理内存不足的问题:虚拟内存可以通过页面置换技术,使得程序不需要受限于物理内存的大小,而可以使用外部磁盘作为扩展的内存空间。当物理内存不足时,虚拟内存可以将一些不常用的页面从物理内存中移出,并将一些需要用到的页面从磁盘中调入物理内存中。这样可以增加可用的内存空间,以及实现对大型程序或者数据结构的支持。

支持动态内存分配技术:虚拟内存可以通过分段机制或者堆机制,使得程序可以根据需要动态地申请和释放可变大小的内存空间。这样可以灵活地适应不同的需求,以及实现对递归、动态数组、链表等数据结构的支持。

动态内存分配

(这一部分跟C语言的指针没学好有点关系。。。应该没写清楚)

动态内存分配器(Dynamic Memory Allocator)维护着一个进程的虚拟内存区域——堆(heap)。分配器将堆视为一组不同大小的块的集合进行维护。每个块就是一个连续的虚拟内存片(chunk)。其中分为已分配的和空闲的部分。已分配的要么是被应用程序显式已分配状态,要么是被释放,释放同样分为应用程序显式释放和内存分配器自身隐式执行两种。

在C语言中,malloc只会为其分配空间而不进行初始化;calloc是基于malloc的瘦包装函数,会将分配的内存初始化为0;realloc则是可以改变一个以前已分配块的大小。

使用原因,当我们想要输入一串数据时,如果采用硬编码的形式,数据过长需要重新分配空间,数据过短太浪费空间;更好的方法是在运行前,得知数组大小的值,然后动态的分配这个数组。

注意:动态内存分配是在一个进程中的虚拟内存区域堆中进行的,所以虚拟内存不是一个无限的资源,一个系统中被所有进程分配的虚拟内存的全部数量受磁盘上交换空间的数量所限制的。

分配器的目标

1)最大化吞吐率。指每个单位时间内完成的请求的数量,通常用于衡量系统的性能和效率。请求可以是分配请求或释放请求,分别表示申请或释放内存空间。

2)最大化内存利用率。内存利用率是指内存中被占用的空间与总空间的比例,通常用于衡量内存的使用情况和资源浪费程度。内存利用率越高,表示内存空间被有效利用的程度越高,反之则表示内存空间被浪费的程度越高。

最大化吞吐率和最大化内存利用率是两个不同的目标,它们之间可能存在冲突或者平衡。一般来说,为了最大化吞吐率,需要减少内存分配和释放的开销,提高内存访问的速度,选择合适的分配算法和回收策略等。 而为了最大化内存利用率,需要减少内存碎片,提高内存空间的复用,选择合适的分配大小和对齐方式等。

内存碎片

内存碎片是指内存空间中由于分配和释放操作导致的小块空闲区域,它们无法被有效利用,造成内存资源的浪费。

主要分为两种类型:外部碎片和内部碎片。外部碎片是指分配给进程的内存块之间的空闲区域,它们由于大小或位置不合适,无法满足新的分配请求;内部碎片是指分配给进程的内存块内部的空闲区域,它们由于分配大小超过进程实际需求,或者分配对齐方式导致的多余空间。

它会影响内存利用率和系统性能,因为它们会减少可用的内存空间,增加内存分配和回收的开销,降低内存访问的速度等。但同样可以通过一些方法来减少或避免,比如选择合适的分配算法(如最佳适应、最坏适应、首次适应等),使用紧凑技术(将已分配的内存块移动到一起,消除外部碎片),使用分页或分段机制(将内存空间划分为固定大小或可变大小的单元,减少内部碎片),使用垃圾回收技术(自动检测和回收不再使用的内存空间)等。

注:视频推荐:

1.【【操作系统】内存管理——地址空间】 https://www.bilibili.com/video/BV1oi4y1T7RP/?share_source=copy_web&vd_source=3fb5d6e30320f23cfaa7814e883f9b2f

2.【【操作系统】内存管理——虚拟内存】 https://www.bilibili.com/video/BV18v411a7Vk/?share_source=copy_web&vd_source=3fb5d6e30320f23cfaa7814e883f9b2f

3.【一分钟讲逻辑转换从虚拟内存到物理内存-动画版】 https://www.bilibili.com/video/BV1gV411u7sc/?share_source=copy_web&vd_source=3fb5d6e30320f23cfaa7814e883f9b2f

附录:

参考视频书籍声明:

1.《深入理解计算机系统(原书第三版)》

2.mooc南京大学袁春风老师计算机系统基础