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


21

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


1
它可能立即执行命令,但是实际打开文件,写入和关闭所需的时间取决于硬盘的速度和类型,任何正在运行的程序等
。– freginold

就给定的例子而言,什么是“过程”?是echo并且>不是单独的(短暂的)过程吗?echo>执行之前,remains的输出在哪里?
oɔɯǝɹ

1
@oɔɯǝɹ >是shell重定向。就像程序已打开要写入的命名文件并用它替换stdout一样,这正是Shell所做的。
Dan D.

7
我认为,不管是否刷新,向您提供file包含内容都是操作系统的责任Hello
Salman A

1
如果程序在计算机A上运行,并且您正在读取计算机B上的文件,并且计算机A的文件系统已通过网络安装,则最终可能会读取空文件,具体取决于网络文件系统类型和安装设置。因此,您可能要禁用该装载的缓存。

Answers:


21

涉及多层缓冲区/高速缓存。

  1. CPU缓存。

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

  2. 进程内缓冲区。

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

  3. 内核缓冲区

    然后,操作系统将保留自己的缓存,以最大程度地减少发送到磁盘的请求数量。该高速缓存尤其不属于任何进程,因此其中的数据可能属于已经完成的进程,并且由于所有访问都通过此处,因此如果下一个程序到达此处,则下一个程序将看到该数据。内核将在有时间或明确要求时将这些数据写入磁盘。

  4. 驱动器缓存

    磁盘驱动器本身也保留了高速缓存以加快访问速度。这些文件的写入速度非常快,并且有一个命令可将剩余数据写入高速缓存中,并在完成时进行报告,操作系统在关机时会使用该命令来确保在断电之前不会遗漏任何数据。

对于您的应用程序来说,将数据注册到内核缓冲区中就足够了(此时实际数据可能仍驻留在CPU高速缓存中,并且可能尚未写入主内存中):“ echo”进程终止,这将终止意味着必须清除所有进程中的缓冲区并将数据移交给OS,并且在启动新进程时,可以保证OS在被询问时将提供相同的数据。


7
考虑CPU缓存似乎与我无关。这在这里是不必要的细节。就像遍历所有细节一样,直到表示硬盘盘或ssd存储器上某个位的某个物理量发生变化以翻转它。
mvw

3
实际上,CPU缓存是相当正交的。
西蒙·里希特

2
更重要的是,CPU缓存在内核之间是一致的,这就是为什么它完全不在画面中的原因。在x86上,它甚至与DMA保持一致(并且x86具有总存储顺序的内存排序模式),因此任何可以读取内存的东西都将以内存操作的全局顺序看到最近存储到该地址的数据。(由于从存储队列中进行存储转发,因此CPU内核甚至可以在全局可见之前看到自己的存储)。在没有高速缓存一致性DMA的非x86平台上,Linux内核确保在DMA到那些地址之前先清除高速缓存。
彼得·科德斯

1
“这些在大多数情况下都是对应用程序程序员隐藏的。” 为什么要“大部分”?我是嵌入式开发人员,除了在引导加载程序(不是“应用程序”)期间,我完全忽略了CPU缓存。我认为任何应用程序开发人员都不会受到CPU缓存影响的影响。
山姆

1
在某些CPU中,可能会利用@Sam缓存未命中/命中以及推测执行来绕过读取访问限制。也许这就是答案所指的?
John Dvorak

22

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

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

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

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


1
“如果应用程序没有任何内部缓存” -这是一个非常大的“如果”:绝大多数的I / O库实现使用默认缓冲标准输出。就是说,例如,C标准要求在退出时刷新stdout缓冲区(但exit至少在没有隐式调用的情况下可能不会)。其他库/语言(例如Java!)提供的保证较少。
康拉德·鲁道夫

如果仅将其限制为重定向原语(即我的问题中的命令)怎么办?它没有内部缓存,对吗?
埃里克(Eric)

@Eric不,你应该没事。
mtak

10
我不确定是否能得到这个答案。问题是“流程何时退出”。每个具有内部写缓存的应用程序都将在进程退出时将它们刷新到磁盘,如果这不是更早发生的话。IOW,这些缓存在这里无关紧要。
MSalters

2
而且,内部缓冲区将在退出时被刷新或从存在中消失,对吗?因此,即使内部缓冲区不刷新,无论等待多长时间,该内容都将不可观察。
WorldSEnder

21

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

一般来说,答案是否定的

这取决于命令。如其他答案所述,如果命令未在内部缓冲数据,则命令终止时所有数据将可用。

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

C保证正常出口将刷新缓冲区。“正常退出”是指exit被调用-明确地或通过从返回main。但是,异常退出可能会绕过此调用(因此会留下未刷新的缓冲区)。

这是一个简单的例子:

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

int main() {
    printf("test");
    raise(SIGABRT);
}

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

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

就是说,您的问题正文提出了一些稍有不同的问题:如果数据完全到达文件,则应在命令终止后立即执行(取决于其他答案中所述的警告)。


7
当命令行工具正在向文件以及stdout或stderr写入数据时,我也看到异常退出,例如调试日志,并且用户已经完成了一个或多个操作,然后键入'q'以减少退出。如果命令行工具不处理SIGPIPE,则磁盘文件并不总是完全刷新。
Zan Lynx

+1,但是“它应该在命令终止立即执行”不是很正确:在进程退出之前会进行任何调用write()pwrite()系统调用,这就是文件更改可见的时候。因此,最后一个文件更改肯定是进程终止之前(最迟在此之前)。我认为,即使有了文件,也没有任何办法可以观察到进程终止是否会在所有文件更改之前发生。mmap(MAP_SHARED)
彼得·科德斯

9

我认为还没有一个问题可以充分解决这个问题:

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

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

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

  • 如果某个进程读取了文件,则如果请求的文件部分当前在缓冲区中,则内核将向该进程显示缓冲区内容;如果不是,则内核将从底层存储介质中获取数据并将其放置在缓冲区中,然后返回上一步。

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


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


2
严格来说,不是Linux的文件系统语义可以保证这一点,而是POSIX语义可以保证这一点。特别是,BSD的行为与macOS甚至Windows完全相同(尽管这是Windows遵循POSIX语义的少数情况之一)。这还假定没有人使用mmap()O_DIRECT和O_DIRECT 做奇怪的事情,这可能导致磁盘和页面缓存之间的事务不同步(但这将解决退出该过程的那一刻)。
奥斯汀·海默加恩

2
@AustinHemmelgarn:严格来说我们都是正确的因为Linux被设计与心中的Unix(系统V)应用程序的支持,后来为支持POSIX也立足于系统V的许多概念
大卫·弗斯特

5

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

fcloseC函数的手册页显示:

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

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

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

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

如有疑问,请编写测试。


2
不论是否使用C语言,所有内容都会/应该使用close()syscall来关闭文件的描述符。
Attie

@Attie:你并不需要close文件退出(在不检查错误哈克程序)之前; 内核将清理它们,close在进程终止后有效地要求您。不过,您确实需要fclose任何缓冲的stdio流,或者让libc使用来完成此操作exit(3),而不是直接使用退出系统调用。
彼得·科德斯

如有疑问,请编写测试。 这对于检测比赛状况是不好的建议。在运行于一种硬件上的一个内核上进行测试可能会告诉您,在该系统上测试产生的软件条件下不可能发生竞争,或者如果这样做很难检测到。但是它不能告诉您在所有文件系统,内核和所有硬件(例如PowerPC)上,这种行为是否应该安全。也就是说,您无法确定您所依赖的保证是实施细节还是有针对性的面向未来的保证!(在这种情况下。)
彼得·科德斯

这取决于实际情况。一些建议使他的shell脚本运行的人可能会受到此建议的帮助。它不适合作为更高级但不太可能的环境的通用解决方案,例如,软件工程师在OS内核上工作,某些人在英特尔的微代码更新上工作,或者某些gal在ISS的某些系统上工作。
mvw

3

当我将命令的输出重定向到文件(例如echo Hello > file)时,是否可以保证命令退出后该文件具有此类数据?

是。Shell打开输出文件,然后echo直接输出到该文件。命令退出后,操作完成。

还是在命令出口和写入文件的数据之间还有一个很小的窗口?

数据是否已经在介质上是另一回事,这仅在此后发生硬件故障时才有意义,或者您绕过已挂载的文件系统,使用一些取证软件检查活动分区。

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

不用担心,内核仅保留文件的一个视图,而与打开频率无关。


“内核只保存文件的一个观点”:不是为真mmap(MAP_SHARED):门店注入mmaped区域不连贯与文件的读操作(由该线程或其他进程)。这就是为什么msync(2)存在。至少这是手册页警告的内容;根据实现的不同,Linux可能实际上会映射页面缓存中的物理页面,在这种情况下,我猜想它基本上是一致的(模内存排序)。无论如何,这仍然是以前发生的一切_exit(2)
彼得·科德斯

2

通常,内核拥有的任何数据都由内核周期维护和清理。此类数据包括通过系统调用(例如)传输到内核内存的数据write(2)

但是,如果你的应用程序(例如C库)执行对缓冲顶部的这一点,那么内核显然不知道,所以不保证其清理。

而且,我不认为清理有任何时间上的保证—通常,清理是在“尽力而为”(阅读:“有时间的时候”)的基础上进行的。


waitpid()如果清理完全发生,那么可以保证任何清理/缓冲区刷新都将在父进程返回之前进行。也就是说,其他进程无法直接观察到该进程在文件完成任何修改之前就终止了。(我说“直接”是为了排除通过NFS文件时间戳进行的间接观察,因为主机之间的NFS缓存并不完全一致。)
Peter Cordes,

@PeterCordes:我想这取决于您所说的“清理”而不是“维护”的含义。对我来说,“维护”是“提供一个一致的视图”(确实有您提到的保证),“清理”是“刷新到磁盘”,我认为它没有时间保证。
Mehrdad

哦,我明白了,您正在回答问题的“刷新到磁盘”部分,该部分与以后在读取文件时看到的内容无关。“清除”是指“清理脏的I / O缓存/缓冲存储器”。是的,没有时序保证,除非您使用fsync/ fdatasync,但是Linux上的缓冲区写回将在/proc/sys/vm/dirty_writeback_centisecs百分之一秒后开始(如果没有被其他I / O流量延迟),并且该procfs目录中的各种其他可调参数也会影响事情(例如,如何较大,以使缓冲区在执行任何回写操作之前先增大)。
彼得·科德斯

2

还是在命令出口和写入文件的数据之间还有一个很小的窗口?

不,没有。

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

您可以在命令退出后立即读取文件的最终内容,而不会再读取空文件。(在C和C ++中,使用waitwaitpidwait3wait4系统调用来等待程序退出,然后才读取文件。如果使用的是Shell,其他编程语言或库(例如C库)调用系统或Java Process类),它可能已经使用了其中一种系统调用。)

正如其他答案和评论所指出的那样,如果程序退出后没有刷新其内部输出缓冲区(例如,由于_exit中止或接收到致命信号,或者由于Java程序正常退出)。但是,此时您无能为力:未经处理的数据将永远丢失,额外的等待将无法恢复。


0

抱歉,可能添加了另一个多余的答案,但大多数似乎都集中在问题标题的红色鲱鱼上。但是据我所知,问题根本不在于缓冲,而在于:

当我将命令的输出重定向到文件(例如,echo Hello> file)时,是否可以保证在命令退出后该文件具有此类数据?

是的,无条件。您描述的“>”以及“ |”的用法 “ <”和“ <”是Unix和Linux界高度依赖的基于管道的处理模型。在每个Linux安装中,完全取决于此行为,您会发现数百个(甚至不是数千个)脚本。

它可以根据您的设计工作,并且即使有极少数的竞争条件,也可能在几十年前就已解决。


不幸的是,这是多余的。只有几个答案主要集中在将数据提交到非易失性存储上的红色圈套。请参阅@pts的答案以及其他几个答案以获得清晰的说明:文件修改是在退出之前进行的,或者根本不进行。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.