当我将命令的输出重定向到文件(例如echo Hello > file
)时,该文件是否保证在命令退出后立即包含此类数据?或者在命令退出和数据写入文件之间是否仍有非常小的窗口?我想在命令退出后立即读取文件,但我不想读取空文件。
答案1
其中涉及多层缓冲区/缓存。
CPU 缓存。
数据按字节逐个组合,并存储在 CPU 缓存中。如果 CPU 缓存已满,并且数据有一段时间没有被访问,则包含我们数据的块可能会被写入主内存。这些对于应用程序程序员来说大部分都是隐藏的。
进程内缓冲区。
在收集数据的进程中会留出一些内存,因此我们需要尽可能少地向操作系统发出请求,因为这相对昂贵。进程将数据复制到这些缓冲区,这些缓冲区可能再次由 CPU 缓存支持,因此无法保证数据被复制到主内存。应用程序需要明确刷新这些缓冲区,例如使用 fclose(3) 或 fsync(3)。exit(3) 函数也会在进程终止之前执行此操作,而 _exit(2) 函数才不是,这就是为什么手册页中有一个大警告,仅当您知道自己在做什么时才调用该函数。
内核缓冲区
然后,操作系统会保留自己的缓存,以尽量减少需要发送到磁盘的请求数量。此缓存不属于任何特定进程,因此其中的数据可能属于已完成的进程,并且由于所有访问都经过此处,因此如果下一个程序到达此处,它将看到数据。内核会在有时间或明确要求时将这些数据写入磁盘。
驱动器缓存
磁盘驱动器本身也保留缓存以加快访问速度。这些写入速度相当快,并且有一个命令用于将剩余数据写入缓存并在完成时报告,操作系统在关机时使用该命令来确保在关机前没有未写入的数据。
对于您的应用程序,将数据注册到内核缓冲区中就足够了(此时实际数据可能仍存在于 CPU 缓存中,可能尚未写入主内存):“echo”进程终止,这意味着任何进程内的缓冲区都必须被刷新,数据必须交给操作系统,然后当您启动一个新进程时,可以保证操作系统在被要求时会返回相同的数据。
答案2
如果应用程序没有任何内部缓存,则更改将立即写入文件。您的示例也是如此。该文件是内存中的逻辑实体,将立即更新。对文件的任何后续操作都将看到程序所做的更改。
然而,这并不意味着更改已写入物理磁盘。更改可能会停留在操作系统文件系统缓存或硬件缓存中。要刷新文件系统缓冲区,请使用该sync
命令。
我想在命令退出后立即读取文件,但我不想读取空文件。
您不应该在这里遇到任何实际问题。
答案3
当进程退出时缓冲区会自动刷新到磁盘吗?
一般来说答案是不。
这取决于命令。正如其他答案所提到的,如果该命令不会在内部缓冲数据,当命令终止时所有数据都将可用。
但大多数(如果不是全部)标准 I/O 库做默认情况下(在某种程度上)缓冲 stdout,并对应用程序关闭时缓冲区的自动刷新提供不同的保证。
C 保证正常退出将刷新缓冲区。“正常退出”意味着exit
调用 — 要么明确地,要么通过从返回main
。但是,异常退出可以绕过此调用(因此留下未刷新的缓冲区)。
这是一个简单的例子:
#include <signal.h>
#include <stdio.h>
int main() {
printf("test");
raise(SIGABRT);
}
如果你编译并执行它,test
将不是必然写入标准输出。
其他编程语言给出的保证就更少了:例如,Java 确实不是程序终止时自动刷新. 如果输出缓冲区包含未终止的行,则可能会丢失,除非System.out.flush()
明确调用。
也就是说,你的问题主体问的是稍微不同的问题:如果数据到达文件根本,它应该在命令终止后立即执行此操作(须遵守其他答案中描述的警告)。
答案4
假设您的命令由使用 C 运行时库的某个程序执行,则在某些时候它应该调用fclose
来关闭打开的文件。
C 函数的手册页fclose
显示:
注意事项请注意,fclose() 仅刷新 C 库提供的用户空间缓冲区。为了确保数据物理存储在磁盘上,内核缓冲区也必须刷新,例如,使用 sync(2) 或 fsync(2)。
的手册页也fflush
有同样的说明。的手册页中close
写道:
成功关闭并不能保证数据已成功保存到磁盘,因为内核会延迟写入。文件系统在流关闭时刷新缓冲区并不常见。如果您需要确保数据已物理存储,请使用 fsync(2)。(此时将取决于磁盘硬件。)
请注意,即使数据未同步到驱动器,其他进程也可以使用这些数据。这对你来说可能已经足够好了。
如果您有疑问,请写一个测试。