使用 perl 读取系统文件而不在打开时发出额外的搜索系统调用

Reading system files with perl without issuing extra seek syscalls on open

我正在尝试使用 perl 从 /proc/sys linux 伪文件系统 (procfs and sysfs) 中解析一些伪文件。此类文件不同于常规文件 - 它们由自定义文件操作处理程序实现。其中大多数 stat 的大小为零,有些无法打开以供读取,有些则无法写入。有时它们实现不正确(这是错误,但它已经在内核中),我仍然想直接从 perl 读取它们而不启动一些辅助工具。

有使用 perl 读取 /proc/loadavg 的快速示例,此文件已正确实现:

perl -e 'open F,"</proc/loadavg"; $_=<F>; print '

通过命令的 strace 我可以检查 perl 如何实现 open 函数:

$ strace perl -e 'open F,"</proc/loadavg"; $_=<F>; print ' 2>&1 | egrep -A5 ^open.*loadavg

open("/proc/loadavg", O_RDONLY)         = 3
ioctl(...something strange...)    = -1 ENOTTY
lseek(3, 0, SEEK_CUR)                   = 0
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0

open perl 函数使用了 lseek 个系统调用。

使用 cat /proc/loadavg 的 strace 没有额外的 seek 类型的系统调用:

$ strace cat /proc/loadavg 2>&1 | egrep -A2 ^open.*loadavg
open("/proc/loadavg", O_RDONLY)         = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0

我想读(或写)的特殊文件误实现了 seek 文件操作,不会向 read(或 write)系统调用提供任何有用的数据 seek.

是否可以在不调用额外 lseek 的情况下打开文件以在 perl5(无外部模块)中读取? (并且不使用 system("cat < /proc/loadavg")

有没有办法在不调用额外的情况下打开文件以在 perl5 中写入 lseek

有 sysopen,但它也有额外的 lseek:perl -e 'use Fcntl;sysopen(F,"/proc/loadavg",O_RDONLY);sysread(F,$_,2048); print '

如您所见,Perl 的内置 open 掩盖了相当多的魔法。如果魔法挡住了你的路,sysopenPOSIX::open() 会提供递减的魔法等级。 POSIX::open() 非常不神奇,它 returns 文件描述符而不是 Perl 文件句柄,你必须使用 POSIX::read() 而不是普通的 Perl 运算符来从中获取数据。如果这对你的情况来说不够原始,你可能运气不好。

POSIX 模块是核心 perl 发行版的一部分,因为它是 Perl 5 的第一个版本,所以如果你没有它,你的 Perl 安装就会瘫痪。

如果你想要真正的低级并避免 POSIX::open() 中的 mmap(并且避免加载巨大的 POSIX 模块),请执行 syscall()是你自己。可能想 require syscall.ph 获取 SYS_openSYS_read 的值,如果你不知道它们(对我来说 我知道 readwriteopen 分别是 012 - 这对于下面的 syscall 函数来说很重要) .

以下代码:

strace perl -mPOSIX -e'$fd=POSIX::open("/proc/loadavg");POSIX::read($fd, 
$_, 9999);' 2>&1 | egrep -A2 '^open.*loadavg'

给出类似的东西(对我来说 open()openat()

openat(AT_FDCWD, "/proc/loadavg", O_RDONLY) = 3
mmap(NULL, 135168, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =             
0x7f2389f0d000
read(3, "1.22 2.51 1.54 3/206 18145\n", 9999) = 27

尝试这样的事情:

strace perl -MFcntl -E'$p="/proc/loadavg"; $fd=syscall 2, $p, O_RDONLY; $bf = 
"[=12=]"x50; syscall 0, $fd, $bf, 50' 2>&1 | egrep -A1 '^open.*loadavg'

并得到:

open("/proc/loadavg", O_RDONLY) = 3
read(3, "0.45 0.18 0.20 2/241 12349\n", 50) = 27

编辑:
关于,

There is [=31=] special char in end of the read. How can I parse multiline from POSIX::read, there is no while(<FILE>) now.

请注意,当您“while(<FILE>)”时,您实际上只是一次调用 read() 一个字节并检查 '\n' 字符——或任何您的 $/(输入记录分隔符)设置为(您可以通过 strace 确认)。

因此,检查 $/ 的简单循环就足够了。 (注意 read() returns,成功时,读取的字节数(0 表示 EOF)。这是单个“readline”的粗略示例:

require 'syscall.ph';
require Fcntl;
my($path, $fd, $buf, $res);
$path = '/proc/meminfo';
$fd = syscall SYS_open(), $path, O_RDONLY;
$buf = ' ';
$res = '';
$res .= $buf while syscall SYS_read(), $fd, $buf, 1 and $buf ne $/;
syscall SYS_close(), $fd; # optional in this case

请注意,如果您想要便携性syscall-ing 可能是其中之一最糟糕的选择,但这是特异性的代价。 (从这个意义上说,POSIX::open/read/close() 也好不了多少。)。为了保持可移植性,您最好使用 ,并忽略对 fstatfcntl;

的额外调用