Небуферизованный IO в Perl
У меня есть приложение Perl, которое записывает журналы в файл, используя вызовы open и print.
open (FH, "d:\\temp.txt");
print FH "Some log";
close (FH);
Однако во время внезапного выключения компьютера журналы не сохраняются в файл. Поэтому после поиска в нескольких местах были предложены два варианта выполнения небуферизованного ввода-вывода (т.е. записи текста на диск вместо сохранения его в кеше и последующей его очистки):
Я пробовал оба эти варианта, и он просто не работает. Любая запись, которую я делаю за секунды до ненормального отключения, теряется.
Есть ли способ, которым я могу почти детерминистически выполнить небуферизованный ввод-вывод в Perl? Я использую 64-битную Windows 7 с Perl 5.8.3.
РЕДАКТИРОВАТЬ: Я искал, как заставить Windows выполнять небуферизованный ввод-вывод, и это, как это может быть сделано! Вызов
- CreateFile с FILE_FLAG_NO_BUFFERING для параметра dwFlagsAndAttributes. Однако при этом необходимо учитывать проблемы выравнивания памяти (т. Е. Буферы доступа к файлам должны быть выровнены по секторам; приложение определяет размер сектора, вызывая GetDiskFreeSpace).
- Используйте WriteFile для записи данных в файл. Эта запись будет буферизована, и вместо того, чтобы идти в кеш, она сразу переходит на диск.
- Наконец, вызовите FlushFileBuffers для сброса метаданных, связанных с файлами.
Может кто-нибудь помочь с Win32 API от Perl для этих 3 вызовов.
3 ответа
Как насчет этого?
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;
}
use IO::Handle;
open(FH, "d:\\temp.txt");
FH->autoflush(1);
print FH "Some log";
close(FH);
Это доставит его в ОС как можно скорее, но ОС может занять некоторое время, чтобы зафиксировать его на диске. Тем не менее я уверен, что вы найдете это подойдет вашим потребностям.
Если бы вы работали в Unix, я бы сослался на вашу синхронизацию для получения дополнительной информации о том, как ОС передает данные на диск.
Лучшее, что вы можете сделать, это sysopen
с O_SYNC
fcntl
флаг или fsync()
от File::Sync
; опции, которые вам дали, гарантируют, что данные не буферизуются внутри вашей программы, но ничего не делают с тем, выполняет ли ядро буферизацию записей (что происходит потому, что постоянная очистка одного и того же блока на диск замедляет все другие операции ввода-вывода). И даже в этом случае вы можете проиграть, потому что некоторые жесткие диски будут лгать ОС и утверждать, что данные были записаны на носитель, когда они все еще находятся в буфере памяти на диске.