Perl IO:操做系統層次的IO

sysopen()

open()和sysopen()都打開文件句柄,open()是比較高層次的打開文件句柄,sysopen()相對要底層一點。但它們打開的文件句柄並無區別,只不過sysopen()有一些本身的特性:可使用幾個open()沒有的flag,能夠指定文件被建立時的權限等。函數

必定要注意的是,io buffer和open()、sysopen()無關,而是和讀、寫的方式有關,例如read()、getc()以及行讀取都使用io buffer,而sysread、syswrite則直接繞過io buffer。操作系統

例如:unix

sysopen HANDLE, "file.txt", O_RDONLY;
open HANDLE, $filename, O_WRONLY|O_CREAT, 0644;
open HANDLE, $filename, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;

其中sysopen支持的flag部分有如下幾種:指針

# 三種基本讀寫模式
O_RDONLY
O_RDWR
O_WRONLY

# 配合基本讀寫模式的額外模式
O_APPEND
O_TRUNC
O_CREAT
O_EXCL       只能配合CREAT使用,只有文件
             不存在時才建立,文件存在時直
             接失敗而不是打開它

O_BINARY     二進制模式,表示不作換行符轉換
O_TEXT       文本模式,表示作換行符轉換

O_NONBLOCK  非阻塞模式
O_NDELAY    同上,另外一種表示方式,爲了可移
            植性,建議使用上面的模式

非阻塞讀寫

sysopen()比open()多出的一個好用的特性就是O_NONBLOCK。在使用非阻塞的IO時,能夠在等待過程當中去執行其它任務。code

可是須要明確一點,對於普通文件的讀寫,若是沒有使用文件鎖,那麼沒有必要使用O_NONBLOCK,由於內核緩衝區的存在使得對普通文件的讀寫不可能會出現阻塞問題。seo

例如:ip

use Fcntl;
open HANDLE, '/dev/ttyS0', O_RDONLY | O_NONBLOCK;

# sysread一個字符,但不會阻塞
my $key;
while(sysread HANDLE, $key, 1){
    if (defined $key){
        print "got $key\n";
    } else {
        do other tasks;
    }
    sleep 1;
}

close HANDLE;

O_NONBLOCK在沒法成功返回時,將返回EAGAIN,並設置給$!變量。因此,更優寫法以下:資源

use Fcntl;
open HANDLE, '/dev/ttyS0', O_RDONLY | O_NONBLOCK;

# sysread一個字符,但不會阻塞
my $key;
while(sysread HANDLE, $key, 1){
    if (defined $key){
        print "got $key\n";
    } else {
        if($! == 'EAGAIN'){
            do other tasks;
            sleep 1;
        } else {
            warn "Error read: $!";
            last;
        }
    }
}

close HANDLE;

IO::File自動探測(sys)open

使用IO::File模塊的new()方法能夠根據提供的參數方式自動探測要調用open()仍是sysopen()來打開文件句柄:字符串

  • 若是使用字符串的模式(< > >> +> +< +>>),則調用open()
  • 若是使用數值格式或O_XXX或使用了權限位,則調用sysopen()

例如:get

# open()
my $fh = IO::File->new($filename, "+<");

# sysopen()
my $fh = IO::File->new($filename, O_RDWR);
my $fh = IO::File->new($file, O_RDWR, 0644);

sysread()

sysread()實現操做系統層的讀操做,它經過read()系統調用直接讀取文件描述符,會直接繞過IO Buffer層。

sysread FILEHANDLE,SCALAR,LENGTH,OFFSET
sysread FILEHANDLE,SCALAR,LENGTH

表示從FILEHANDLE文件句柄中讀取LENGTH字節長度的數據保存到標量SCALAR中,若是指定了OFFSET,則從標量的OFFSET位置處開始寫入讀取到的數據。sysread()的返回值是讀取到的字節數。

下面是一個結合O_NONBLOCK修飾符的sysread()。

#!/usr/bin/perl
use strict;
use warnings;
use Fcntl;

sysopen my $fh, $ARGV[0], O_RDONLY | O_NONBLOCK
    or die "open failed: $!";

my $data;
my $size = sysread $fh, $data, 20;
if ($size == 20 ){
    # 已讀取20字節
    # 繼續讀取30字節追加到$data尾部
    $size += sysread $fh, $data, 30, 20;
    print "已讀數據爲: $data\n";
    if ($size < 50){
        print "文件大小爲$size,數據不足50字節\n";
    }else{
        print "已讀字節數:$size\n";
    }
} elsif($size > 0) {
    print "文件大小爲$size,數據不足20字節\n";
} else {
    print "空文件\n";
}

在上面的代碼中,主要是sysread()和O_NONBLOCK須要解釋下。若是沒有O_NONBLOCK,那麼sysread()在讀取不到20字節、50字節時將被阻塞等待。但如今的狀況下,若是數據不足20、50字節時,sysread()將直接返回,並返回讀取到的字節數(小於20或30)。

這裏若是sysread()替換成read(),它們的區別是,sysread()只讀取20或50字節數據,不會多讀任何一個字節,而read()則可能多讀一些數據到IO Buffer中,可是隻取其中的20或50字節,剩餘的數據遺留在IO Buffer中

因而,文件IO的指針就出現了不一樣值,對於sysread,他的指針就是20或50位置處,可是read()讀取數據時,底層的文件指針將可能出如今1000字節處,但IO buffer中的IO指針將在20或50處。根據這個差值,能夠計算出IO Buffer中保存了多少字節的數據。見後文sysseek()。

最後注意,不存在syseof()這樣的函數來判斷是否讀取到了文件的結尾。可是,能夠經過讀取數據的返回值(即讀了多少字節)來判斷是否到了文件結尾

syswrite()

syswrite()實現操做系統層的寫操做,它經過write()系統調用直接向文件描述符中寫入數據,它會直接繞過IO Buffer層。

syswrite FILEHANDLE,SCALAR
syswrite FILEHANDLE,SCALAR,LENGTH
syswrite FILEHANDLE,SCALAR,LENGTH,OFFSET

若是隻有兩個參數,則直接將標量SCALAR表明的數據寫入到FILEHANDLE中,若是指定了LENGTH,則從SCALAR中取LENGTH字節長度的數據寫入到FILEHANDLE中,若是指定了OFFSET,則表示從標量的OFFSET處開始截取LENGTH長度的數據寫入到FILEHANDLE中。OFFSET能夠指定爲負數,這表示從尾部開始數寫入OFFSET個字節的數據。若是LENGTH大於從OFFSET開始計算能夠獲取到的數據,則能獲取到多少數據就寫入多少數據。

syswrite()返回實際寫入的字節數,若是出錯了則返回undef。

例如:

# 寫入abcdef
syswrite STDOUT, "abcdef";

# 寫入abc
syswrite STDOUT, "abcdef", 3;

# 寫入cde
syswrite STDOUT, "abcdef", 3, 2;

# 寫入cde
syswrite STDOUT, "abcdef", 3, -4;

實際上,不適用syswrite(),直接使用print也能夠繞過io buffer,但前提是設置文件句柄的IO層爲unix層,由於unix層是文件句柄到文件描述符的最底層,它會禁用全部上層,包括buffer。

binmod(FILEHANDLE, ":unix");

例以下面的例子中,在10秒內將每秒輸出一個點,若是把binmode那行刪除,將在10秒以後一次性輸出10個點。若是刪除binmode,還能夠將print "."改成syswrite STDOUT, ".";,它將一樣每秒輸出一個點。

#!/usr/bin/perl

binmode(STDOUT, ":unix");
for (0..9){
    print ".";   # syswrite STDOUT, ".";
    sleep 1;
}
print "\n";

sysseek()

sysseek FILEHANDLE,POSITION,WHENCE

sysseek()經過lseek()系統調用設置或返回IO指針的位置,它直接繞過IO Buffer操做文件描述符。它和seek()的語法上沒什麼區別:

# seek using whence numbers
sysseek HANDLE, 0, 0; # rewind to start
sysseek HANDLE, 0, 2; # seek to end of file
sysseek HANDLE, 20, 1; # seek forward 20 characters

# seek using Fcntl symbols
use Fcntl qw(:seek);
sysseek HANDLE, 0, SEEK_SET; # rewind to start
sysseek HANDLE, 0, SEEK_END; # seek to end of file
sysseek HANDLE, 20, SEEK_CUR; # seek forward 20 characters

sysseek()返回設置IO指針位置後的新位置。例如原來IO指針位置爲第三個字節處,向前移動5字節後,sysseek()將返回8。因此,能夠經過sysseek()來實現tell()函數的功能,只需將sysseek()相對於當前位置移動0字節便可:

sysseek(HANDLE, 0, SEEK_CUR);

除了繞過IO buffer,sysseek()和seek()基本相同。但正是它繞過了io buffer,致使了sysseek()和tell()的結果可能大不同,tell()獲取的是IO Buffer中IO指針的位置,而sysseek(HANDLE, 0, SEEK_CUR)獲取的是文件描述符層次的IO指針位置。因此在使用IO Buffer類的讀函數時,能夠經過sysseek() - tell()計算出緩衝在IO Buffer中的數據比緩衝在page cache中的數據少多少字節。這種額外緩衝一些數據到page cache的行爲稱爲"預讀"(readahead()),它帶來的好處是可能會減小後續讀操做阻塞的時間。

下面是一個說明tell()和sysseek()區別的示例:

#!/usr/bin/perl
use strict;
use warnings;

use 5.010;
use Fcntl q(:seek);

open my $fh, "<", "abc.log";

my $readed;
read $fh, $readed, 5;

print "tell pos: ", tell($fh);
print "sysseek pos: ", sysseek($fh, 0, SEEK_CUR);

向abc.log中寫入10個字節(實際爲11個字節,由於echo自動加換行符)的數據:

$ echo "0123456789" >abc.log

執行上面的Perl程序:

tell pos: 5
sysseek pos: 11

上面的程序中,使用read()函數讀取了5個字節數據,可是read()是使用IO Buffer的,它會從磁盤文件中多讀取一些數據放到page cache中,例如這裏最多隻能讀11字節到page cache,而後從page cache中讀取指定數量5字節的數據到IO buffer中供read讀取並保存到$readed標量中。由於文件描述符已經讀取了11個字節的數據,因此sysseek()的返回值爲11,而tell()則是io buffer中讀取數據的位置,即5字節。因此,read()結束後,page cache中還剩下6字節的數據供後續讀操做放入到io buffer中

IO::Handle:sync()

從perl的文件句柄到操做系統的文件描述符,再從文件描述符到設備上的實體文件,兩個層次之間都有各自的buffer層。

+--------------------------+
|        FileHandle        |
+--------------------------+
  |
  | Perl IO Buffer
  v
+--------------------------+
|     File Description     |
+--------------------------+
  |
  | page cache/buffer cache
  v
+--------------------------+
|        (dev)disk         |
+--------------------------+

其中文件描述符是操做系統的資源,從文件描述符到硬件設備之間的緩衝(page cache),能夠經過fsync()來刷,但Perl文件句柄層的IO Buffer,操做系統顯然不負責這個屬於Perl IO的上層緩衝。

Perl的IO::Handle中提供了刷緩衝的方法:

  • flush():將Perl IO Buffer中的數據刷到文件描述符
    • IO Buffer中未讀取完的數據被丟棄
    • IO Buffer中未寫入完成的數據當即寫入到文件描述符,寫入完成後flush才成功
  • sync():實現和系統調用fsync()相同的功能,將文件描述符到設備文件之間的緩衝刷盤
相關文章
相關標籤/搜索