一、文件读写的用户程序、操作系统、磁盘交互原理
最近为了彻底搞懂文件读写原理,我特意查询了很多资料,包括Java读写文件的API代码、操作系统处理文件以及磁盘硬件知识等。由于网上现存技术文章,几乎没有找到一篇能够彻底综合讲明白这个原理的文章。心中还是有很多疑问。且有不少文章包括书籍所阐述的随机/顺序读写原理讲述的都是错误或误导性的。所以我综合了一下我能查阅到的所有资料,深入细节知识,给大家彻底讲明白这事。原创文章,转发请保留第一作者著作权。谢谢!
如下图所示。我们编写的用户程序读写文件时必须经过的OS和硬件交互的内存模型。
1、读文件
用户程序通过编程语言提供的读取文件api发起对某个文件读取。此时程序切换到内核态,用户程序处于阻塞状态。由于读取的内容还不在内核缓冲区中,导致触发OS缺页中断异常。然后由OS负责发起对磁盘文件的数据读取。读取到数据后,先存放在OS内核的主存空间,叫PageCache。然后OS再将数据拷贝一份至用户进程空间的主存ByteBuffer中。此时程序由内核态切换至用户态继续运行程序。程序将ByteBuffer中的内容读取到本地变量中,即完成文件数据读取工作。
2、写文件
用户程序通过编程语言提供的写入文件api发起对某个文件写入磁盘。此时程序切换到内核态用户程序处于阻塞状态,由OS负责发起对磁盘文件的数据写入。用户写入数据后,并不是直接写到磁盘的,而是先写到ByteBuffer中,然后再提交到PageCache中。最后由操作系统决定何时写入磁盘。数据写入PageCache中后,此时程序由内核态切换至用户态继续运行。
用户程序将数据写入内核的PageCache缓冲区后,即认为写入成功了。程序由内核态切换回用于态,可以继续后续的工作了。PageCache中的数据最终写入磁盘是由操作系统异步提交至磁盘的。一般是定时或PageCache满了的时候写入。如果用户程序通过调用flush方法强制写入,则操作系统也会服从这个命令。立即将数据写入磁盘然后由内核态切换回用户态继续运行程序。但是这样做会损失性能,但可以确切的知道数据是否已经写入磁盘了。
一、文件读写详细过程
1、读文件
如下所示为一典型Java读取某文件内容的用户编程代码。接下来我们详细解说读取文件过程。
// 一次读多个字节
byte[] tempbytes = new byte[100];
int byteread = 0;
in = new FileInputStream(fileName);//①
ReadFromFile.showAvailableBytes(in);
// 读入多个字节到字节数组中,byteread为一次读入的字节数
while ((byteread = in.read(tempbytes)) != -1) { //②
System.out.write(tempbytes, 0, byteread);
}
首先通过位置①的代码发起一个open的系统调用,程序由用户态切换到内核态。操作系统通过文件全路径名在文件目录中找到目标文件名对应的文件iNode标识ID,然后用这个iNode标识ID在iNode索引文件找到目标文件iNode节点数据并加载到内核空间中。这个iNode节点包含了文件的各种属性(创建时间,大小以及磁盘块空间占用信息等等)。然后再由内核态切换回用户态,这样程序就获得了操作这个文件的文件描述。接下来就可以正式开始读取文件内容了。
然后再通位置②,循环数次获取固定大小的数据。通过发起read系统调用,操作系统通过文件iNode文件属性中的磁盘块空间占用信息得到文件起始位的磁盘物理地址。再从磁盘中将要取得数据拷贝到PageCache内核缓冲区。然后将数据拷贝至用户进程空间。程序由内核态切换回用户态,从而可以读取到数据,并放入上面代码中的临时变量tempbytes中。
整个过程如下图所示。
至于上面说到的操作系统通