如何使用外部手段强制 运行 程序将其 I/O 缓冲区的内容刷新到磁盘?

How to force a running program to flush the contents of its I/O buffers to disk with external means?

我有一个很长的 运行ning C 程序,它在开始时打开一个文件,在执行期间写出 "interesting" 内容,并在它完成之前关闭文件。使用 gcc -o test test.c(gcc 版本 5.3.1.)编译的代码如下所示:

//contents of test.c
#include<stdio.h>

FILE * filept;

int main() {
    filept = fopen("test.txt","w");
    unsigned long i;
    for (i = 0; i < 1152921504606846976; ++i) {
        if (i == 0) {//This case is interesting!
            fprintf(filept, "Hello world\n");
        }
    }
    fclose(filept);
    return 0;
}

问题是因为这是一个科学计算(想想搜索素数,或者任何你最喜欢的难以破解的东西)它真的可以运行 非常 很长时间。因为我确定我不够耐心,所以我想中止当前的计算,但我想以某种 智能 的方式通过某种方式强制程序 通过外部方式 清除当前在 OS buffer/disk 缓存中的所有数据,无论在哪里。

这是我尝试过的方法(对于上面的这个虚假程序,当然不是真正的交易 目前仍然 运行ning):

  1. 按下 ctrl+C;或
  2. 发送 kill -6 <PID>(以及 kill -3 <PID>)——正如@BartekBanachewicz 所建议的那样,

但是在这些方法中的任何一种之后,在程序最开始创建的文件 test.txt 仍然是空的 。这意味着, fprintf() 的内容在计算过程中留在了某个中间缓冲区中,等待某个 OS/hardware/software 刷新信号,但是由于没有获得这样的信号,内容 消失了。这也意味着,@EJP

发表的评论

Your question is based on a fallacy. 'Stuff that is in the OS buffer/disk cache' won't be lost.

这里好像不适用。经验表明,这些东西确实会丢失。

我正在使用 Ubuntu 16.04,如果可能的话,我愿意为这个进程附加一个调试器,如果以这种方式检索数据是安全的。由于我以前从未做过这样的事情,如果有人能给我一个 详细的 的答案,我将不胜感激 安全可靠地 [=55] =].或者我也对其他方法持开放态度。这里没有错误的余地,因为我不会重新运行程序。

注意:当然我可以打开和关闭 inside if 分支的文件,但是一旦你有 many东西要写。无法重新编译程序,因为它仍在进行一些计算。

注 2:原始问题以与 C++ 相关的稍微更抽象的方式提出了相同的问题,并被标记为这样(这就是为什么人们在评论中建议 std::flush(),这无济于事即使这是一个 C++ 问题)。好吧,我想我当时进行了重大修改。


有些相关:Will data written via write() be flushed to disk if a process is killed?

默认情况下,对信号 SIGTERM 的响应是立即关闭应用程序。但是,您可以添加自己的自定义信号处理程序来覆盖此行为,如下所示:

#include <unistd.h>
#include <signal.h>
#include <atomic>
...
std::atomic_bool shouldStop;

...
void signalHandler(int sig)
{
    //code for clean shutdown goes here: MUST be async-signal safe, such as:
    shouldStop = true;
}
...
int main()
{
    ...
    signal(SIGTERM, signalHandler); //this tells the OS to use your signal handler instead of default
    signal(SIGINT, signalHandler);  //can do it for other signals too
    ...
    //main work logic, which could be of form:
    while(!shouldStop) {
        ...
        if(someTerminatingCondition) break;
        ...
    }
    //cleanup including flushing
    ...
}

请注意,如果采用这种方法,您必须确保您的程序在您的自定义处理程序 运行 之后确实终止(没有义务立即这样做,并且可以 运行 清理它认为合适的逻辑)。如果它不关闭,linux 也不会关闭它,因此从外部角度来看,SIGTERM 将是 'ignored'。

请注意,默认情况下 linux kill 命令会发送一个 SIGTERM,调用上述行为。如果您的程序 运行ning 在前台并且按下 Ctrl-C,则会发送一个 SIGINT,这就是为什么您可能希望按照上述方式处理它。

另请注意,上面建议的实现注意安全,因为除了设置原子标志外,在信号处理程序中不执行任何异步逻辑。正如下面的评论所指出的,这很重要。有关允许和不允许的详细信息,请参阅 this page 的异步信号安全部分。

我可以澄清一下吗?显然几个月过去了,我想你的程序不再是 运行 了......但是这里有一些关于缓冲的混淆仍然不清楚。

只要您使用 stdio 库和 FILE *,默认情况下,您的内部将有一个相当小的缓冲区(取决于实现,但通常是一些 KB)该程序正在累积您编写的内容,并在写满时(或文件关闭时)将其刷新到 OS。当您终止进程时,丢失的就是这个缓冲区。

如果数据 已经 刷新到 OS,那么它将保存在 unix 文件缓冲区中,直到 OS 决定将其保存到磁盘(通常很快),或者有人运行 sync 命令。如果您切断了计算机的电源,那么该缓冲区也会丢失。您可能不关心这种情况,因为您可能不打算切断电源!但这就是@EJP 所说的( OS buffer/disk 缓存中的内容不会丢失):你的问题是 stdio缓存,不是OS.

在理想情况下,您编写的应用会在关键点刷新(或 std::flush())。在您的示例中,您会说:

    if (i == 0) {//This case is interesting!
        fprintf(filept, "Hello world\n");
        fflush(filept);
    }

这会导致 stdio 缓冲区刷新到 OS。我想你真正的作家更复杂,在那种情况下我会尽量让 fflush 发生 "often but not too often"。太少了,当你终止进程时你会丢失数据,太频繁了,如果你写了很多,你就失去了缓冲的性能优势。

在您描述的情况下,程序已经 运行 并且无法停止和重写,那么您唯一的希望,正如您所说,就是在调试器中停止它。你需要做什么的细节取决于标准库的实现,但你通常可以查看 FILE *filept 对象内部并开始跟踪指针,虽然很混乱。 @ivan_pozdeev 关于在调试器中执行 std::flush()fflush() 的评论很有帮助。