当我读/写文件时(在操作系统级别)发生了啥?

Posted

技术标签:

【中文标题】当我读/写文件时(在操作系统级别)发生了啥?【英文标题】:What's going on (in the OS level) when I'm reading/writing a file?当我读/写文件时(在操作系统级别)发生了什么? 【发布时间】:2011-06-19 15:43:05 【问题描述】:

假设一个程序正在读取文件 F.txt,而另一个程序正在同时写入该文件。

(当我考虑如果我是系统程序员我将如何实现此功能时)我意识到可能存在歧义:

    第一个程序会看到什么?

    第二个程序在哪里写入新字节? (即“就地”写入与写入新文件,然后用新文件替换旧文件)

    有多少程序可以同时写入同一个文件?

    .. 或许有些不那么明显。

所以,我的问题是:

    读写文件功能的主要策略是什么?

    它们在哪些操作系统(Windows、Linux、Mac OS 等)中受支持?

    它可以依赖于某些编程语言吗? (我可以假设 Java 可以尝试在所有支持的操作系统上提供一些统一的行为)

【问题讨论】:

【参考方案1】:

单字节读取有很长的路要走,从磁板/闪存单元到您的本地 Java 变量。这是单个字节经过的路径:

    磁板/闪速电池 内部硬盘缓冲区 SATA/IDE 总线 SATA/IDE 缓冲区 PCI/PCI-X 总线 计算机的数据总线 计算机的 RAM 通过DMA 操作系统Page-cache Libc 读取缓冲区,也就是用户空间fopen() 读取缓冲区 本地 Java 变量

出于性能原因,操作系统完成的大部分文件缓冲都保存在页面缓存中,将最近读取和写入的文件内容存储在 RAM 上。

这意味着 Java 代码的每次读写操作都是在本地缓冲区中完成的:

FileInputStream fis = new FileInputStream("/home/vz0/F.txt");

// This byte comes from the user space buffer.
int oneByte = fis.read();

一个页面通常是一个 4KB 的内存块。每个页面都有一些特殊的标志和属性,其中之一是“脏页”,这意味着该页面有一些未写入物理媒体的修改数据。

一段时间后,当操作系统决定将脏数据刷新回磁盘时,它会将数据发送到与原来相反的方向。

每当两个不同的进程将数据写入同一个文件时,产生的行为是:

不可能,如果文件被锁定。第二个进程将无法打开文件。 未定义,如果写入文件的同一区域。 预期,如果对文件的不同区域进行操作。

“区域”取决于应用程序使用的内部缓冲区大小。例如,在一个 2 MB 的文件上,两个不同的进程可能会写入:

前 1kB 数据中的一个 (0; 1024)。 另一个关于最后 1kB 的数据(2096128;2097152)

仅当本地缓冲区大小为 2 MB 时,才会发生缓冲区重叠和数据损坏。在 Java 上,您可以使用 Channel IO 来读取文件,并对里面发生的事情进行细粒度控制。

许多事务性数据库通过发出sync operation 来强制将本地 RAM 缓冲区中的一些写入写回磁盘。与单个文件相关的所有数据都被刷新回磁板或闪存单元,有效地确保在断电时不会丢失任何数据。

最后,memory mapped file 是一个内存区域,它允许用户进程直接从页面缓存读取和写入页面缓存,绕过用户空间缓冲。

页面缓存系统对于protected mode 多任务操作系统的性能至关重要,每个现代操作系统(Windows NT 及以上、Linux、MacOS、*BSD)都支持所有这些功能。

【讨论】:

【参考方案2】:

策略可以和文件系统一样多。通常,SO 侧重于通过在文件与光盘同步之前缓存文件来避免 I/O 操作。从缓冲区读取将看到以前保存的数据。所以在软硬件之间是一层缓冲(比如mysql MyISAM引擎就用这个层很多)

JVM 在关闭文件或程序调用 fsync() 之类的方法时将文件描述符缓冲区同步到磁盘,但是当缓冲区超过定义的阈值时,也可以通过 SO 同步。在 JVM 中,这当然是在所有支持的操作系统上统一的。

【讨论】:

【参考方案3】:

http://ezinearticles.com/?How-an-Operating-Systems-File-System-Works&id=980216

【讨论】:

以上是关于当我读/写文件时(在操作系统级别)发生了啥?的主要内容,如果未能解决你的问题,请参考以下文章

当我从 .NET 生成一个新线程时到底发生了啥?

无法打开相机意图。 queryIntentActivities 在 api 级别 30 返回一个空列表,但在级别 29 中找到结果。发生了啥变化?

当我创建一个与访问中的其他查询一起使用的查询时,幕后发生了啥?

函数返回时发生了啥

当用户更改小程序窗口大小时,真正发生了啥?

git的“远程处理”期间发生了啥