Java IO 之 IO在底层内部如何工作?

这篇博客文章主要讨论与IO相关的事物在较低级别的工作方式。这篇文章供那些想知道如何在机器级别映射Java IO操作的读者使用。以及您的应用程序在运行时硬件在所有时间内的所有功能。我假设您熟悉基本的IO操作,例如读取文件,通过Java I / O API写入文件;因为那超出了这篇文章的范围。

目录

缓冲区处理和内核vs用户空间
虚拟内存
内存分页
面向文件/块的I / O 
文件锁定
面向 Stream 的I / O

缓冲区处理和内核与用户空间

缓冲区以及如何处理缓冲区是所有I / O的基础。术语“输入/输出”仅意味着将数据移入和移出缓冲区。只要时刻牢记这一点即可。通常,进程通过请求操作系统从缓冲区中清空数据(写操作)或向缓冲区中填充数据(读操作)来执行I / O。以上是I / O概念的全部摘要。操作系统内部执行这些传输的机制可能非常复杂,但是从概念上讲,它非常简单,我们将在本文中讨论其中的一小部分。

操作系统级别的数据缓冲

上图显示了块数据如何从外部源(例如硬盘)移动到正在运行的进程(例如RAM)内部的存储区的简化“逻辑”图。首先,该进程要求通过进行read()系统调用来填充其缓冲区。此调用导致内核向磁盘控制器硬件发出命令以从磁盘获取数据。磁盘控制器通过DMA将数据直接写入内核内存缓冲区,而无需主CPU的进一步协助。磁盘控制器完成缓冲区填充后,内核将数据从内核空间中的临时缓冲区复制到进程指定的缓冲区中;当它请求read()操作时。

需要注意的一件事是内核尝试缓存和/或预取数据,因此进程请求的数据可能已经在内核空间中可用。如果是这样,则将过程所请求的数据复制出来。如果数据不可用,则该过程将在内核将数据带入内存时挂起。

虚拟内存

您必须已经多次听说虚拟内存。让我对它进行一些思考。

所有现代操作系统都使用虚拟内存。虚拟内存意味着使用人工或虚拟地址代替物理(硬件RAM)内存地址。虚拟内存具有两个重要优点:

1)多个虚拟地址可以引用相同的物理内存位置。
2)虚拟内存空间可以大于可用的实际硬件内存。

在上一节中,从内核空间复制到最终用户缓冲区必须看起来像是额外的工作。为什么不告诉磁盘控制器将其直接发送到用户空间中的缓冲区?好吧,这是使用虚拟内存完成的,它的优势是上面的1。

通过将内核空间地址映射到与用户空间中虚拟地址相同的物理地址,DMA硬件(只能访问物理内存地址)可以填充一个缓冲区,该缓冲区对于内核和用户空间进程而言都是同时可见的。

虚拟内存

这消除了内核空间和用户空间之间的副本,但是需要内核空间和用户缓冲区共享相同的页面对齐方式。缓冲区还必须是磁盘控制器使用的块大小的倍数(通常为512字节磁盘扇区)。操作系统将其内存地址空间划分为页面,页面是固定大小的字节组。这些内存页始终是磁盘块大小的倍数,通常为2的幂(这简化了寻址)。典型的内存页面大小为1,024、2,048和4,096字节。虚拟和物理内存页面大小始终相同。

内存分页

为了支持虚拟内存的第二个优点(具有比物理内存更大的可寻址空间),有必要进行虚拟内存分页(通常称为交换)。这是一种方案,通过该方案可以将虚拟内存空间的页面持久保存到外部磁盘存储中,从而在物理内存中为其他虚拟页面腾出空间。本质上,物理内存充当页面调度区域的缓存,这是磁盘上的空间,当磁盘空间被迫退出物理内存时,该空间用于存储内存页面的内容。

将内存页面大小调整为磁盘块大小的倍数,可使内核向磁盘控制器硬件发出直接命令,以将内存页面写入磁盘或在需要时重新加载它们。事实证明,所有磁盘I / O都是在页面级别完成的。在现代的分页操作系统中,这是数据在磁盘和物理内存之间移动的唯一方式。

现代CPU包含一个称为内存管理单元(MMU)的子系统。该设备在逻辑上位于CPU和物理内存之间。它包含将虚拟地址转换为物理内存地址所需的映射信息。当CPU引用一个内存位置时,MMU会确定该位置所在的页面(通常是通过移动或屏蔽地址值的位)并将该虚拟页面号转换为物理页面号(这是在硬件中完成的,并且非常复杂)。快速)。

面向文件/块的I / O

文件I / O始终在文件系统的上下文中发生。文件系统与磁盘是完全不同的东西。磁盘将数据存储在扇区中,通常每个扇区512字节。它们是对文件语义一无所知的硬件设备。它们只是提供了许多可以存储数据的插槽。在这方面,磁盘的扇区类似于内存页面;它们都具有统一的大小,并且可以作为一个大数组寻址。

另一方面,文件系统是更高级别的抽象。文件系统是一种整理和解释存储在磁盘(或某些其他随机访问的,面向块的设备)上的数据的特殊方法。您编写的代码几乎总是与文件系统交互,而不是直接与磁盘交互。它是定义文件名,路径,文件,文件属性等的抽象的文件系统。

文件系统(在硬盘中)组织一系列大小一致的数据块。一些块存储元信息,例如空闲块,目录,索引的映射等。其他块包含实际文件数据。有关单个文件的元信息描述了哪些块包含文件数据,数据结束于何处,数据的最后更新时间等。当用户进程发出读取文件数据的请求时,文件系统实现将确定磁盘上的确切位置数据生命。然后,它将采取措施将这些磁盘扇区放入内存。

文件系统还具有页面的概念,其大小可以与基本内存页面相同,也可以是其倍数。典型的文件系统页面大小为2,048至8,192字节,并且始终是基本内存页面大小的倍数。

分页文件系统如何执行I / O归结为以下逻辑步骤:

  1. 确定请求跨越哪个文件系统页面(磁盘扇区组)。磁盘上的文件内容和/或元数据可能分布在多个文件系统页面上,并且这些页面可能是不连续的。
  2. 在内核空间中分配足够的内存页面以容纳已标识的文件系统页面。
  3. 在这些内存页面和磁盘上的文件系统页面之间建立映射。
  4. 为每个内存页面生成页面错误。
  5. 虚拟内存系统会捕获页面错误,并调度pageins通过从磁盘读取其内容来验证这些页面。
  6. 一旦pageins完成,文件系统将分解原始数据以提取请求的文件内容或属性信息。

请注意,此文件系统数据将像其他内存页面一样被缓存。在后续的I / O请求中,部分或全部文件数据可能仍存在于物理内存中,并且可以重新使用而无需从磁盘重新读取。

文件锁定

文件锁定是一种方案,通过该方案,一个进程可以阻止其他进程访问文件或限制其他进程访问该文件的方式。虽然“文件锁定”一词的含义是锁定整个文件(并且通常这样做),但锁定通常可以在更细粒度的级别上进行。文件区域通常是锁定的,粒度低至字节级别。锁与特定文件相关联,该锁从该文件中的特定字节位置开始,并在特定字节范围内运行。这很重要,因为它允许许多进程协调对文件特定区域的访问,而不会妨碍其他进程在文件中的其他位置工作。

文件锁有两种形式:共享独占。多个共享锁可能对同一文件区域同时生效。另一方面,排他锁要求对请求的区域没有其他锁生效。

Stream I / O

并非所有的I / O都是面向块的。还有 Stream I / O,它是在管道上建模的。I / O Stream 的字节必须顺序访问。TTY(控制台)设备,打印机端口和网络连接是 Stream 的常见示例。

Stream 通常(但不一定)比块设备慢,并且通常是间歇输入的来源。大多数操作系统允许将 Stream 置于非阻塞模式,这允许进程检查输入是否在 Stream 上可用,而不会卡住当前是否可用。这种功能允许进程在输入到达时处理输入,但在 InputStream 空闲时执行其他功能。

超越非阻塞模式的一步是进行就绪选择的能力。这类似于非阻塞模式(并且通常在非阻塞模式的基础上构建),但将检查 Stream 是否已准备就绪的检查分担给操作系统。可以告诉操作系统观看 Stream 的集合,并向这些 Stream 中的哪些 Stream 准备就绪的过程返回指示。此功能允许进程利用操作系统返回的就绪信息,使用通用代码和单个线程多路复用许多活动 Stream 。这在网络服务器中被广泛使用以处理大量的网络连接。准备就绪选择对于大批量缩放至关重要。

这就是这个非常复杂的主题,其中包含大量技术词汇words

给我评论您的想法和疑问。

saigon has written 1440 articles

One thought on “Java IO 之 IO在底层内部如何工作?

Leave a Reply