【问题标题】:How to force a running program to flush the contents of its I/O buffers to disk with external means?如何强制正在运行的程序通过外部方式将其 I/O 缓冲区的内容刷新到磁盘?
【发布时间】:2016-09-14 14:20:03
【问题描述】:

我有一个长时间运行的 C 程序,它在开始时打开一个文件,在执行过程中写出“有趣”的东西,并在文件完成之前关闭文件。使用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;
}

问题在于,因为这是一个科学计算(想想搜索素数,或者任何你最喜欢的难以破解的东西)它真的可以运行 非常 很久了。由于我确定我没有足够的耐心,我想中止当前的计算,但我想以一种智能的方式通过以某种方式强制程序通过外部手段做到这一点。 em> 清除当前在 OS 缓冲区/磁盘缓存中的所有数据。

这是我尝试过的(对于上面这个伪造的程序,当然不是真正的交易目前仍在运行):

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

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

您的问题是基于一个谬误。 '操作系统中的东西 缓冲区/磁盘缓存'不会丢失。

似乎不适用于这里。经验表明,这些东西确实会丢失。

我使用的是 Ubuntu 16.04,如果可能,并且如果可以安全地以这种方式检索数据,我愿意将调试器附加到此进程。由于我以前从未做过这样的事情,如果有人能给我一个详细的答案,我将不胜感激如何将内容刷新到磁盘中安全可靠。或者我也对其他方法持开放态度。这里没有出错的余地,因为我不会再次重新运行程序。

注意:当然我可以在if 分支打开和关闭一个文件,但是一旦你有许多 东西要写,那效率就非常低了。无法重新编译程序,因为它仍在进行一些计算。

注 2:最初的问题以与 C++ 相关的稍微抽象的方式被问到同一个问题,并被标记为这样(这就是为什么 cmets 中的人建议 std::flush(),即使这是无济于事一个 C++ 问题)。好吧,我想我当时做了一个重大修改。


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

【问题讨论】:

  • 让程序退出而不是杀死它怎么样?
  • 寻找std::flush
  • @BartekBanachewicz 实际上可能有效......所以我应该向程序发送一个不同的SIG... 信号。我会检查的。
  • 杀死程序时是否要强制刷新?
  • 您的问题是基于一个谬误。 “操作系统缓冲区/磁盘缓存中的内容”不会丢失。您需要担心的是仍在应用程序中的东西,例如stdiostreams 缓冲区,fflush()std::flush() 是这两个的答案。

标签: c linux gcc io flush


【解决方案1】:

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

一旦您使用 stdio 库和FILE *,默认情况下,您的程序内部将有一个相当小的(取决于实现,但通常为 KB)缓冲区,用于累积您编写的内容,并在它已满时(或在文件关闭时)将其刷新到操作系统。当你杀死你的进程时,丢失的是这个缓冲区。

如果数据 已刷新到操作系统,则将其保存在 unix 文件缓冲区中,直到操作系统决定将其保存到磁盘(通常很快),或者有人运行 @987654322 @ 命令。如果您关闭计算机的电源,则此缓冲区也会丢失。您可能不关心这种情况,因为您可能不打算拉动电源!但这就是@EJP 所说的(关于 操作系统缓冲区/磁盘缓存中的东西不会丢失):你的问题是 stdio 缓存,不是操作系统。

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

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

这将导致 stdio 缓冲区刷新到操作系统。我想你真正的作家更复杂,在那种情况下,我会尝试让 fflush “经常但不要太频繁”发生。太罕见了,当你终止进程时会丢失数据,太频繁了,如果你写很多,你会失去缓冲的性能优势。

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

【讨论】:

  • 感谢您抽出宝贵的时间来写这篇详细的...尝试的答案。顺便说一句,我的程序仍在运行。我已经决定最好不要惹它。
  • @Matsmath OMG!真的吗?哇!你知道吗,我想我也不会碰它... :-)
  • 我需要在外部触发刷新以进行标准输出文件重定向。该程序以./program &gt; out.txt 的形式启动。是否有可能以某种方式获取该文件的引用并在外部调用fflush(当然不修改程序)?
  • @Mvorisek 对不起,不这么认为。缓冲区只是进程内部的一些内存(它不是操作系统的东西),如果程序没有用“按需刷新”功能(通过信号处理程序等)编写,那么它就不能这样做。当我发现进程日志被过度缓冲时,我的正常技巧是做一些刺激额外日志的事情,它会在缓冲区满时刷新,但这对所有事情都不起作用。调试器,也许吧?
【解决方案2】:

默认情况下,对信号 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 将从外部角度被“忽略”。

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

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

【讨论】:

  • 这是一个有趣的方法,我想我应该实现它。但现在它没有帮助,因为我有一个当前正在运行的程序。
  • 请注意,您的“干净关机代码”必须是异步信号安全的。一般来说,刷新 IO 缓冲区是异步信号安全的。 about halfway down this page 列出了唯一保证是异步信号安全的函数调用。 C++ IO 将介于实现定义和未定义行为之间。
  • 另一种解决方案是附加一个调试器(这将停止程序),然后从中调用std::flush()(例如gdb 可以评估表达式)。然而,同样的,它是不安全的,因为程序可能会在某些瞬态状态下停止。最安全的方法是在任何 I/O 之外的某处设置断点并等待它被命中 - 这样您就知道 I/O 系统处于可用状态。
  • @ivan_pozdeev 我需要在外部触发刷新以进行标准输出文件重定向。该程序以./program &gt; out.txt 的形式启动。是否有可能以某种方式获取该文件的引用并在外部调用fflush(当然不修改程序)?
猜你喜欢
  • 1970-01-01
  • 2019-10-26
  • 2011-12-18
  • 1970-01-01
  • 1970-01-01
  • 2014-09-20
  • 1970-01-01
  • 1970-01-01
  • 2014-01-01
相关资源
最近更新 更多