字符串从内存写入到磁盘的过程中到底发生了什么(一)
2016-08-10 10:45
579 查看
有个朋友问我说写的挺好,但是不知道怎么实践。我本来说看看Sqlite源码,但想想对于新手直接看源码还不现实的。和几个朋友讨论了下这个问题,发现了一个野鸡网站,里面有一个关于I/O存储的题。看了下题目设计和评测规则,挺靠谱的,新手去写写这个题对I/O优化很应该能有很大提升。这里是链接,大家可以去看看,感觉这题好好做做再去看Sqlite,就没有那么困难了。
相信每个程序员都往磁盘写过数据,然而写磁盘的过程到底发生了什么呢?这次我就来带领大家来一次大冒险。
然而归根结底,当你的应用程序需要使用到操作系统功能时候,无论调用什么API,最后还是需要系统调用。当然,写磁盘这件事是由操作系统来管理的,所以操作系统也提供了一组对应的系统调用。
以文中的例子来说,应用程序调用了API
顺便一提,fopen调用的open系统调用,而fclose调用的是close系统调用。
首先一块物理磁盘可以分多个区,每个区对应一个文件系统。EXT4文件系统中又分了多个group,每个group中有:
super block:记录inode和data block的总量和使用量等信息
inode:存放文件的元信息以及文件内容所在data block位置
data block:存放文件内容,一个文件可能会使用多个data block
那么以本文中例子来说,就是在当前目录创建了一个文件名为
不过,文件系统也只是让一个简单的写入,变成了一系列的磁盘写入,但是不管是inode也好,data block也好,说到底还是存储在硬盘,其本质还是往磁盘写入一些数据。
在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通过调用硬件设备的驱动程序,就完成了整个写入的过程。
所以一个简单的文件写入接口,后面居然还有这么多事情。然而这一切,就在你一眨眼的瞬间就完成了。
相信每个程序员都往磁盘写过数据,然而写磁盘的过程到底发生了什么呢?这次我就来带领大家来一次大冒险。
示例代码
这段代码的作用就是往一个data文件中写入
Hello, World!。我们就以这段C代码和Linux系统(内核版本4.X)为例子来讲解。
#include <stdio.h> 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栈的全景。所以一个简单的文件写入接口,后面居然还有这么多事情。然而这一切,就在你一眨眼的瞬间就完成了。
相关文章推荐
- java 字符串使用+号的时候 到底发生了什么
- 网页打开过程到底发生了什么
- C/C++的编译过程中到底发生了什么?
- 已成功与服务器建立连接,但是在登录过程中发生错取。(provider:共享内存提供程序,error:0-管道的另一端上无任何进程。)(Microsoft SQL Server,错误:233)
- 已成功与服务器建立连接,但是在登录过程中发生错误。 (provider: 共享内存提供程序, error: 0 - 管道的另一端上无任何进程。)
- 已成功与服务器建立连接 但是在登录过程中发生错误。 provider 共享内存提供程序 error 0 管道的另一端上无任何进程。
- $(".class")后到底发生了什么 -- jquery选择器分析
- 受力的物体内部到底发生了什么情况—解释惯性力学三定律
- 已成功与服务器建立连接,但是在登录过程中发生错误。 (provider: 共享内存提供程序, error: 0 - 管道的另一端上无任何进程。)
- 已成功与服务器建立连接,但是在登录过程中发生错误。 (provider: 共享内存提供程序, error: 0 - 管道的另一端上无任何进程。)
- .NET 3.5框架中到底发生了什么巨变?
- c++字符串直接写入内存
- 已成功与服务器建立连接,但是在登录过程中发生错误。 (provider: 共享内存提供程序, error: 0 - 管道的另一端上无任何进程。)
- RAM(随机存储器),ROM(只读存储器),内存还有硬盘到底有什么区别呢?
- 开发视频监控系统纪实 10 视频录制过程中的内存无法写入问题
- 函数调用时,栈里到底发生了什么?
- 网络速度10M指的是位还是字节?--10M带宽到底是多快?和磁盘的1M有什么区别?
- 建立视图和存储过程到底能有什么作用和方便之处
- linux 启动过程临时页表到底映射了多大内存?
- 已成功与服务器建立连接,但是在登录过程中发生错误。 (provider: 共享内存提供程序, error: 0 - 管道的另一端上无任何进程。)