随着硬件的发展,过去的OS并不能很好的适应新硬件的速度,必须修改内核,以充分发挥硬件的性能。
基础:
挑战:
基础:
挑战:
基础:
挑战:
基础:
挑战:
基础:
挑战:
基础:
挑战:
随着CPU技术的高速发展,目前已经有在单台机器上,运行1k+多的cores了。
然而对于现有的操作系统,例如Linux/FreeBSD等,在处理多核时,并不如人意。并且随着核数的增加,性能出现不升反降的现象。
在现有的内核中,对共享资源会使用锁操作,来完成并发。这在核数比较少的情况下,可以很好的工作,但是当核数多起来,会导致CPU大部分时间都在抢占锁的session中,导致性能不升反降。
VM的种类有,系统级别的VM,也有进程级别的VM,而它们又分VM的指令是否与host指令一致。
虚拟VM可以在不同层面作出虚拟,例如进程级别的VM是通过DLL来抽象出来的;
kvm是通过OS层面抽象出来的;
还可以直接通过抽象hardware来直接提供虚拟化技术
下图为系统调用的过程
OS运行原理
VMM工作原理
问题:
研究发现linux一般的体积有1G。vm在启动一个1G的Linux的时候,非常慢。使用lightVm(8M)一个体积非常小的os,可以大大加速vmm的处理时间。
可以看出,VM的性能下降,更多是因为系统调用发生的双倍上下文切换引起的。那么如果通过设计vmm把硬件的特权指令直接交给进程去执行,就可以减少系统调用,进而实现优化的目的
随着新应用和新场景的出现,会导致某些api变得不常用。新的需求,又会引发需要增加api接口来实现新功能的支持。
对多核的支持,现存的linux api支持的并不好。
例如fd总是返回最小的一个,会导致在分配fd时必须加自旋锁,来保证分配fd的唯一性
由于C的这些特点,使得Linux内核的bug数量一直维持在高水平
当然C可以作为内核语言,它的优势是足够灵活和高效,几乎可以使用所有场景
作为高级语言,它可以有效的解决C带来的缺陷,但同时,为了解决这些缺陷,也带来了性能的开销。
它的垃圾回收机制,也同时引起了性能的问题。
但是bug的数量会大大减少。
因此在考虑linux内核的语言时,看性能和安全的取舍。
它同时具备高性能和高安全的特点。但是学习曲线比较高
在现有几乎所有的linux都是使用C来编写,即使高级语言有很多好处,但是替换C不是一朝一夕的事情,很多问题有待实现和观察。
由于单核CPU受限于功耗和设计复杂度等因素,导致很难再在单核上优化CPU,因此多核成为了加快CPU的一种途径。
对于单个CPU,内部会有多个核组成。每个核拥有自己的L1 cache。而对于L2 cache,可以是共享和单独占有两种类型。对于共享cache的好处是数据一致性处理更简单,但是会有空间冲突的不好;对于独占cache的好处是不会有冲突,但是在保证一致性方面需要进一步的处理。
NUMA是由多个CPU组成的,而每个CPU拥有多个核,每个CPU有自己的cache,不同的CPU拥有不同的时钟周期。
对于每个cache,是全局可见的,由于时钟周期不一样,这导致在跨CPU访问cache会有更大的开销
在实践测试的过程中发现,在核数增长到一定程度,机器的性能会出现不升反降的现象。原因是OS内部,对共享资源有大量的同步互斥原语的控制,这直接造成了OS无法做到水平扩展。
每个CPU拥有自己的cache,这导致在多线程处理同一个变量的时候,这个变量同时位于不同的CPU的cache中,如果此时一个线程改变了这个共享变量的值,那么此时就必须通知另外一个cache,来更新这个值,来保证一致性。
共用cache:可以解决数据同步的问题。但是这导致一个时钟周期只有一个CPU可以操作cache,这会带来严重的性能问题。
加入同步方案
I->S:CPU请求读,数据来源内存或者其他cache,占用总线
S->M:CPU请求写,发送数据失效Invalid到其他cache,令其他cache S->I,占用总线
M->S:CPU写完成
S->I:其他CPU请求写
从MSI可以看到,如果一个cache从I->M(失效->修改)的转变需要占用总线两次,这在总线占用的开销来说比较昂贵。
MESI在MSI的基础上增加了E(独占)状态
I->E:数据总线发送invalid,占用总线
E->M:修改数据,不发送任何消息
来实现I->M只占用总线一次来加快效率
由于CPU的速度远远高于cache,在CPU触发多次写的时候,cache可能来不及接收修改的内容
这是就引入了store buffer来解决这个问题。
但同时Store buffer也带来了不一致性,需要增加在同步数据的时候增加读Store buffer的步骤来解决数据一致性的问题。
由于当CPU需要修改cache,会发送Invalid消息到其他CPU,按照MSI协议,CPU需要等待其他CPU返回ACK才开始操作cache。但由于考虑到执行效率的情况,CPU并不会等待ACK,而是马上执行修改cache的操作。
这就导致CPU的执行有可能出现数据不一致的问题。
为了解决Invalid queue引发的不一致性问题,CPU引入barrier的操作,对于特殊变量,可以通过barrier执行清空queue和store buffer,来严格保证数据一致性。
但同时barrier会带来性能下降的问题。
因此只有在特殊情况下才使用barrier,来保证特殊变量的一致性
其他情况下CPU允许不一致和局部乱序的执行代码。
对于上述代码,即使在单核处理器上,assert也有可能是false。
这是由于在编译优化的过程中,编译器会判断程序的两条指令是否有数值关联,如果没有,就会根据运行效率,来调整两条语句的顺序,来达到优化的目的。
通过加入fence操作,解决了上述问题
对于两个线程的共享变量,如果没有加fence操作,有可能总线并不足够在一次传送可以送完整个变量的值,从而导致数据错乱的情况。
通过加入fence操作,保证了变量的一致性
通过加入原子操作,使得两个线程可以按顺序的执行
普通的spin lock,由于在多个CPU共享同一个atomit变量,造成,在fence同步时,开销非常大,MCS lock通过把atomit变量分散到不同cache里面,来解决这个问题。
每个lock维持自己的一个自旋变量,当上一个自旋锁释放时,会更新链表的下一个元素的自旋变量,来把下一个锁打开。
OS内存在大量链表操作,在修改链表时,如果每次都把整个链表锁了,会引发非常大的开销。
通过RCU可以有效的解决这个问题。
原文:https://www.cnblogs.com/kukafeiso/p/14163801.html