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

时间:2016-05-18 08:11:05

标签: c linux gcc io flush

我有一个长期运行的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 <PID>(以及kill -3 <PID>) - 正如@BartekBanachewicz所建议的那样,
  3. 但是在这些方法中的任何一个之后,在程序的最开始创建的文件test.txt仍为空。这意味着,fprintf()的内容在计算过程中留在某个中间缓冲区中,等待某些OS /硬件/软件刷新信号,但由于没有获得这样的信号,内容消失了。这也意味着@EJP的评论

      

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

    似乎不适用于此处。经验表明,这些东西确实迷失了。

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

    注意:当然我可以在<{1}}分支内打开和关闭文件 ,但是当你有许多要写的东西时效率非常低。重新编译程序是不可能的,因为它仍然在进行一些计算。

    注意2:原始问题以与C ++相关的稍微抽象的方式被问到相同的问题,并被标记为这样(这就是为什么评论中的人建议if,即使这是一个C ++问题)。好吧,我想我当时做了一个重大编辑。

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

2 个答案:

答案 0 :(得分:3)

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

一旦你使用 stdio 库和FILE *,默认情况下你的程序中会有一个相当小的(依赖于实现,但通常是一些KB)缓冲区,它正在积累什么你写的,并在它满了时(或关闭文件)将它刷新到操作系统。当您终止进程时,就会丢失此缓冲区。

如果数据已刷新到操作系统,则将其保存在unix文件缓冲区中,直到操作系统决定将其持久保存到磁盘(通常很快),或者有人运行{{ 1}}命令。如果你扼杀了计算机上的电源,那么这个缓冲区也会丢失。你可能不关心这种情况,因为你可能不打算扯下力量!但这就是@EJP所讨论的内容(重新在OS缓冲区/磁盘缓存中的东西'不会丢失):你的问题是 stdio 缓存,不是操作系统。

在一个理想的世界中,你会编写你的应用程序,以便在关键点上进行处理(或sync)。在你的例子中,你会说:

std::flush()

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

在您描述的情况下,程序已在运行且无法停止和重写,那么您唯一的希望就是在调试器中停止它。你需要做的细节取决于std lib的实现,但你通常可以查看 if (i == 0) {//This case is interesting! fprintf(filept, "Hello world\n"); fflush(filept); } 对象内部并开始关注指针,但是它很乱。 @ ivan_pozdeev关于在调试器中执行FILE *fileptstd::flush()的评论很有帮助。

答案 1 :(得分: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,这就是为什么你可能想要处理它以及上面的那个。

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