程序开发离不开操作系统的支持。要开发出一款性能高效、功能强劲的程序,加深对操作系统的认知是每个软件开发从业者不可避免的路子。而且,在这个360行行行跨java的时代
,卷王不绝如缕!
随着跨行的小伙伴越来越多,大家对于科班相应的知识储备相对欠缺。除此之外,面试的八股里面。操作系统的内容不绝于耳。由此,抽出业余补充补充自己的卷量是非常有必要的。
干货概要
认识CPU的多级缓存和安全等级?
从操作系统的内存管理入手,认识进程与线程。
1 CPU的多级缓存架构
在了解CPU的缓存架构之前,把握计算机中最为核心的数据处理模块的结构和交互模型是很有必要的哦
现代CPU为了提升执行效率,减少CPU与内存的交互(交互影响CPU效率),一般在CPU上集成了多级缓存架构,常见的为三级缓存结构:
- 一级缓存(L1 Cache):分为数据缓存和指令缓存,逻辑核独占
- 二级缓存(L2 Cache):物理核独占,逻辑核共享
- 三级缓村(L3 Cache):所有物理核共享
三级缓存按照其所在的位置以及作用,各自的空间和存取速度都有区别:
- 在存储空间方面:内存>L3>L2>L1>寄存器;
- 在存取速度方面:寄存器>L1>L2>L3>内存;
*一个特别需要记住的知识点:缓存是由最小的存储区块-缓存行组成,缓存行大小通常为64byte。程序在读取缓存中的数据时均是以缓存行为单位对数据进行读取(类似于JVM中的栈结构中每个线程的操作均是以栈帧为基本单位进行操作的)【例如:L1缓存大小是512kb,而cacheline = 64byte,那么就是L1里有512 * 1024/64个缓存行】
大致理解CPU读取数据的过程(对于后续理解JMM模型等一系列的常考点有实质性帮助)
1、CPU要取寄存器X的值,只需要一步:直接读取。
2、CPU要取L1 cache的某个值,需要1-3步(或者更多):把cache行锁住,把某个数据拿来,解锁,如果没锁住就慢了。
3、CPU要取L2 cache的某个值,先要到L1 cache里取,L1当中不存在,在L2里,L2开始加锁,加锁以后,把L2里的数据复制到L1,再执行读L1的过程,上面的3步,再解锁。
4、CPU取L3 cache的也是一样,只不过先由L3复制到L2,从L2复制到L1,从L1到CPU。
5、CPU取内存则最复杂:通知内存控制器占用总线带宽,通知内存加锁,发起内存读请求,等待回应,回应数据保存到L3(如果没有就到L2),再从L3/2到L1,再从L1到CPU,之后解除总线锁定。
CPU为啥要设置高速缓存?
CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及
CPU。这就造成了高性能能的内存和硬盘价格及其昂贵。然而CPU的高度运算需要高速的数据。为了解决
这个问题,CPU厂商在CPU中内置了少量的高速缓存以解决I\O速度和CPU运算速度之间的不匹配问题。
在CPU访问存储设备时,无论是存取数据抑或存取指令,都趋于聚集在一片连续的区域中,这就被称为局部性原理。
- 时间局部性:如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。比如循环、递归、方法的反复调用等。
- 空间局部性:如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。
- 比如顺序执行的代码、连续创建的两个对象、数组等。
了解CPU运行安全等级,进一步理解JVM的运行模式!
CPU有4个运行级别,分别为:
ring0
ring1
ring2
ring3
Linux与Windows只用到了2个级别:ring0、ring3,操作系统内部内部程序指令通常运行在ring0级别,操作系统以外的第三方程序运行在ring3级别,第三方程序如果要调用操作系统内部函数功能,由于运行安全级别不够,必须切换CPU运行状态,从ring3切换到ring0,然后执行系统函数。由此可以看出JVM创建线程中,线程阻塞唤醒是重型操作,因为CPU要切换运行状态
再详细看看JVM创建线程的过程中运行状态的变化:
1:CPU从ring3切换ring0创建线程
2:创建完毕,CPU从ring0切换回ring3
3:线程执行JVM程序
4: 线程执行完毕,销毁还得切会ring0
2 认识操作系统的内存管理
操作系统有用户空间与内核空间两个概念,目的也是为了做到程序运行安全隔离与稳定,以32位操作系统4G大小的内存空间为例:
其中有最为核心的两个常考点:
用户空间:从
0x00000000 到 0xc0000000(PAGE_OFFSET) 的线性地址可由用户代码 和 内核代码进行引用的范围
内核空间:从0xc0000000(PAGE_OFFSET)到 0xFFFFFFFFF的线性地址只
能由内核代码进行访问的内存范围。
在这个基础上我们可以知道:在 4 GB 的内存空间中,只有 3 GB 可以用于用户应用程序。进程与线程只能运行在用户方式(usermode)或内核方式(kernelmode)下。用户程序运行在用户方式下,而系统调用运行在内核方式下。在这两种方式下所用的堆栈不一样:用户方式下用的是一般的堆栈(用户空间的堆栈),而内核方式下用的是固定大小的堆栈(内核空间的对战,一般为一个内存页的大小),即每个进程与线程其实有两个堆栈,分别运行与用户态与内核态。
由内存的空间划分,对于CPU调度的基本单位–线程来说,也划分为:
内核线程模型:系统内核管理线程(KLT),内核保存线程的状态和上下文信息,线程阻塞不会引起进程阻塞。在多处理器系统上,多线程在多处理器上并行运行。线程的创建、调度和管理由内核完成,效率比ULT要慢,比进程操作快。
用户线程模型:用户程序实现,不依赖操作系统核心,应用提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/内核态切换,速度快。内核对ULT无感知,线程阻塞则进程(包括它的所有线程)阻塞。