I/O 基础

缓冲区操作

缓冲区以及缓冲区是如何工作,是所有I/O的基础。“输入/输出”就是把数据移进或移出缓冲区。

进程执行I/O操作,就是向操作系统发出请求,让它要么把缓冲区的数据排干(写),要么用数据把缓冲区填满(读)。进程使用这一机制处理所有数据进出操作。

从磁盘读数据到进程内存区:
read-from-disk-into-user-process

进程使用 read( ) 系统调用,要求其缓冲区被填满。内核随即向磁盘控制硬件发出命令,要求其从磁盘读取数据。磁盘控制器把数据直接写入内核内存缓冲区,这一步通过DMA完成,无需主CPU 协助。一旦磁盘控制器把缓冲区装满,内核即把数据从内核空间的临时缓冲区拷贝到进程执行 read( ) 调用时指定的缓冲区。

用户空间与内核空间

  • 用户空间:用户空间是常规进程所在区域,是非特权区域,比如该区域的代码不能直接访问硬件设备。

  • 内核空间:内核空间是操作系统所在区域,有特别的权利:能与设备控制器通讯,控制用户区域进程的运行状态等等。

所有I/O都直接或间接通过内核空间,通过请求页面调度完成。

当进程请求I/O操作的时候,它执行一个系统调用将控制权移交给内核。内核随即采取必要步骤,找到进程所需数据,并把数据传送到用户空间内指定的缓冲区。如果数据已在内核空间,直接拷贝即可;如果不在内核空间,则进程被挂起,内核着手把数据读进内存。

发散、汇聚

根据发散、汇聚的概念,进程只需一个系统调用,就能把一连串缓冲区地址传递给操作系统,然后内核就可以顺序填充或排干多个缓冲区,读的时候把数据发散到多个用户空间缓冲区,写的时候再从多个缓冲区把数据汇聚起来。

scatter-gather

虚拟内存

虚拟内存意为使用虚假(或虚拟)地址取代物理(硬件RAM)内存地址,好处有两大类:

  • 一个以上的虚拟地址可指向同一个物理内存地址。
  • 虚拟内存空间可大于实际可用的硬件内存。

由于设备控制器不能通过DMA直接存储到用户空间,但通过把内核空间地址和用户空间的虚拟地址映射到同一个物理地址,这样,DMA硬件(只能访问物理内存地址)就可以填充对内核与用户空间进程同时可见的缓冲区。

内存空间多重映射:
memory-multi-mapping

省去了内核与用户空间的来往拷贝,前提是,内核与用户缓冲区必须使用相同的页对齐,缓冲区的大小还必须是磁盘控制器块大小的倍数。

操作系统把内存地址空间划分为页,及固定大小的字节组。内存页的大小总是磁盘块大小的倍数,通常是2次幂(可简化寻址操作)。

内存页:
memory-page

内存页面调度

为了支持虚拟内存寻址空间大于物理内存,必须进行虚拟内存分页(一般称为交换,真正的交换是在进程层面完成,非页面层面)。对于暂时不用的内存页放到外部磁盘存储,为物理内存中的其他虚拟页腾出空间。本质上,物理内存充当了分页区的高速缓存;分页区就是从物理内存置换出来,存储在磁盘上的内存页面。

现代CPU包含一个内存管理单元(MMU)的子系统,逻辑上位于 CPU与物理内存之间。该设备包含了虚拟地址向物理内存地址转换时所需映射信息。当CPU引用某内存地址时,MMU负责确定该地址所在页(通常通过对地址进行移位和屏蔽位操作来实现),并将虚拟页号转换为物理页号(由硬件完成,速度极快)。如果当前不存在与该虚拟页形成有效映射的物理内存页,MMU向CPU提交一个页错误。

页错误随即产生一个陷阱(类似于系统调用),把控制权移交给内核,附带导致错误的虚拟地址信息,然后内核采取步骤验证页的有效性。内核会安排页面调入操作,把缺失的页内容读回物理内存。这往往导致别的页被移出物理内存,好给新来的页让地方。在这种情况下,如果待移出的页已经被碰过了(自创建或上次页面调入以来,内容已发生改变),还必须首先执行页面调出,把页内容拷贝到磁盘上的分页区。

如果所要求的地址不是有效的虚拟内存地址(不属于正在执行的进程的任何一个内存段),则该页不能通过验证,段错误随即产生。于是,控制权转交给内核的另一部分,通常导致的结果就是进程被强令关闭。

一旦出错的页通过了验证,MMU 随即更新,建立新的虚拟到物理的映射(如有必要,中断被移出页的映射),用户进程得以继续。造成页错误的用户进程对此不会有丝毫察觉,一切都在不知不觉中进行。

文件I/O

文件系统把一连串大小一致的数据块组织到一起。有些块存储元信息,如空闲块、目录、索引等的映射,有些包含文件数据。单个文件的元信息描述了哪些块包含文件数据、数据在哪里结束、最后一次更新是什么时候,等等。

采用分页技术的操作系统执行I/O的全过程可总结为如下步骤:

  • 确定请求的数据分布在文件系统的哪些页(磁盘扇区组)。磁盘上的文件内容和元数据可能跨越多个文件系统页,而且这些页可能也不连续。
  • 在内核空间分配足够数量的内存页,以容纳得到确定的文件系统页。
  • 在内存页与磁盘上的文件系统页之间建立映射。
  • 为每一个内存页产生页错误。
  • 虚拟内存系统俘获页错误,安排页面调入,从磁盘上读取页内容,使页有效。
  • 一旦页面调入操作完成,文件系统即对原始数据进行解析,取得所需文件内容或属性信息。

文件系统数据也会进行高速缓存,大多数操作系统假设进程会继续读取文件剩余部分,因而会预读额外的文件系统页。

类似的步骤在写文件数据时也会采用。这时,文件内容的改变(通过write( ))将导致文件系统页变脏,随后通过页面调出,与磁盘上的文件内容保持同步。文件的创建方式是,先把文件映射到空闲文件系统页,在随后的写操作中,再将文件系统页刷新到磁盘。

内存映射文件

file-memory-mapping

内存映射 I/O 使用文件系统建立从用户空间直到可用文件系统页的虚拟内存映射。好处有:

  • 用户进程把文件数据当作内存,所以无需发布 read( ) 或write( ) 系统调用。
  • 当用户进程碰触到映射内存空间,页错误会自动产生,从而将文件数据从磁盘读进内存。如果用户修改了映射内存空间,相关页会自动标记为脏,随后刷新到磁盘,文件得到更新。
  • 操作系统的虚拟内存子系统会对页进行智能高速缓存,自动根据系统负载进行内存管理。
  • 数据总是按页对齐的,无需执行缓冲区拷贝。
  • 大型文件使用映射,无需耗费大量内存,即可进行数据拷贝。

如果数据缓冲区是按页对齐的,且大小是内建页大小的倍数,那么,对大多数操作系统而言,其处理效率会大幅提升。

文件锁定

文件锁定机制允许一个进程阻止其他进程存取某文件,或限制其存取方式。通常的用途是控制共享信息的更新方式,或用于事务隔离。在控制多个实体并行访问共同资源方面,文件锁定是必不可少的。

文件锁定可以细致到单个字节。锁定与特定文件相关,开始于文件的某个特定字节地址,包含特定数量的连续字节。这对于协调多个进程互不影响地访问文件不同区域,是至关重要的。

文件锁定有两种方式:共享的和独占的。多个共享锁可同时对同一文件区域发生作用;独占锁则不同,它要求相关区域不能有其他锁定在起作用。

流I/O

流I/O模仿了通道,必须顺序存取。

非块模式:多数操作系统允许把流置于非块模式,这样,进程可以查看流上是否有输入,这使得进程可以在有输入的时候进行处理,输入流闲置时执行其他功能。

就绪性选择:就绪性选择与非块模式类似,但是把查看流是否就绪的任务交给了操作系统。操作系统受命查看一序列流,并提醒进程哪些流已经就绪。凭借操作系统返回的就绪信息,进程就可以使用相同代码和单一线程,实现多活动流的多路传输。

I/O 基础》上有4个想法

  1. hi,码蜂

    讲解I/O很清晰,从底层出发,有几个小问题不是很清楚?
    1、研究I/O的目的体现在哪里,体现在项目中?还是只是为了研究恩。
    2、对于I/O这一块内容,是从本书或者资料上看到?

    谢谢。

    • 1、目前只是研究,做的web项目并不直接应用到。
      2、这东西是看《Java NIO》一书的笔记。

回复 danceLEe 取消回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据