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
模块的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()实现操做系统层的读操做,它经过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()实现操做系统层的写操做,它经过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 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中。
从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
中提供了刷缓冲的方法: