主要是讲解操作系统的内存的管理技术和虚拟内存技术的实现原理。
内存也可称为主存,不管硬盘多大、里面存放了多少程序和数据,只要程序运行或者数据要进行计算处理,就必须先将它们装入内存。
内存的速度还有逻辑上内存和系统的连接方式和结构
Cache 中存放了内存中的一部分数据,CPU 在访问内存时要先访问 Cache,若 Cache 中有需要的数据就直接从 Cache 中取出,若没有则需要从内存中读取数据,并同时把这块数据放入 Cache 中。但是由于程序的局部性原理,在一段时间内,CPU 总是能从 Cache 中读取到自己想要的数据。Cache 可以集成在 CPU 内部,也可以做成独立的芯片放在总线上,现在 x86 CPU 和 ARM CPU 都是集成在 CPU 内部的。其逻辑结构如下图所示。
Cache 主要由高速的静态储存器、地址转换模块和 Cache 行替换模块组成。
Cache 会把自己的高速静态储存器和内存分成大小相同的行,一行大小通常为 32 字节或者 64 字节。Cache 和内存交换数据的最小单位是一行,为方便管理,在 Cache 内部的高速储存器中,多个行又会形成一组。除了正常的数据空间外,Cache 行中还有一些标志位,如脏位、回写位,访问位等,这些位会被 Cache 的替换模块所使用。
Cache 大致的逻辑工作流程如下
1.CPU 发出的地址由 Cache 的地址转换模块分成 3 段:组号,行号,行内偏移。
2.Cache 会根据组号、行号查找高速静态储存器中对应的行。如果找到即命中,用行内偏移读取并返回数据给 CPU,否则就分配一个新行并访问内存,把内存中对应的数据加载到 Cache 行并返回给 CPU。写入操作则比较直接,分为回写和直通写,回写是写入对应的 Cache 行就结束了,直通写则是在写入 Cache 行的同时写入内存。
3. 如果没有新行了,就要进入行替换逻辑,即找出一个 Cache 行写回内存,腾出空间,替换行有相关的算法,替换算法是为了让替换的代价最小化。例如,找出一个没有修改的 Cache 行,这样就不用把它其中的数据回写到内存中了,还有找出存在时间最久远的那个 Cache 行,因为它大概率不会再访问了。以上这些逻辑都由 Cache 硬件独立实现,软件不用做任何工作软件是透明的。
这是一颗最简单的双核心 CPU,它有三级 Cache,第一级 Cache 是指令和数据分开的,第二级 Cache 是独立于 CPU 核心的,第三级 Cache 是所有 CPU 核心共享的。
下面来看看 Cache 的一致性问题,主要包括这三个方面。
1. 一个 CPU 核心中的指令 Cache 和数据 Cache 的一致性问题。
2. 多个 CPU 核心各自的 2 级 Cache 的一致性问题。
3.CPU 的 3 级 Cache 与设备内存,如 DMA、网卡帧储存,显存之间的一致性问题。
CPU 核心中的指令 Cache 和数据 Cache 的一致性问题,对于程序代码运行而言,指令都是经过指令 Cache,而指令中涉及到的数据则会经过数据 Cache。
所以,对自修改的代码(即修改运行中代码指令数据,变成新的程序)而言,比如我们修改了内存地址 A 这个位置的代码(典型的情况是 Java 运行时编译器),这个时候我们是通过储存的方式去写的地址 A,所以新的指令会进入数据 Cache。但是我们接下来去执行地址 A 处的指令的时候,指令 Cache 里面可能命中的是修改之前的指令。所以,这个时候软件需要把数据 Cache 中的数据写入到内存中,然后让指令 Cache 无效,重新加载内存中的数据。再来看看多个 CPU 核心各自的 2 级 Cache 的一致性问题。从上图中可以发现,两个 CPU 核心共享了一个 3 级 Cache。比如第一个 CPU 核心读取了一个 A 地址处的变量,第二个 CPU 也读取 A 地址处的变量,那么第二个 CPU 核心是不是需要从内存里面经过第 3、2、1 级 Cache 再读一遍,这个显然是没有必要的。在硬件上 Cache 相关的控制单元,可以把第一个 CPU 核心的 A 地址处 Cache 内容直接复制到第二个 CPU 的第 2、1 级 Cache,这样两个 CPU 核心都得到了 A 地址的数据。不过如果这时第一个 CPU 核心改写了 A 地址处的数据,而第二个 CPU 核心的 2 级 Cache 里面还是原来的值,数据显然就不一致了。为了解决这些问题,硬件工程师们开发了多种协议,典型的多核心 Cache 数据同步协议有 MESI 和 MOESI。MOESI 和 MESI 大同小异。
Cache 的 MESI 协议MESI 协议定义了4种基本状态:M、E、S、I,即修改(Modified)、独占(Exclusive)、共享(Shared)和无效(Invalid)。
1.M 修改(Modified):当前 Cache 的内容有效,数据已经被修改而且与内存中的数据不一致,数据只在当前 Cache 里存在。比如说,内存里面 X=5,而 CPU 核心 1 的 Cache 中 X=2,Cache 与内存不一致,CPU 核心 2 中没有 X。
2.E 独占(Exclusive):当前 Cache 中的内容有效,数据与内存中的数据一致,数据只在当前 Cache 里存在;类似 RAM 里面 X=5,同样 CPU 核心 1 的 Cache 中 X=5(Cache 和内存中的数据一致),CPU 核心 2 中没有 X。
3.S 共享(Shared):当前 Cache 中的内容有效,Cache 中的数据与内存中的数据一致,数据在多个 CPU 核心中的 Cache 里面存在。例如在 CPU 核心 1、CPU 核心 2 里面 Cache 中的 X=5,而内存中也是 X=5 保持一致。
无效(Invalid):当前 Cache 无效。前面三幅图 Cache 中没有数据的那些,都属于这个情况。
最后还要说一下 Cache 硬件,它会监控所有 CPU 上 Cache 的操作,根据相应的操作使得 Cache 里的数据行在上面这些状态之间切换。Cache 硬件通过这些状态的变化,就能安全地控制各 Cache 间、各 Cache与内存之间的数据一致性了。
地址空间如果要使多个应用程序同时运行在内存中,必须要解决两个问题:保护和重定位。我们来看是如何解决的︰第一种解决方式是用保护密钥标记内存块,并将执行过程的密钥与提取的每个存储字的密钥进行比较。这种方式只能解决第一种问题(破坏操作系统),但是不能解决多进程在内存中同时运行的问题。
还有一种更好的方式是创造一个存储器抽象︰地址空间,就像进程的概念创建了一种抽象的CPU来运行程序,地址空间也创建了一种抽象内存供程序使用。地址空间是进程可以用来寻址内存的地址集。每个进程都有它自己的地址空间,独立于其他进程的地址空间,但是某些进程会希望可以共享地址空间。
内存交换由于的计算机的实际的物理内存有限,但是的有的程序的是需要大量运行内存来实现的处理的,为了计算机能够运行程序,可以采取的是将程序的必要的数据先加载到内存中,然后在利用内存交换技术,将即将处理的数据添加到内存中,将现在不使用的数据放置在硬盘中。
刚开始的时候,只有进程A在内存中,然后从创建进程B和进程C或者从磁盘中把它们换入内存,然后在图d中,A被换出内存到磁盘中,最后A重新进来。因为图g中的进程A现在到了不同的位置,所以在装载过程中需要被重新定位,或者在交换程序时通过软件来执行﹔或者在程序执行期间通过硬件来重定位。基址寄存器和变址寄存器就适用于这种情况。
内存紧缩(memory compaction):交换在内存创建了多个空闲区(hole),内存会把所有的空闲区尽可能向下移动合并成为一个大的空闲区。但是这项技术通常不会使用,因为这项技术回消耗很多CPU时间。
当进程被创建或者换入内存时应该为它分配多大的内存。如果进程被创建后它的大小是固定的并且不再改变,那么分配策略就比较简单︰操作系统会准确的按其需要的大小进行分配。
Data segment Data segment代码段数据段bss段rodata段:栈(stack)堆(heap)代码段(codesegment/textsegment) :又称文本段,用来存放指令,运行代码的一块内存空间此空间大小在代码运行前就已经确定,内存空间一般属于只读,某些架构的代码也允许可写,在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
数据段(datasegment) :可读可写,存储初始化的全局变量和初始化的static变量,数据段中数据的生存期是随程序持续性(随进程持续性)随进程持续性︰进程创建就存在,进程死亡就消失
bss段(bsssegment) :可读可写,存储未初始化的全局变量和未初始化的static变量bss段中数据的生存期随进程持续性,bss段中的数据—般默认为0。
rodata段:只读数据比如printf 语句中的格式字符串和开关语句的跳转表。也就是常量区。例如,全局作用域中的const int ival = 10,ival存放在 .rodata段;再如,函数局部作用域中的 printf("Hello world %dNn" , c);语句中的格式字符串“Hello world %d\n",也存放在.rodata 段。
栈(stack):可读可写,存储的是函数或代码中的局部变量(非static变量)
栈的生存期随代码块持续性,代码块运行就给你分配空间,代码块结束,就自动回收空间
堆(heap) :可读可写,存储的是程序运行期间动态分配的malloc/realloc的空间,堆的生存期随进程持续性,从malloc/realloc到free一直存在
位图(bitmap):使用位图方法时,内存可能被划分为小到几个字或大到几千字节的分配单元。每个分配单元对应于位图中的一位,0表示空闲, 1表示占用(或者相反)。一块内存区域和其对应的位图如下:
空闲列表(free lists):另一种记录内存使用情况的方法是,维护一个记录已分配内存段和空闲内存段的链表,段会包含进程或者是两个进程的空闲区域。可用上面的图c 来表示内存的使用情况。链表中的每一项都可以代表一个空闲区(H)或者是进程(P)的起始标志,长度和下一个链表项的位置。
大部分使用虚拟内存的系统中都会使用一种分页(paging)技术。在任何一台计算机上,程序会引用使用一组内存地址。当程序执行MOV REG 1000的时候,这条指令时,它会把内存地址为1000的内存单元的内容复制到REG中(或者相反,这取决于计算机)。地址可以通过索引、基址寄存器、段寄存器或其他方式产生。这些程序生成的地址被称为虚拟地址(virtual addresses)并形成虚拟地址空间(virtual address space),在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存中线上,读写操作都使用同样地址的物理内存。在使用虚拟内存时,虚拟地址不会直接发送到内存总线上。相反,会使用MMU(Memory Management Unit)内存管理单元把虚拟地址映射为物理内存地址,像下图这样:
虚拟地址空间由固定大小的单元组成,这种固定大小的单元称为页(pages)。而相对的,物理内存中也有固定大小的物理单元,称为页框(page frames)。页和页框的大小一样。
页表的结构保护位(Protection):告诉我们哪一种访问是允许的,最简单的表示形式是这个域只有一位,О表示可读可写,1表示的是只读。
修改位(Modified)和访问位(Referenced):会跟踪页面的使用情况。当一个页面被写入时,硬件会自动的设置修改位。修改位在页面重新分配页框时很有用。如果一个页面已经被修改过(即它是脏的),则必须把它写回磁盘。如果一个页面没有被修改过(即它是干净的),那么重新分配时这个页框会被直接丢弃,因为磁盘上的副本仍然是有效的。这个位有时也叫做脏位(dirty bit),因为它反映了页面的状态。
访问位(Referenced):在页面被访问时被设置,不管是读还是写。这个值能够帮助操作系统在发生缺页中断时选择要淘汰的页。不再使用的页要比正在使用的页更适合被淘汰。这个位在后面要讨论的页面置换算法中作用很大。
虚拟内存技术 CPU虚拟化技术:CPU虚拟化:在物理机(宿主机)中通过线程或进程这种纯软件方式模拟出假的CPU。通过CPU虚拟化就可以将一个物理CPU发给不同的虚拟机使用,但物理CPU核数要大于虚拟CPU总核数,因为虚拟出来的每颗CPU实际上就是一个线程或者进程。虚拟CPU过多时,需要进行进程/线程切换,比较浪费。CPU虚拟化:总资源=服务器CPU个数*单个CPUx核数kernel*线程(超线程为2,单线程为1)。
全虚拟化技术:为每个VM维护一个影子页表记录虚拟化内存与物理内存的映射关系,VMM将影子页表提交给CPU的内存管理单元MMU进行地址转换,VM的页表无需改动。
半虚拟化技术:采用页表写入法,为每个VM创建一个页表并向虚拟化层注册。VM运行过程中VMM不断管理和维护该页表,确保VM能直接访问到合适的地址。
硬件辅助虚拟化技术:EPT/NPT是内存管理单元MMU的扩展,CPU硬件一个特性,通过硬件方式实现GuestOS物理内存地址到主机物理内存地址的转换,系统开销更低,性能更高。
常用的计算服务架构:openstack Nova: OpenStack是开源的云平台,通过不同的组件提供计算、存储、网络、数据库等多种云服务。其中计算服务由Nova组件提供,通过nova-API与其他组件通信,通过nova-compute对接不同的虚拟层提供计算虚拟化服务。
阿里云ECS 架构:云服务器ECS (Elastic Compute Service)是阿里云提供的基于KVM虚拟化的弹性计算服务,建立在阿里云飞天(Apsara)分布式操作系统上。实现计算资源的即开即用和弹性伸缩。请求的主要调用流程为:OpenAPI、业务层、控制系统、宿主机服务。
腾讯云CVM架构:云服务器CVM (Cloud Virtual Machine)是腾讯提供的基于KVM虚拟化的弹性计算服务,建立在腾讯云分布式资源管理调度系统vStation上。请求的主要调用流程为:API Server、vStation、服务器集群。
内存虚拟化抽象了物理内存,虚拟机每个进程都被赋予一块连续的、超大的虚拟内存空间。
内存复用技术:内存复用指在服务器物理内存一定的情况下,通过综合运用内存复用技术对内存进行分时复用。内存复用技术有:
内存气泡:虚拟化层将较空闲VM内存,分配给内存使用较高的虚拟机。内存的回收和分配由虚拟化层实现,虚拟机上的应用无感知,提高物理内存利用率。
内存交换:将外部存储虚拟成内存给VM使用,将VM上长时间未访问的数据存放到外部存储上,建立映射关系。VM再次访问这些数据是通过映射在与内存上的数据进行交换。
内存共享:VM只对共用的内存(共享数据内容为零的内存页)做只读操作,有写操作时运用,写时复制(VM有写操作时,开辟另一空间,并修改映射)。
全虚拟化:通过软件模拟的形式模拟IO设备,不需要硬件支持,对虚拟机的操作系统也不需要修改(因为模拟的都是一个常见的硬件网卡,如IntelE1000,主流操作系统一般都自带这些驱动,因此默认情下虚拟机不需要再安装驱动。缺点就是性能差了。
半虚拟化:由Hypervisor提供资源调用接口。VM通过特定的调用接口与Hypervisor通信,完成获取完整I/O资源控制操作。(需修改内核及驱动程序,存在移植性和适用性问题,导致其使用受限。)
Pass-through:Hypervisor直接把硬件PCl设备分配给虚拟独占使用,性能当然好啦。但是浪费硬件设备,且配置复杂,首先需要在hypervisor指定通过PClid方式分配给指定的虚拟机,然后虚拟机再识别到设备再安装驱动来使用。
硬件辅助虚拟化:通过硬件的辅助可以让虚拟机直接访问物理设备,而不需要通过VMM。最常用的就是SR-IOV(Single Root l/OVirtualizmion)单根I/O虚拟化标准,该技术可以直接虚拟出128-512网卡,可以让虚拟机都拿到一块独立的网卡,直接使用I/O资源。
I/O环适配功能
正常我们的I/O分为:
非密集I/O:在一秒钟读的次数很少。
密集l/O:在一秒钟内完成了多次读,但每次读取的内容是少量的。
I/O环适配功能主要用来提升大块(44K以上)多队列(32队列深度以上)类型的IO密集型业务的I/O性能(就是将存储设备资源利用率提高).或者是用户可通过开启I/O环适配功能,提升I/O性能。
网络虚拟化虚拟化是对所有IT资源的虚拟化,提高物理硬件的灵活性及利用效率。云计算中的计算和存储资源分别由计算虚拟化和存储虚拟化提供,而网络作为IT的重要资源也有相应的虚拟化技术,网络资源由网络虚拟化提供。
网络是由各种设备组成,有传统的物理网络,还有运行在服务器上看不到的虚拟网络。如何呈现和管理它们将是网络虚拟化的首要目标。
将物理网络虚拟出多个相互隔离的虚拟网络,从而使得不同用户之间使用独立的网络资源,从而提高网络资源利用率,实现弹性的网络。VLAN就是一种网络虚拟化,在原有网络基础上通过VLAN Tag划分出多个广播域。网络虚拟化保障我们创建出来的虚拟机可以正常通信、访问网络。
为什么要采用网络虚拟化呢?
数据中心无法满足部署多台虚拟机,网络架构固定。云计算数据中心满足部署多台虚拟机,网络架构会随虚拟机的迁移改变,满足虚拟机的迁移。
网络虚拟化的目的:节省物理主机的网卡设备资源,并且可以提供应用的虚拟网络所需的L2—L7层网络服务。网络虚拟化软件提供逻辑上的交换机和路由器(L2-L3),逻辑负载均衡器,逻辑防火墙(L4-L7)等,且可以以任何形式进行组装,从而为虚拟机提供一个完整的L2-L7层的虚拟网络拓扑。
物理网络包含的设备
路由器:工作在网络层,连接两个不同的网络。
二层交换机:工作在数据链路层,转发数据。
三层交换机:工作在网络层,结合了部分路由和交换机的功能。
服务器网卡:提供通信服务。
网络虚拟化的特点:
与物理层解耦:接管所有的网络服务、特性和应用的虚拟网络必要的配置,简化这些服务、配置将它们映射给虚拟化层,使用服务的应用只需要和虚拟化网络层打交道。
网络服务抽象化:虚拟网络层可以提供逻辑接口、逻辑交换机和路由器等,并确保这些网络设备和服务的监控、QoS和安全。可以和任意安全策略自由组合成任意拓扑的虚拟网络。
网络按需自动化:通过API自动化部署,一个完整的、功能丰富的虚拟网络可以自由部署在底层物理设施上。通过网络虚拟化,每个应用的虚拟网络和安全拓扑拥有移动性。
多租户网络安全隔离:计算虚拟化使多种业务或不同租户资源共享同一个数据中心资源,但其同时需要为多租户提供安全隔离网络。
虚拟化网卡架构