Neo's Blog

不抽象就无法深入思考
不还原就看不到本来面目!

0%

接口的清晰度(可阅读性)、数据验证和错误处理、业务逻辑代码的清晰度、和可测试性。

https://zhuanlan.zhihu.com/p/77289303

由于比特串每个比特都独立且服从0-1分布,因此从左到右扫描上述某个比特串寻找第一个“1”的过程从统计学角度看是一个伯努利过程,例如,可以等价看作不断投掷一个硬币(每次投掷正反面概率皆为0.5),直到得到一个正面的过程。在一次这样的过程中,投掷一次就得到正面的概率为1/2,投掷两次得到正面的概率是

在处理器内核中一般会有多个执行单元,比如算术逻辑单元、位移单元等。在引入并行指令集之前,CPU在每个时钟周期内只能执行单条指令,也就是说只有一个执行单元在工作,其他执行单元处于空闲状态;

在引入并行指令集之后,CPU在一个时钟周期内可以同时分配多条指令在不同的执行单元中执行。

对于一条从内存中读取数据的指令,CPU的某个执行单元在执行这条指令并等到返回结果之前,按照CPU的执行速度来说它足够处理几百条其他指令,而CPU为了提高执行效率,会根据单元电路的空闲状态和指令能否提前执行的情况进行分析,把那些指令地址顺序靠后的指令提前到读取内存指令之前完成。

实际上,这种优化的本质是通过提前执行其他可执行指令来填补CPU的时间空隙,然后在结束时重新排序运算结果,从而实现指令顺序执行的运行结果。

什么是指令重排序

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MemoryReorderingExample {
private static int x=0,y=0;
private static int a=0,b=0;
public static void main(String[] args) throws InterruptedException {
int i=0;
while(true){
i++;
x=0;y=0;
a=0;b=0;
Thread t1=new Thread(()->{
a=1;
x=b;
});
Thread t2=new Thread(()->{
b=1;
y=a;
});
t1.start();
t2.start();
t1.join();
t2.join();
String result="第"+i+"次("+x+","+y+")";
if(x==0&&y==0){
System.out.println(result);
break;
}
}
}
}

在运行多次后,会出现(0,0)的结果。

以上很惊讶的结果,就是指令重排序导致的结果。

定义

指令重排序是指编译器或CPU为了优化程序的执行性能而对指令进行重新排序的一种手段,重排序会带来可见性问题,所以在多线程开发中必须要关注并规避重排序。

分类

编译器级别的指令重排

第一阶段,编译器重排序,就是在编译过程中,编译器根据上下文分析对指令进行重排序,目的是减少CPU和内存的交互,重排序之后尽可能保证CPU从寄存器或缓存行中读取数据。

在前面分析JIT优化中提到的循环表达式外提(Loop Expression Hoisting)就是编译器层面的重排序,从CPU层面来说,避免了处理器每次都去内存中加载stop,减少了处理器和内存的交互开销。

因为stop在单线程视角下,永远不会变化,所以编译器有理由把stop判断逻辑往前移动。

1
2
3
4
5
while(true){
if (!stop) {
i++;
}
}

从而变成

1
2
3
4
5
if(!stop){
while(true){
i++;
}
}

运行期间的处理器指令重排

也分为两部分

并行指令集重排序

这是处理器优化的一种,处理器可以改变指令的执行顺序。

as-if-serial语义

as-if-serial表示所有的程序指令都可以因为优化而被重排序,但是在优化的过程中必须要保证是在单线程环境下,重排序之后的运行结果和程序代码本身预期的执行结果一致,Java编译器、CPU指令重排序都需要保证在单线程环境下的as-if-serial语义是正确的。

内存系统重排序

这是处理器引入Store Buffer缓冲区延时写入产生的指令执行顺序不一致的问

存储子系统重排序:在处理器严格依照程序顺序执行两个内存访问操作的情况下,在存储子系统的作用下其他处理器对这两个操作的感知顺序仍然可能与程序顺序不一致,即两个操作的执行顺序看起来好像发生了变化,这种现象就是存储子系统重排序。这只是一种现象而不是一种动作,它并没有真正对指令执行顺序进行调整,而只是造成了一种指令的执行顺序好像是被调整过一样的现象,其重排序对象是内存操作的结果。内存重排序也可能导致线程安全问题,比如那个s0和s1写入写缓冲器中,但是顺序发生变化,P1没有读到p2线程写入缓冲器中的,读的还是老的,就出现线程安 全问题了。

CPU 软件虚拟化

CPU 软件虚拟化#
基于软件的 CPU 虚拟化,故名思议,就是通过软件的形式来模拟每一条指令。通过前面的文章我们知道常用的软件虚拟化技术有两种:优先级压缩和二进制代码翻译。这两种是通用技术,可以用在所有虚拟化类型中。我们就结合 intercept 和 virtualize 来看看 CPU 软件虚拟化是怎么做的。

首先,一些必须的硬件知识要知道,X86 体系架构为了让上层的软件(操作系统、应用程序)能够访问硬件,提供了四个 CPU 特权级别,Ring 0 是最高级别,Ring 1 次之,Ring 2 更次之,Ring 3 是最低级别。

一般,操作系统由于要直接访问硬件和内存,因此它的代码需要运行在最高级别 Ring 0 上,而应用程序的代码运行在最低级别 Ring 3 上,如果要访问硬件和内存,比如设备访问,写文件等,就要执行相关的系统调用,CPU 的运行级别发生从 Ring 3 到 Ring 0 的切换,当完成之后,再切换回去,我们熟悉的用户态和内核态切换的本质就来自这里。

虚拟化的实现也是基于这个思想,VMM 本质上是个 Host OS,运行在 Ring 0 上,Guest OS 运行在 Ring 1 上,再往上是相应层次的应用程序运行在 Ring 2 和 Ring 3 上。

image

当 Guest OS 或上层应用在执行相关的特权指令时,就会发生越权访问,触发异常,这个时候 VMM 就截获(intercept)这个指令,然后模拟(virtualize)这个指令,返回给 Guest OS,让其以为自己的特权指令可以正常工作,继续运行。整个过程其实就是优先级压缩和二进制代码翻译的体现。

Read more »

0. 相关概念

VMM - 虚拟机监控系统

VMM 定义

VMM 全称是 Virtual Machine Monitor,虚拟机监控系统,也叫 Hypervisor,是虚拟化层的具体实现。主要是以软件的方式,实现一套和物理主机环境完全一样的虚拟环境,物理主机有的所有资源,包括 CPU、内存、网络 IO、设备 IO等等,它都有。这样的方式相当于 VMM 对物理主机的资源进行划分和隔离,使其可以充分利用资源供上层使用。

虚拟机通常叫做客户机(guest),物理机叫宿主机(host),VMM 处在中间层,既要负责对虚拟资源的管理,包括虚拟环境的调度,虚拟机之间的通信以及虚拟机的管理等,又要负责物理资源的管理,包括处理器、中断、内存、设备等的管理,此外,还要提供一些附加功能,包括定时器、安全机制、电源管理等。

image

Read more »

Memory 软件虚拟化

常规软件内存虚拟化

虚拟机本质上是 Host 机上的一个进程,按理说应该可以使用 Host 机的虚拟地址空间,但由于在虚拟化模式下,虚拟机处于非 Root 模式,无法直接访问 Root 模式下的 Host 机上的内存。

这个时候就需要 VMM 的介入,VMM 需要 intercept (截获)虚拟机的内存访问指令,然后 virtualize(模拟)Host 上的内存,相当于 VMM 在虚拟机的虚拟地址空间和 Host 机的虚拟地址空间中间增加了一层,即虚拟机的物理地址空间,也可以看作是 Qemu 的虚拟地址空间(稍微有点绕,但记住一点,虚拟机是由 Qemu 模拟生成的就比较清楚了)。

所以,内存软件虚拟化的目标就是要将虚拟机的虚拟地址(Guest Virtual Address, GVA)转化为 Host 的物理地址(Host Physical Address, HPA),中间要经过虚拟机的物理地址(Guest Physical Address, GPA)和 Host 虚拟地址(Host Virtual Address)的转化,即:

GVA -> GPA -> HVA -> HPA

其中前两步由虚拟机的系统页表完成,中间两步由 VMM 定义的映射表(由数据结构 kvm_memory_slot 记录)完成,它可以将连续的虚拟机物理地址映射成非连续的 Host 机虚拟地址,后面两步则由 Host 机的系统页表完成。如下图所示。

image

这样做得目的有两个:

提供给虚拟机一个从零开始的连续的物理内存空间。

在各虚拟机之间有效隔离、调度以及共享内存资源。

Read more »

Linux 下网络设备虚拟化的几种形式

为了完成虚拟机在同主机和跨主机之间的通信,需要借助某种“桥梁”来完成用户态到内核态(Guest 到 Host)的数据传输,这种桥梁的角色就是由虚拟的网络设备来完成,上面介绍了一个第三方的开源方案——OVS,它其实是一个融合了各种虚拟网络设备的集大成者,是一个产品级的解决方案。

但 Linux 本身由于虚拟化技术的演进,也集成了一些虚拟网络设备的解决方案,主要有以下几种:

(1)TAP/TUN/VETH
TAP/TUN 是 Linux 内核实现的一对虚拟网络设备,TAP 工作在二层,TUN 工作在三层。Linux 内核通过 TAP/TUN 设备向绑定该设备的用户空间程序发送数据,反之,用户空间程序也可以像操作物理网络设备那样,向 TAP/TUN 设备发送数据。

基于 TAP 驱动,即可实现虚拟机 vNIC 的功能,虚拟机的每个 vNIC 都与一个 TAP 设备相连,vNIC 之于 TAP 就如同 NIC 之于 eth。

当一个 TAP 设备被创建时,在 Linux 设备文件目录下会生成一个对应的字符设备文件,用户程序可以像打开一个普通文件一样对这个文件进行读写。

比如,当对这个 TAP 文件执行 write 操作时,相当于 TAP 设备收到了数据,并请求内核接受它,内核收到数据后将根据网络配置进行后续处理,处理过程类似于普通物理网卡从外界收到数据。当用户程序执行 read 请求时,相当于向内核查询 TAP 设备是否有数据要发送,有的话则发送,从而完成 TAP 设备的数据发送。

TUN 则属于网络中三层的概念,数据收发过程和 TAP 是类似的,只不过它要指定一段 IPv4 地址或 IPv6 地址,并描述其相关的配置信息,其数据处理过程也是类似于普通物理网卡收到三层 IP 报文数据。

VETH 设备总是成对出现,一端连着内核协议栈,另一端连着另一个设备,一个设备收到内核发送的数据后,会发送到另一个设备上去,这种设备通常用于容器中两个 namespace 之间的通信。

Read more »

0. 问题汇总

问题1:M1芯片MAC系统无法安装预期ubuntu系统

安装不同版本的ubuntu系统,出现不通的问题,总体有两种:

  1. BIOS无法识别到CDROM中的ISO镜像
  2. 安装系统时,页面卡死在黑屏状态

复现场景:

宿主机器:M1芯片的MAC系统
虚拟机软件:Parellels Desktop 17
ubuntu版本:ubuntu-20.04-live-server-amd64

问题原因:

计算机硬件与操作系统内核版不匹配,Parellels Desktop虚拟化出来的CPU依旧为M1苹果芯片,而安装的镜像为AMD64,两者不是一个体系,所以出现问题。

解决方案:

安装跟硬件匹配的内核版本:ubuntu-XXX-arm64,例如ubuntu-20.04-live-server-amd64

Read more »