码迷,mamicode.com
首页 > 其他好文 > 详细

Perl IO:操作系统层次的IO

时间:2019-03-02 11:05:57      阅读:333      评论:0      收藏:0      [点我收藏+]

标签:大于   led   移动   binary   数值   文件的   绕过   lseek   usr   

sysopen()

open()和sysopen()都打开文件句柄,open()是比较高层次的打开文件句柄,sysopen()相对要底层一点。但它们打开的文件句柄并没有区别,只不过sysopen()有一些自己的特性:可以使用几个open()没有的flag,可以指定文件被创建时的权限等。

一定要注意的是,io buffer和open()、sysopen()无关,而是和读、写的方式有关,例如read()、getc()以及行读取都使用io buffer,而sysread、syswrite则直接绕过io buffer。

例如:

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时,可以在等待过程中去执行其它任务。例如:

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()

例如:

# 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中的数据长度。

下面是一个说明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的,它会从文件描述符中多读取一些数据到IO Buffer中(一般是几K的数据),例如这里最多只能读11字节到io buffer,然后从io buffer中返回指定数量5字节的数据保存到$readed标量中。因为已经从文件描述符中读取了11个字节的数据,所以sysseek()的返回值为11,而tell()则是io buffer中读取数据的位置,即5字节。所以,read()结束后,io buffer中还剩下6字节的数据

Perl IO:操作系统层次的IO

标签:大于   led   移动   binary   数值   文件的   绕过   lseek   usr   

原文地址:https://www.cnblogs.com/f-ck-need-u/p/10459768.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!