【问题标题】:Reading system files with perl without issuing extra seek syscalls on open使用 perl 读取系统文件而不在打开时发出额外的搜索系统调用
【发布时间】:2016-11-02 04:49:31
【问题描述】:

我正在尝试使用 perl 解析来自/proc/sys linux 伪文件系统(procfssysfs)的一些伪文件。此类文件与常规文件不同 - 它们由自定义文件操作处理程序实现。其中大多数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 文件操作,并且在seek 之后不会向read(或write)系统调用提供任何有用的数据。

有没有办法在不调用额外lseek 的情况下打开文件以在 perl5(无外部模块)中读取? (并且不使用system("cat &lt; /proc/loadavg")

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

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

【问题讨论】:

  • 在我尝试过的 linux 机器上,open F, "&lt;:unix", "/proc/loadavg" 只执行fstat 并在 exec 上设置关闭
  • @ikegami,你的回答中使用了什么样的黑魔法(&lt;:unix)(文档或源链接可能有用)?它也没有在我的测试 linux 上搜索。
  • open 的文档中所述,PerlIO 层记录在PerlIO 中。
  • 签出perl -MPerlIO -E'open F, "&lt;", $ARGV[0] or die $!; say for PerlIO::get_layers(\*F);' ~/.bashrc,然后再次尝试指定:unix
  • @ikegame,还有fstatmy $SET,"&gt;:unix",".../driver_command; print $SET $COMMAND."\n"; close $SET;我可以禁用它吗?我的脚本仍然无法与驱动程序通信;但普通的echo SAME_COMMAND &gt; .../driver_command 有效,我在这里只看到额外的 fstat。

标签: linux perl file-io seek procfs


【解决方案1】:

正如您所注意到的,Perl 的内置 open 掩盖了相当多的魔法。如果魔法阻碍了你,那么sysopenPOSIX::open() 会提供递减的魔法。 POSIX::open() 足够不神奇,它返回文件描述符而不是 Perl 文件句柄,您必须使用 POSIX::read() 而不是普通的 Perl 运算符来从中获取数据。如果这对您的情况还不够原始,那么您可能就不走运了。

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

【讨论】:

  • 请调用,添加一些文档链接。 ikegami 推荐了&lt;:unix 打开的黑魔法选项,您可以将其添加为您的答案的其他替代解决方案吗?
  • 调用,谢谢,POSIX::open + 读取无需任何额外的系统调用即可工作:perl -e 'use POSIX (); $fd=POSIX::open("/proc/loadavg");$bytes = POSIX::read( $fd, $buf, 30 ); POSIX::close($fd);print $bytes." read: ".$buf;' with result 27 读取:0.16 0.09 0.02 ....`
  • 读取末尾有 '\0' 特殊字符。如何从 POSIX::read 解析多行,现在没有 while(&lt;FILE&gt;)
【解决方案2】:

如果你想真正做到低级,甚至避免来自POSIX::open()mmap(也避免加载巨大的POSIX 模块),请自己执行syscall()s。如果你不知道require syscall.ph 来获取SYS_openSYS_read 的值(对我来说我知道readwriteopen0,1,2,这对于下面的 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 = 
"\0"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

编辑
关于comment from @osgx

读取末尾有\0 特殊字符。如何从 POSIX::read 解析多行,现在没有 while(&lt;FILE&gt;)

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

因此,检查$/ 的简单循环就足够了。 (请注意,read() 成功时会返回读取的字节数(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() 在这个意义上也好不了多少。)。为了保持可移植性,您最好使用@ikegami's comment (<:unix),并忽略对fstatfcntl 的额外调用;

【讨论】:

  • "读、写和打开都是 0,1,2" 对于任何 linux 平台,它们总是 0,1 和 2 吗?
  • 不;直接使用系统调用并不是最便携的。尝试require syscall.ph 找出SYS_readSYS_writeSYS_open 常量适合您。旁注:我没有关闭文件描述符的任何方法。你可以使用POSIX::close(),但你也可以使用syscall(SYS_close,$fd),(对我来说是syscall(3,$fd))。
猜你喜欢
  • 1970-01-01
  • 2016-01-20
  • 1970-01-01
  • 2013-02-13
  • 1970-01-01
  • 2016-10-14
  • 1970-01-01
  • 2020-10-21
  • 1970-01-01
相关资源
最近更新 更多