进程退出时缓冲区会自动刷新到磁盘吗?

Eri*_*ric 23 linux hard-drive process file-io

当我将命令的输出重定向到一个文件(例如,echo Hello > file)时,该文件是否能保证在命令退出后立即具有此类数据?或者在命令退出和写入文件的数据之间仍然有一个非常小的窗口?我想在命令退出后立即读取文件,但我不想读取空文件。

mta*_*tak 23

如果应用程序没有任何内部缓存,则更改将立即写入文件。你的例子也是如此。该文件是内存中的一个逻辑实体,将立即更新。对文件的任何后续操作都将看到程序所做的更改。

但是,这并不意味着更改已写入物理磁盘。这些更改可能会留在操作系统文件系统缓存或硬件缓存中。要刷新文件系统缓冲区,请使用该sync命令。

我想在命令退出后立即读取文件,但我不想读取空文件。

您不应该在这里遇到任何实际问题。

  • 我不确定我是否得到了这个答案。问题是关于“进程何时退出”。每个具有内部写缓存的应用程序都会在进程退出时将它们刷新到磁盘,如果这之前没有发生的话。IOW,这些缓存在这里无关紧要。 (10认同)
  • “如果应用程序没有任何内部缓存”——这是一个“非常”大的“如果”:绝大多数 I/O 库实现默认使用缓冲区 stdout。也就是说,例如,C 标准要求在退出时刷新 stdout 缓冲区(但如果至少没有隐式调用“exit”,则可能不会刷新)。其他库/语言(例如 Java!)提供的保证较少。 (2认同)
  • 此外,内部缓冲区将在退出时刷新或从存在中消失,对吗?因此,即使内部缓冲区不刷新,内容也不会被观察到,无论等待多长时间。 (2认同)

Sim*_*ter 22

涉及多层缓冲区/缓存。

  1. CPU 缓存。

    数据是一个字节一个字节地放在一起,存储在 CPU 缓存中。如果 CPU 缓存已满并且有一段时间没有访问数据,则包含我们数据的块可能会被写入主内存。这些在大多数情况下对应用程序员是隐藏的。

  2. 进程内缓冲区。

    在收集数据的过程中会留出一些内存,因此我们需要尽可能少地向操作系统发出请求,因为这相对昂贵。该进程将数据复制到这些缓冲区,这些缓冲区可能再次由 CPU 缓存支持,因此无法保证将数据复制到主内存。应用程序需要显式刷新这些缓冲区,例如使用 fclose(3) 或 fsync(3)。exit(3) 函数也在进程终止之前执行此操作,而 _exit(2) 函数没有执行此操作,这就是为什么手册页中有一个很大的警告,该函数仅在您知道自己是什么时才调用它正在做。

  3. 内核缓冲区

    然后操作系统保留自己的缓存,以尽量减少它需要发送到磁盘的请求数量。这个缓存不特别属于任何进程,所以里面的数据可能属于已经完成的进程,而且由于所有访问都经过这里,如果到达这里,下一个程序就会看到数据。内核将在有时间或明确要求时将此数据写入磁盘。

  4. 驱动器缓存

    磁盘驱动器本身也保留缓存以加快访问速度。这些写入速度相当快,并且有一个命令将剩余数据写入缓存中并在完成时报告,操作系统在关机时使用该命令以确保在断电前没有数据未被写入。

对于您的应用程序,将数据注册在内核缓冲区中就足够了(此时实际数据可能仍然存在于 CPU 缓存中,并且可能尚未写入主内存):“echo”进程终止,这意味着任何进程内缓冲区必须已被刷新并将数据移交给操作系统,然后当您启动一个新进程时,可以保证操作系统会在询问时返回相同的数据。

  • 考虑到 CPU 缓存似乎与我无关。这是这里不必要的详细程度。就像遍历所有细节一样,直到代表硬盘盘片或 ssd 内存上的某个物理量的某个物理量被更改以翻转它。 (8认同)
  • 事实上,CPU 缓存是相当正交的。 (3认同)
  • 更重要的是,CPU 缓存在内核之间是一致的,这就是它完全不在画面中的原因。在 x86 上,它甚至与 DMA 一致(并且 x86 具有总存储顺序内存排序模式),因此任何可以读取内存的东西都将看到最近存储在内存操作全局顺序中该地址的数据。(由于来自存储队列的存储转发,CPU 内核甚至在它们变得全局可见之前就会看到它自己的存储)。在没有缓存一致性 DMA 的非 x86 平台上,Linux 内核确保缓存在 DMA 之前被刷新到这些地址。 (2认同)

Kon*_*lph 21

进程退出时缓冲区会自动刷新到磁盘吗?

一般来说,答案是否定的

这取决于命令。正如其他答案所提到的,如果命令不在内部缓冲数据,则在命令终止时所有数据都将可用。

但大多数(如果不是全部)标准 I/O 库默认情况下(在某种程度上)都会缓冲 stdout,并在应用程序关闭时对自动刷新缓冲区提供不同的保证。

C 保证正常退出将刷新缓冲区。“正常退出”意味着exit被调用——要么是显式的,要么是从main. 然而,异常退出可以绕过这个调用(因此留下未刷新的缓冲区)。

这是一个简单的例子:

#include <signal.h>
#include <stdio.h>

int main() {
    printf("test");
    raise(SIGABRT);
}
Run Code Online (Sandbox Code Playgroud)

如果你编译这个并执行它,test不会一定被写入stdout。

其他编程语言给更少的保证:Java中,例如,是否没有在程序终止时自动刷新。如果输出缓冲区包含未终止的行,则它可能会丢失,除非System.out.flush()明确调用。

这就是说,你的问题问的身体的东西稍有不同:如果文件中的数据到达可言,应该命令终止(须在其他的答案中描述的注意事项)后立即这样做。

  • 当命令行工具写入文件和 stdout 或 stderr(如调试日志)时,我还看到异常退出,并且用户已完成管道到头或更少,然后键入“q”以减少退出。如果命令行工具不处理 SIGPIPE,则磁盘文件并不总是完全刷新。 (7认同)

Dav*_*ter 9

我认为还没有任何问题充分解决这个问题:

我想在命令退出后立即读取文件,但我不想读取空文件。

正如其他答案所解释的那样,行为良好的程序会在进程正常终止之前刷新其内部文件缓冲区。之后,数据在写入持久存储之前可能仍留在内核或硬件缓冲区中。然而,Linux 的文件系统语义保证所有进程以与内核相同的方式查看文件内容,包括内部缓冲区1

这通常是通过每个文件对象最多有一个内核缓冲区来实现的,并且要求所有文件访问都通过这个缓冲区。

  • 如果一个进程读取一个文件,内核将把缓冲区的内容呈现给进程,如果请求的文件部分当前在缓冲区中;如果不是,内核会从底层存储介质中获取数据并将其放入缓冲区,然后返回上一步。

  • 如果进程写入文件,则数据首先放置在该文件的内核缓冲区中。最终缓冲区内容将被刷新到存储。同时,读取访问从同一个缓冲区得到满足(见上文)。


1至少对于常规文件、目录和符号链接。FIFO 和套接字是另一回事,因为它们的内容无论如何都不会持久存储。有一些特殊情况的常规文件,其内容取决于询问者;示例是 procfs 和 sysfs 中的文件(认为/proc/self这是读取符号链接的进程的进程 ID 的符号链接)。

  • 严格来说,保证这一点的不是 Linux 的文件系统语义,而是 POSIX 语义。特别是,BSD 的行为与 macOS 甚至 Windows 完全相同(尽管这是 Windows 遵循 POSIX 语义的少数情况之一)。这也假设没有人使用 `mmap()` 和 O_DIRECT 做奇怪的事情,这可能导致磁盘和页面缓存之间的事情不同步(但这将解决执行该操作的进程退出的那一刻)。 (2认同)
  • @AustinHemmelgarn:严格来说,我们都是对的,因为 Linux 的设计考虑到了对 Unix (System V) 应用程序的支持,后来又支持 POSIX,POSIX 也基于 System V 的许多概念。 (2认同)

mvw*_*mvw 5

假设您的命令是由使用 C 运行时库的某个程序执行的,在某些时候它应该调用fclose以关闭打开的文件。

fcloseC 函数的手册页说:

注意 请注意, fclose() 仅刷新 C 库提供的用户空间缓冲区。为了确保数据物理存储在磁盘上,内核缓冲区也必须刷新,例如,使用 sync(2) 或 fsync(2)。

并且手册页fflush具有相同的注释。手册页close说:

成功关闭并不能保证数据已成功保存到磁盘,因为内核会延迟写入。当流关闭时,文件系统刷新缓冲区并不常见。如果您需要确保数据是物理存储的,请使用 fsync(2)。(此时将取决于磁盘硬件。)

请注意,即使数据未同步到驱动器,其他进程也可以使用该数据。也许这对你来说已经足够了。

如果您有疑问,请编写一个测试。

  • C 与否,一切都将/应该使用 `close()` 系统调用来关闭文件的描述符。 (2认同)