字符串从内存写入到磁盘的过程中到底发生了什么(一)

3,144 阅读4分钟
原文链接: blog.csdn.net

版权声明:本文为博主原创文章,未经博主允许不得转载。

相信每个程序员都往磁盘写过数据,然而写磁盘的过程到底发生了什么呢?这次我就来带领大家来一次大冒险。

示例代码

这段代码的作用就是往一个data文件中写入Hello, World!。我们就以这段C代码和Linux系统(内核版本4.X)为例子来讲解。

#include 

int main() {
    FILE* f = fopen("data", "w+");
    fputs("Hello, World!", f);
    fclose(f);
}

API到系统调用

编写应用程序一般是调用API(应用编程接口)来完成。虽然API这个词已经被滥用了,但最初API的思想在于封装系统调用(System Call),让系统调用对用户透明。因为系统调用既然叫系统调用,就说明是和操作系统相关的。也就说,使用A系统的系统调用的程序,到了B系统可能就无法编译了。

然而归根结底,当你的应用程序需要使用到操作系统功能时候,无论调用什么API,最后还是需要系统调用。当然,写磁盘这件事是由操作系统来管理的,所以操作系统也提供了一组对应的系统调用。

以文中的例子来说,应用程序调用了API fputs,接着fputs会调用系统调用write来交由操作系统来完成接下来的工作。

顺便一提,fopen调用的open系统调用,而fclose调用的是close系统调用。

系统调用到文件系统

Linux系统为了兼容多种操作系统,把各种文件系统抽象成VFS(Virtual File System, 虚拟文件系统),VFS会提供一组未实现的接口,然后不同的文件系统提供不同的实现。比如我们之前提到的write系统调用,会调用VFS的sys_write接口,然后会调用具体文件系统的对应的write方法。

文件系统

一段字符串到了文件系统就比较复杂了,因为不仅仅要记录字符串本身,还需要记录一些元信息(属于哪个文件,在哪个目录等等)。这里以最流行的EXT4文件系统为例来进行讲解。

首先一块物理磁盘可以分多个区,每个区对应一个文件系统。EXT4文件系统中又分了多个group,每个group中有:

  • super block:记录inode和data block的总量和使用量等信息
  • inode:存放文件的元信息以及文件内容所在data block位置
  • data block:存放文件内容,一个文件可能会使用多个data block

那么以本文中例子来说,就是在当前目录创建了一个文件名为data的文件。对应的磁盘操作有添加一个inode用于记录该data文件,然后把该inode关联到其所在的文件夹中,这样就可以通过该文件夹找到该文件了。接着data文件对应inode中会记录一个data block,在写入完成后,该data block的内容为Hello, World

不过,文件系统也只是让一个简单的写入,变成了一系列的磁盘写入,但是不管是inode也好,data block也好,说到底还是存储在硬盘,其本质还是往磁盘写入一些数据。

Block I/O

文件系统又是调用什么接口写磁盘的呢?在Linux中就是通过Block I/O来完成的。

在Linux中,提供了Block I/O层,用于封装对Block设备操作。Block设备就是能够随机读写一个固定块数据的设备,比如我们熟悉的HDD硬盘,HDD硬盘的一个扇区(Sector)就是其最小读写单位。一般HDD磁盘的最小读写单位(也就是扇区)为512字节。也就是说,在HDD磁盘上,写入10字节和写入512字节都是对应一次Block I/O。

文件系统会把需要待写入的数据转化为Block I/O调用。由于Block设备一次写入和寻址是比耗时的,所以Block I/O并不会立即写入,而是会缓存在队列中,进行一轮合并和重排之后再进行操作,达到优化性能的目的。

最后,Block I/O通过调用硬件设备的驱动程序,就完成了整个写入的过程。

总结

我们简单地讨论一次写入的大体流程,诸多细节留待后续讲解,整个Linux I/O栈的全景可以先从下图先睹为快:

所以当你只是简单调用了一个文件写入接口,后面居然还有这么多事情。然而这一切,就在你一眨眼的瞬间就完成了。