内功修炼-操作系统之文件管理和IO基础

文件系统基础

磁盘组织与管理

基础IO

在计算机操作系统中,所谓的I/O就是 输入(Input)输出(Output),也可以理解为读(Read)和写(Write),针对不同的对象,I/O模式可以划分为磁盘IO模型和网络IO模型。

IO操作会涉及到用户空间和内核空间的转换,先来理解以下规则:

  • 内存空间分为用户空间内核空间,也称为用户缓冲区和内核缓冲区;
  • 用户的应用程序不能直接操作内核空间,需要将数据从内核空间拷贝到用户空间才能使用;
  • 无论是read操作,还是write操作,都只能在内核空间里执行;
  • 磁盘IO和网络IO请求加载到内存的数据都是先放在内核空间的;

用户空间&内核空间

简单说,内核空间是操作系统内核代码运行的地方用户空间用户程序代码运行的地方。当应用进程执行系统调用陷入内核代码中执行时就处于内核态,当应用进程在运行用户代码时就处于用户态

// 简单看几行代码,分析下是应用程序在用户空间和内核空间之间的切换过程:
str = "i am qige" // 用户空间
x = x + 2
file.write(str) // 切换到内核空间
y = x + 4 // 切换回用户空间

查看 CPU 时间在 User Space 与 Kernel Space 之间的分配情况,可以使用top命令。在第三行中,%Cpu(s): 5.7us(用户空间), 9.2sy(内核空间), 0.0ni, 96.6id(闲置)

PIO&DMA工作原理

大家都知道一般我们的数据是存储在磁盘上的,应用程序想要读写这些数据肯定就需要加载到内存中。接下来给大家介绍下 PIODMA 这两种 IO 设备和内存之间的数据传输方式。

PIO缺点:每次IO请求都需要CPU多次参与,效率很低。

DMA(直接内存访问,Direct Memory Access)跟PIO模式相比,DMA就是CPU的一个代理,它负责了一部分的拷贝工作,从而减轻了CPU的负担。需要注意的是,DMA承担的工作是从磁盘的缓冲区内核缓冲区网卡设备内核的soket buffer的拷贝工作,以及内核缓冲区磁盘缓冲区内核的 soket buffer网卡设备的拷贝工作,而内核缓冲区到用户缓冲区之间的拷贝工作仍然由CPU负责。

缓冲IO和直接IO

用户空间是不能直接访问内核空间的数据的,如果需要访问怎么办?很简单,就需要将数据从内核空间拷贝的用户空间。

  • 缓冲IO:其实就是磁盘中的数据通过 DMA 先拷贝到内核空间,然后再从内核空间拷贝到用户空间。大多数采用此方法。
  • 直接IO:磁盘中的数据直接通过 DMA 拷贝到用户空间。通常由数据库、消息中间件等需要应用程序自己实现数据缓存的管理。

缓冲IO的优缺点

  • 在一定程度上分离了内核空间和用户空间,保护系统本身的运行安全;
  • 因为内核中有缓冲,可以减少读盘的次数,从而提高性能。
  • 数据在传输过程中需要在应用程序地址空间(用户空间)和内核缓冲(内核空间)之间进行多次数据拷贝操作

直接IO的优缺点

  • 最直观目的是减少一次从内核缓冲区到用户程序缓冲的数据复制
  • 如果访问的数据不在应用程序缓冲中,那么每次数据都会直接从磁盘进行加载,这种直接加载会非常缓慢。通常 直接I/O 跟 异步I/O 结合使用会得到较好的性能。

DMA与零拷贝技术

磁盘可以说是计算机系统中最慢的硬件之一,读写速度比内存慢10倍以上,所以针对优化磁盘的技术非常的多,比如:零拷贝,直接I/O,异步I/O等等。

这里我们就以文件传输为切入点,分析I/O工作方式,以及如何优化文件传输的性能。

DMA技术,全称为Direct Memory Access(又称直接内存访问技术)。

没有DMA技术前的I/O过程

  • 首先在用户进程进行read()系统调用的时候,操作系统会由用户态切换到内核态,然后由CPU向磁盘发起IO请求。
  • 磁盘在接收到IO请求以后,进行数据准备工作,将数据放在磁盘控制器缓冲区里。
  • 在数据准备工作完成以后,磁盘向CPU发出IO中断信号。
  • CPU收到中断信号后,会先将磁盘缓冲区中的文件copy到PageCache中,再将数据从PageCache中copy到用户缓冲区中。在这期间CPU是无法执行其他任务的。copy完成之后read()调用返回,操作系统刚从内核态切换回用户态。

故可以得知:在数据传输的过程,需要CPU亲自的去拷贝数据,并且在这期间CPU无法去做其他事情。简单的搬运几个字符没有问题,但是当处理大量数据的时候,如果每次都让CPU来搬运,显然忙不过来。

有DMA技术之后的I/O过程

  • 当有了DMA控制器以后,在进程向CPU发出read()调用以后,CPU向DMA控制器发起IO请求,然后DMA控制器再向磁盘发出IO请求。
  • 当磁盘接收到IO请求以后,会进行数据准备工作,将数据放到磁盘数据缓冲区当中。
  • 当磁盘的数据准备工作完成以后,不再向CPU发出中断信号,而是通知DMA控制器。
  • DMA控制器在接收到通知以后,将数据从磁盘控制器缓冲区中copy到内核缓冲区中。在这期间并不占用CPU,CPU可以处理其他的事情,执行其他的任务。
  • DMA控制器处理完之后向CPU发出中断信号,由CPU将数据从内核缓冲区copy至用户缓冲区中。copy完成之后read()调用返回,操作系统从内核态切换回用户态。

注意:起初的DMA控制器只在主板上,但是现在IO设备越来越多,数据传输的需求也不尽相同,所以现在每个IO设备中都有DMA控制器。

在进行read调用的时候,操作系统由用户态转为内核态,需要进行DMA拷贝CPU拷贝各一次。(DMA拷贝是指由DMA控制器将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中,CPU拷贝是指将数据从内核缓冲区中copy到用户缓冲区中),在read调用结束以后,操作系统再从内核态切换回用户态。

在进行write调用的时候,操作系统由用户态转为内核态,需要进行CPU拷贝和DMA拷贝各一次。(CPU拷贝是指将用户缓冲区的数据拷贝到socket缓冲区中。而DMA拷贝是指将socket缓冲区中的数据拷贝到网卡中。)

在文件传输场景下,我们无需在用户空间对数据进行再加工。因此用户缓冲区的存在是没有必要的。

如何实现零拷贝

传统 I/O 的工作方式在读写文件时,共发生了四次数据拷贝,两次是DMA拷贝,另外两次则是通过CPU拷贝要想提高文件传输的性能,就需要减少「用户态与内核态的上下文切换」和「内存拷贝」的次数

// 传统 I/O 的工作方式是
read(file, tmp_buf, len);
write(socket, tmp_buf, len);

// mmap + write怎么实现的零拷贝?
mmap() 系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作
buf = mmap(file, len);
write(sockfd, buf, len);

// sendfile怎么实现的零拷贝?
// 在 Linux 内核版本 2.1 中,提供了一个专门发送文件的系统调用函数 sendfile()
// 它可以替代前面的 read() 和 write() 这两个系统调用,这样就可以减少一次系统调用,也就减少了 2 次上下文切换的开销。
// 其次,该系统调用,可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态,这样就只有 2 次上下文切换,和 3 次数据拷贝。
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

// 如果网卡支持SG—DMA,且读写网络数据时,就可以将数据拷贝次数减小为两次。
// 从 Linux 内核 2.4 版本开始起,对于支持网卡支持 SG-DMA 技术的情况下, sendfile() 系统调用的过程发生了点变化
// 可以进一步减少通过 CPU 把内核缓冲区里的数据拷贝到 socket 缓冲区的过程

这就是所谓的零拷贝(Zero-copy)技术,因为我们没有在内存层面去拷贝数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的

零拷贝技术的文件传输方式相比传统文件传输的方式,减少了 2 次上下文切换和数据拷贝次数,只需要 2 次上下文切换和数据拷贝次数,就可以完成文件的传输,而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运

PageCache

零拷贝的内核缓冲区正是使用了PageCache技术。在零拷贝技术中,用PageCache来缓存最近被访问过的数据,当空间不足时淘汰最久未使用的缓存。并且给予局部性原理,进行预读,减少IO次数。

但是在传输大文件的时候,性能损失非常大。因为在传输大文件的时候如果采用PageCache,PageCache容量有限,则PageCache由于长时间被大文件占据,其他热点的小文件可能就无法充分使用到。这样磁盘的读写性能就会降低。并且PageCache中的大文件数据,由于没有享受到缓存带来的好处,但却耗费 DMA 多拷贝到 PageCache 一次,造成资源的浪费。

因此PageCache 被大文件占据,而导致「热点」小文件无法利用到 PageCache,这样在高并发的环境下,会带来严重的性能问题。

大文件传输采用什么实现

核心思想在于:在发起异步IO读以后,不等待数据就位就可以立即返回,然后去处理其他任务。直到收到内核返回的读取成功通知,才去处理数据。这样就解决了高并发场景下零拷贝技术大文件读取的阻塞问题。因此,对于传输大文件,应该使用异步IO+直接IO的方法。而对于小文件,则应该使用零拷贝。

扩展阅读

操作系统之文件管理,万字长文让你彻底弄懂!!!
《操作系统》——文件管理(上)
《操作系统》——文件管理(下)
《操作系统》——输入输出管理
DMA与零拷贝技术

此处评论已关闭