Perl中的非缓冲IO

3
我有一个Perl应用程序,使用open和print调用将日志写入文件。
open (FH, "d:\\temp.txt");
print FH "Some log";
close (FH);

然而,在机器突然关闭时,日志不会被持久化到文件中。经过多处搜索后,有两个选项建议进行非缓冲IO(即将文本写入磁盘而不是将其保留在缓存中再刷新):
  1. sysopen, syswrite
  2. $| = 1;
我已尝试了这两个选项,但都不起作用。任何在异常关闭前几秒钟进行的写操作都会丢失。
有没有办法在Perl中实现几乎确定性的非缓冲IO?我正在运行Windows 7 64位,并且使用Perl 5.8.3。
编辑:我搜索了如何使Windows执行非缓冲IO,以下是如何完成的!调用
  1. 使用 FILE_FLAG_NO_BUFFERING 作为 dwFlagsAndAttributes 参数调用CreateFile。但是,这样会涉及到内存对齐问题(即文件访问缓冲区应该是扇区对齐的;应用程序可以通过调用GetDiskFreeSpace来确定扇区大小)。
  2. 使用 WriteFile 将数据写入文件。此写入将是非缓冲的,而不是进入缓存,直接进入磁盘。
  3. 最后,调用FlushFileBuffers刷新与文件关联的元数据。

请问有人能够协助使用Perl调用Win32 API进行这三个操作吗?


尝试在您的打印语句中添加\n并使用$| = 1 - a'r
$| 只在当前选择的文件句柄(默认为STDOUT) 上设置自动刷新,而不是所有文件句柄。 - Cameron
\n 对于没有连接到终端的句柄是无效的。对于具有自动刷新 ($|=1) 的句柄,不需要使用 \n - ikegami
我迫不及待地等待着对我最新答案的反馈! - ikegami
1
@ikegami,抱歉回复晚了!感谢您的解决方案,我已经接受了它!它完美地解决了问题!!! - Santhosh
@Santhosh,太棒了!很高兴听到这个消息,谢谢。我希望句柄更自然(不需要显式关闭),但那需要比我愿意付出的更多的代码。享受吧。 - ikegami
3个回答

6
use IO::Handle;
open(FH, "d:\\temp.txt");
FH->autoflush(1);
print FH "Some log";
close(FH);

这将立即将其传输到操作系统,但操作系统可能需要一段时间才能将其写入磁盘。我相信这仍然可以满足您的需求。

如果您使用的是Unix系统,我会建议您查看sync以获取有关如何让操作系统将数据写入磁盘的更多信息。


我也尝试过自动刷新,但仍有90%的情况下数据无法保存到硬盘中。 - Santhosh

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;
}

0
你能做的最好的选择是使用带有 O_SYNC fcntl 标志的 sysopen,或者使用来自 File::Syncfsync();你所得到的选项确保数据不会在程序内部缓冲,但并不能解决 内核 是否缓冲写入(因为不断刷新相同的块到磁盘会减慢所有其他 I/O)。即使这样,你仍然可能会失败,因为一些硬盘会欺骗操作系统,声称数据已经提交到媒体,而实际上仍然在驱动器内存缓冲区中。

似乎在Windows上(除了cygwin),无法使用File :: Sync。 - ikegami
我对Windows编程不够熟悉,希望有其他人可以介入。 - geekosaur
O_SYNC 在 Windows 中也不可用! - Santhosh
1
我建议您开启一个新的线程,询问如何强制Windows将文件提交到磁盘,不涉及Perl方面。一旦您在API级别上找到了解决方法,我可以帮助您编写Win32::API包装器来访问该API。如果您需要我的Win32::API帮助,请在www.perlmonks.org上发布您的请求,因为我可能会错过在StackOverflow上的问题。 - ikegami
@ikegami,感谢您的回复。我已经在原问题中添加了一个EDIT,说明如何使用Win32 API执行无缓冲IO。如果您能帮忙编写Win32 API包装器,我将不胜感激。 - Santhosh
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接