Java NIO系统基础

Posted 林城画序

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java NIO系统基础相关的知识,希望对你有一定的参考价值。

系统硬件

  • CPU - 中央处理单元
  • ALU - 算术逻辑单元
  • PC - 程序计数器
  • USB - 通用串行总线

总线

总线,贯穿整个系统的一组电子管道,在各个部件间传递字节流信息。

传送定长的字节块,称为字;字中的字节数,称为字长。

处理器

CPU,中央处理单元,是解释/执行存储在主存中指令的引擎。

PC,程序计数器,在任何时间点上,都指向主存中某条机器语言指令(内含其地址)。

寄存器文件,存储设备,CPU的核心。它是一个小的存储设备,由一些字长大小的寄存器组成,这些寄存器每个都有唯一的名字。

ALU计算新的数据和地址值。

从系统通电开始,直到系统断电,处理器一直重复指向相同的基本任务:从PC指向的存储器处读取指令,解析指令,执行指令指示的简单操作,然后更新PC指向下一条指令。

  • 加载 - 从主存中拷贝一个字节或一个字到寄存器,覆盖寄存器原来的内容。
  • 存储 - 从寄存器拷贝一个字节或一个字到主存的某个位置,覆盖这个位置上的原来的内容。

 三大逻辑门

与(And)、或(Or)、非(Not)

 时钟控制

组合电路本质上不存储任何信息。它们只是简单地响应输入信号,输出等于输入的某个函数转换。

大多数时候,寄存器都保持在稳定状态(x), 产生的输出等于它的当前状态。信号沿着寄存器前面的组合逻辑传播,产生了一个新的寄存器输入(y), 但只要时钟是低电位,寄存器的输出就保持不变。当时钟变成高电位的时候,输入信号就加载到寄存器,成为下一个状态y, 这个状态就成为寄存器的新输出。寄存器作为电路不同部分中的组合逻辑之间的屏障,只有在每个时钟上升沿时,值才会从寄存器的输入传送到输出。

 

指令阶段

一条指令包含很多操作,可以将这些操作组织为阶段序列。

  • 取指(fetch) :从存储器读入指令,地址为PC的值。
  • 解码(decode) :从寄存器文件读入最多两个操作数,得到valA和/或valB。
  • 执行(execute) :ALU要么执行指令指明的操作,要么增加或减少栈指针。
  • 访存(memory) :将数据写入存储器,或者从存储器中读取数据。
  • 写回(write back) :最多可以写两个结果到寄存器文件。
  • 更新PC(PC update) :将PC设置为下一条指令的地址。

流水线技术

如前所提指令阶段,将指令分解为多个阶段,并让不同指令的各个阶段重叠,从而实现几条指令并行处理,以加速程序运行。

指令的每个阶段都有各自独立的电路来处理,每完成一步,就进到下一步,而前一步则处理后续指令。

 

 

存储器层次模型

上一级是下一级的高速缓存

主存储器

主存是一个临时存储设备,在处理器执行程序时,被用来存放程序,以及程序处理的数据。

可以看作是一个线性字节数组,每个字节都有自己唯一的地址(数组索引),地址从0开始。

从虚拟地址0xC0000000到0xFFFFFFFF,供内核使用,称为内核空间

从虚拟地址0x00000000到0xBFFFFFFF,供各个进程使用,称为用户空间

数据在磁盘和主存之间传递,传递单位是页(内存),一个页是块(磁盘)的倍数,一个块是扇区(磁盘)的倍数,一个扇区通常512字节。

行 - 高速缓存

页 - 主存

块、扇区 - 磁盘

高速缓存

高速缓存(cache)是一个小而快速的存储设备,作为下一层存储设备的缓冲区域。

存储器结构的中心思想是,对于每个k(离CPU越近,k越小), 位于k层的更快更小的存储设备作为位于k+1层的更快更慢的存储设备的缓存。

层次结构中的每一层的缓存来自较低一层的数据对象。

 

缓存命中

当程序需要第k+1层的某个数据对象d时,它首先在当前存储在第k层的一个块中查找d。如果d刚好缓存在第k层中,那么就是缓存命中(cache hit)。

缓存不命中

如果第k层中没有缓存数据对象d, 那么就是缓存不命中。当发生缓存不命中时,第k层的缓存从第k+1层缓存中取出包含d的那个块,如果k层的缓存已经满了的话,可能覆盖现存的一个块。

局部性

局部性有两种形式:时间局部性和空间局部性。

在一个具有良好时间局部性的程序中,被引用过一次的存储器位置很可能在不远的将来再被多次引用。

在一个具有良好空间局部性的程序中,如果一个存储器位置被引用了一次,那么程序很可能在不远的将来引用附近的一个存储器位置。

  • 时间局部性:同一数据对象可能会被多次使用。一旦一个数据对象在第一次不命中时被拷贝到缓存中,我们就会期望后面对该目标有一系列的访问命中。
  • 空间局部性:块通常包含有多个数据对象。我们会期望后面对该块中的其他对象的访问能够补偿不命中后拷贝该块的花费。

缓存行

一个计算机系统,存储器地址有m位,形成M=2m个不同的地址。

高速缓存被组织成一个S=2s个高速缓存组的数组,每个组包含E个高速缓存行。每个行由一个B=2b字节的数据块组成,一个有效位指明这个行包含的数据是否有意义,还有t=m-(b+s)标记位,它们惟一地标识存储在这个高速缓存行中的块。

 

高速缓存是一个关于组的数组。每个组包含一个或多个行。每个行包含一个有效位,一些标记位,以及一个数据块。高速缓存的结构将m个地址划分成了t个标记位,s个组索引位和b个块偏移位。

 

寻址

根据中间的s位组索引确定在哪一组,根据左边的t位标记确定组内的哪一行,判断该行最左边1未有效位是否有效,再根据右边的b位块偏移定位具体的块。

直接映射高速缓存

每个组只有一行(E=1)的高速缓存被称为直接映射高速缓存。

模拟系统

一个系统,它有一个CPU、一个寄存器文件、一个L1高速缓存和一个主存。

当CPU执行一条读存储器字w的指令,它向L1高速缓存请求这个字。如果L1有w的一个缓存的拷贝,那么就得到L1高速缓存命中,很快就抽取出w,并将它返回给CPU。

否则就是高速缓存不命中,当L1向主存请求包含w的块的一个拷贝时,CPU必须等待。当请求的块最终从存储器到达时,L1将这个块放在它的一个高速缓存行里,从被存储的块中抽取出字w,然后将它返回给CPU。

高速缓存确定一个请求是否命中,然后抽取出被请求的字的过程,分为三步:1.组选择   2.行匹配   3字抽取

组选择

行匹配和字抽取

假设有一个直接映射高速缓存:(S, E, B, m) = (4, 1, 2, 4) ,也就是,高速缓存有4个组,每组1行,每块2个字节,4位地址。每个字都是单字节。

内存中的块肯定远多于高速缓存中的块(行),于是,内存中多个块会映射到高速缓存同一个组(依靠标记位区分不同的行)

如下图所示:

 

  1. 一个块中的几个字是相互独立的,它们会被映射到同一行,当改变某个字时,该行会标记为脏,其余字也会无效,伪共享。
  2. 前4个块(0000~0011)全都映射到了第一个行(00),如果一个程序具有良好的空间局部性,那么连续的4个块交替地在高速缓存和主存直接换进换出,抖动。

 

第一个问题很好解决,保证每个字在独立的行上,行填充。

第二个问题的原因是,主存中的块(4位地址)映射到高速缓存时,使用的是高2位作为组索引,如果改为低2两位,则可以避免这个问题。见下图。

相邻的4个块正好映射到4个高速缓存组(行)上(每组一行)。

再看下地址位:

撇开低b位,低b位用于定位块中字节位置,同一块中的字节当然应该映射到同一个缓存行上。

于是定位组的位就在高(t+s)位里选择,根据前面分析的抖动问题,选择中间s位作为组索引比较好。

最后是高t位作为行标记,与组选择和块偏移不同的时,行标记并不能根据索引/偏移一下子定位出来,需要遍历组内所有的行才能找出来。

组相联高速缓存/全相联高速缓存

直接映射高速缓存,有多组,每组1行。好处:速度快,定位到组就是定位到行。坏处:易抖动,因为每组只有1行,有别的块映射到同一行,就不得不把已有的块踢出去。

组相联高速缓存,有多组,每组多行。好处和坏处价于另外两者之间。

全相联高速缓存,只有1组,每组多行。好处:不易抖动,所有的行都在同一组,新来的块会优先选取空行或很久没有使用的块。坏处:速度慢,需要遍历组内所有的行。

 

虚拟内存

虚拟存储器,被组织成为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组,每个字节都有一个唯一的虚拟地址,作为到数组的索引。

将主存看成一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,为每个进程提供了一致的地址空间,保护了每个进程的地址空间不被其他进程破坏。

计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每个字节都有一个惟一的物理地址,从0开始。

根据虚拟寻址,CPU生成一个虚拟地址,通过MMU翻译成物理地址,来访问主存。

 

 

地址空间

{0,1,2,...,N-1}

N = 2n,n位地址空间(32位,64位)

地址空间区分了数据对象(字节)和它们的属性(地址)。

主存中的每个字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址。

 

虚拟存储器(VM)被分割为虚拟页(VP),物理存储器被分割为物理页(PP),大小为P字节,P=2p

虚拟页面的集合分为三个不相交的子集:

  • 未分配的:VM系统还未分配的页。未分配的块没有数据和它们相关联,不占用磁盘空间。
  • 缓存的:当前缓存在物理存储器中的已分配页。
  • 未缓存的:没有缓存在物理存储器中的已分配页。

 

页表

页表记录了虚拟页和物理页的映射关系。虚拟地址空间中的每个页在页表中的一个固定偏移量处都有一个页表条目(PTE)

每个PTE由一个有效位和一个n位地址字段组成。有效位表明了该虚拟地址当前是否被缓存在主存中,地址字段表示主存中物理页的起始位置。

 

页命中

CPU读虚拟存储器的一个字时,地址翻译硬件将虚拟地址作为一个索引,定位到一个PTE,判断有效位,根据PTE中的物理存储器地址构造出这个字的物理地址。

 

缺页

缺页,缓存不命中。触发一个缺页异常。

缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页。

<缺页前>

<缺页后>

地址翻译

地址翻译是一个N元素的虚拟地址空间(VAS)中的元素和一个M元素的物理地址空间(PAS)中元素之间的映射。

TLB

TLB,翻译后备缓冲器,位于MMU中。

TLB,是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。

根据虚拟地址从TLB找PTE,而不是直接访问主存,速度提升好几个数量级。

多级页表

假如系统只用一个单独的页表来进行地址翻译。

如果我们有一个32位的地址空间,4KB的页和一个4字节的PTE,那么我们需要一个232 / 212 = 220 = 1M 个页,需要的位是20,每个页4个字节,需要的位是2,所以共需要22位的页表,也就是需要一个4MB的页表驻留在内存中,太浪费了。

如果使用2级页表,一级页表的每个条目指向一个二级页表,这样每个页表(包括一级和二级)的大小就可以减小到4KB,而且只需要一级页表常驻内存。

进程

进程,一个执行中的程序的实例。

程序,安静地躺在磁盘里;进程,加载到内存里的供CPU执行的那部分程序指令序列。

进程上下文,指的是程序正确运行所需的状态,这些状态包括存放在存储器中的程序的代码和数据、栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。

进程提供给应用程序的两个关键抽象:

  1. 一个独立的逻辑控制流,它提供也给假象,使我们觉得程序独占地使用处理器。
  2. 一个私有的地址空间,它提供一个假象,使我们觉得我们的程序独占地使用存储器系统。

逻辑控制流

进程是轮流使用处理器的,每个进程执行它的流的一部分,然后被其他进程抢占,当前进程挂起,其他进程执行。

任何逻辑流在时间上和别的逻辑流重叠的进程被称为并发进程,这两个进程被称为并发执行。A和B,A和C就并发运行,B和C不是并发运行,因为C的第一条指令是在B的最后一条指令之后执行的。

私有地址空间

 

用户模式和内核模式

处理器提供了一种机制,限制一个应用程序可以执行的指令已经它可以访问的地址空间范围。

处理器使用某个控制寄存器的一个方式位来提供这种功能,该寄存器描述了进程当前享有的权力。当方式位设置了时,进程就运行在内核模式中。一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中任何存储器位置。

方式位没有设置时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令。

一个运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的惟一方法是通过诸如中断、故障、或者陷入系统调用这样的异常。当异常发生时,控制传递到异常处理程序,处理器将模式变为内核模式。

上下文切换

操作系统内核利用一种称为上下文切换的异常控制流来实现多任务。

内核为每个进程维持一个上下文。上下文就是内核重新启动一个被抢占进程所需的状态。包括描绘地址空间的页表,含有当前进程信息的进程表,已经包含进程已经打开文件信息的文件表。

在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决定叫做调度。

  • 保存当前进程的上下文
  • 恢复某个先前被抢占进程所保存的上下文
  • 将控制传递给这个新恢复的进程

内核把进程存放在叫做任务队列的双向循环链表中。内核通过一个唯一的进程标识值或PID来标识每个进程。PID是一个int类型的数。

进程状态

系统中每个进程都必然处于以下5种进程状态中的一种。

  • TASK_RUNNING(运行)                - 进程是可执行的;它或者正在执行,或者在运行队列中等待执行。
  • TASK_INTERRUPTIBLE(可中断) - 进程正在睡眠(也就是说它被阻塞),等待某些条件的达成。一旦条件达成,内核就会把进程状态设置为运行。处于此状态的进程也会因为接收到信号而提前被唤醒并投入运行。
  • TASK_UNINTERRUPTIBLE(不可中断) - 除了不会因为接收到信号而被唤醒从而投入运行,这个状态与可打断状态相同。
  • TASK_ZOMBLE(僵死)    - 该进程已经结束了。但是为了父进程能获知它的消息,该进程的进程描述符仍被保留着。
  • TASK_STOP PED(停止) - 进程停止执行。  

线程

线程本质上也是进程。

线程机制提供了在同一程序内共享内存地址空间运行的一组线程。

对于内核来讲,它就是进程,只是该进程和其他一下进程共享某些资源,比如地址空间。

I/O设备

I/O, 输入/输出设备,是系统与外界的联系通道。

磁盘

磁盘构造

磁盘是由盘片构成的,每个盘片有两面,表面覆盖着磁性记录材料。盘片中间有一个可以旋转的主轴,它使得盘片以固定的旋转速率旋转。磁盘通常包含一个或多个这样的盘片。

每个表面是由一组称为磁道的同心圆组成的,且每个磁道被划分为一组扇区。每个扇区包含相等的数据位(通常512字节)。

磁盘操作

磁盘用连接到一个转动臂的读/写头来读写存储在磁性表面的位。

通过沿着半径轴移动这个转动臂,驱动器可以将读/写头定位在盘面上的任何磁道上,这样的机械运动称为寻道。

磁盘以扇区大小的块来读写数据,对扇区的访问时间有三个主要部分:寻道时间、旋转时间、传送时间。

  • 寻道时间: 为了读取某个目标扇区的内容,转动臂首先将读/写头定位到包含目标扇区的磁道上。寻道时间依赖于转动臂以前的位置和转动臂在盘面上移动的速度。
  • 旋转时间: 一旦读/写头定位到了期望的磁道,驱动器等待目标扇区的第一个旋转到读/写头下。旋转时间依赖于当前读/写头到达目标扇区时盘面的位置和磁盘的旋转速度。
  • 传送时间: 当目标扇区的第一个位位于读/写头下时,驱动器就可以开始读或者写该扇区的内容了。传送时间依赖于旋转速度和每条磁道的扇区数目。

逻辑磁盘块

现代磁盘将它们的构造简化为一个b个扇区大小的逻辑块的序列,编号为0,1,...,b-1

磁盘中有一个小的硬件/固定设备,称为磁盘控制器,维护着逻辑块号和实际(物理)磁盘扇区之间的映射关系。

当操作系统想要执行一个I/O操作时,例如读一个磁盘扇区的数据到主存,操作系统会发送一个命令到磁盘控制器,让它读某个逻辑块号。控制器上的固件执行一个快速表查找,将一个逻辑块号翻译成一个(p盘面、磁道、扇区)的三元组,这个三元组惟一地标识了对应的物理扇区。控制器上的硬件解释这个三元组,将I/O头移动到适当的柱面,等待扇区移动到I/O头下,将I/O头感知到的位放到控制器上的一个小缓冲区中,然后将它们拷贝到主存中。

访问磁盘

CPU使用一种称为存储器映射I/O的技术想I/O设备发射命令。

在使用存储器映射I/O的系统中,地址空间中有一块地址是为与I/O设备通信保留的。每个这样的地址称为一个I/O端口。当一个设备连接到总线时,它与一个或多个端口相关联。

 

行文至此结束。

 

尊重他人的劳动,转载请注明出处:https://www.cnblogs.com/aniao/p/aniao_sys.html

 

以上是关于Java NIO系统基础的主要内容,如果未能解决你的问题,请参考以下文章

Netty基础系列 --彻底理解NIO

java基础知识--NIO详解及实战

Java NIO基础3文件锁

Java网络编程和NIO:Java网络编程基础

Java网络通信编程从基础到框架

Java网络通信编程从基础到框架