perl 中的无缓冲 IO
Posted
技术标签:
【中文标题】perl 中的无缓冲 IO【英文标题】:Unbuffered IO in perl 【发布时间】:2011-07-11 16:20:34 【问题描述】:我有一个 Perl 应用程序,它使用 open 和 print 调用将日志写入文件。
open (FH, "d:\\temp.txt");
print FH "Some log";
close (FH);
但是,在机器突然关闭期间,日志不会保存到文件中。因此,在搜索了几个地方之后,建议使用两个选项来进行无缓冲 IO(即将文本写入磁盘,而不是将其保存在缓存中然后刷新它):
-
sysopen, syswrite
$| = 1;
我已经尝试了这两个选项,但它不起作用。我在异常关机前几秒钟所做的任何写入都会丢失。
有什么方法可以几乎在 Perl 中确定性地完成无缓冲 IO?我正在使用 Perl 5.8.3 运行 Windows 7 64 位。
编辑:我搜索了如何让 Windows 执行无缓冲 IO,这就是它可以做到的方式! 打电话
-
CreateFile 带有 FILE_FLAG_NO_BUFFERING 用于 dwFlagsAndAttributes 参数。但是,这需要考虑 memory alignment issues(即文件访问缓冲区应该是扇区对齐的;应用程序通过调用 GetDiskFreeSpace 来确定扇区大小)
使用WriteFile 将数据写入文件。此写入将是无缓冲的,而不是进入缓存,而是直接进入磁盘。
最后,调用FlushFileBuffers 刷新与文件关联的元数据。
有人可以为这 3 个调用提供来自 Perl 的 Win32 API 的帮助吗?
【问题讨论】:
尝试在打印中添加\n
并使用$| = 1
。
$|
只对当前选定的文件句柄(默认为 STDOUT)设置自动刷新,而不是所有文件句柄
\n
对未连接到终端的句柄没有帮助。具有自动刷新 ($|=1
) 的句柄不需要 \n
。
我迫不及待地等待有关我最新答案的反馈!
@ikegami,抱歉回复晚了!感谢您的解决方案,我已接受!它只是工作!!!
【参考方案1】:
use IO::Handle;
open(FH, "d:\\temp.txt");
FH->autoflush(1);
print FH "Some log";
close(FH);
这将尽快将其发送到操作系统,但操作系统可能需要一段时间才能将其提交到磁盘。不过我相信你会发现这会满足你的需求。
如果您使用的是 unix,我会向您推荐 sync 以获取有关让操作系统将数据提交到磁盘的更多信息。
【讨论】:
我也尝试了autoflush,仍然有90%的时间,它没有将数据持久化到硬盘。【参考方案2】:这个怎么样?
use strict;
use warnings;
use IO::Handle qw( ); # For autoflush.
use Symbol qw( gensym );
use Win32API::File qw( CloseHandle CreateFile GetOsFHandle OsFHandleOpen GENERIC_WRITE OPEN_ALWAYS FILE_FLAG_WRITE_THROUGH );
use Win32::API qw( );
use constant WIN32API_FILE_NULL => [];
sub open_log_handle
my ($qfn) = @_;
my $handle;
if (!($handle = CreateFile(
$qfn,
GENERIC_WRITE,
0, # Exclusive lock.
WIN32API_FILE_NULL, # No security descriptor.
OPEN_ALWAYS, # Create if doesn't exist.
FILE_FLAG_WRITE_THROUGH, # Flush writes immediately.
WIN32API_FILE_NULL, # No prototype.
)))
return undef;
my $fh = gensym();
if (!OsFHandleOpen($fh, $handle, 'wa'))
my $e = $^E;
CloseHandle($handle);
$^E = $e;
return undef;
$fh->autoflush(1);
return $fh;
sub close_log_handle
my ($fh) = @_;
my $handle = GetOsFHandle($fh)
or return undef;
if (!FlushFileBuffers($handle))
my $e = $^E;
close($fh);
$^E = $e;
return undef;
return close($fh);
my $FlushFileBuffers = Win32::API->new('kernel32.dll', 'FlushFileBuffers', 'N', 'N')
or die $^E;
sub FlushFileBuffers
my ($handle) = @_;
return $FlushFileBuffers->Call($handle);
my $fh = open_log_handle('log.txt')
or die $^E;
print($fh "log!\n")
or die $^E;
close_log_handle($fh)
or die $^E;
【讨论】:
【参考方案3】:您可以做的最好的事情是sysopen
带有O_SYNC
fcntl
标志,或fsync()
来自File::Sync
;您获得的选项确保数据不会在您的程序中缓冲,但对 kernel 是否正在缓冲写入不做任何事情(之所以这样做是因为不断将同一块刷新到磁盘会减慢所有其他 I/ ○)。即使这样,您也可能会输,因为某些硬盘会欺骗操作系统,并声称数据实际上仍在驱动器内存缓冲区中时已提交到媒体。
【讨论】:
在 Windows 上似乎无法使用 File::Sync(cygwin 除外) 我对 Windows 编程不够熟悉,无法提供帮助;希望其他人可以加入。 O_SYNC 在 Windows 中也不可用! 我建议你开始一个新线程来询问如何强制 Windows 将文件提交到磁盘,放弃 Perl 方面。一旦您弄清楚如何在 API 级别执行此操作,我可以帮助您编写一个 Win32::API 包装器来访问 API。如果您需要我在 Win32::API 方面的帮助,请在 www.perlmonks.org 上发布您的请求会更好,因为我可能会错过 ***w 上的问题。 @ikegami,感谢您的回复,我已经在原始问题中添加了一个编辑,内容是如何使用 Win32 API 执行无缓冲 IO。如果您能在 Win32 API 包装器方面提供帮助会很高兴。以上是关于perl 中的无缓冲 IO的主要内容,如果未能解决你的问题,请参考以下文章