Чтение системных файлов с помощью Perl без дополнительных системных вызовов поиска при открытии

Я пытаюсь использовать perl для разбора некоторых псевдофайлов из псевдофайловых систем /proc и /sys linux (procfs и sysfs). Такие файлы отличаются от обычных файлов — они реализуются пользовательскими обработчиками операций с файлами. Большинство из них имеют нулевой размер для stat, некоторые не могут быть открыты для чтения, другие не могут быть записаны. Иногда они реализованы неправильно (что является ошибкой, но она уже есть в ядре), и я все равно хочу прочитать их прямо из perl, не запуская какие-то вспомогательные инструменты.

Вот быстрый пример чтения /proc/loadavg с помощью perl, этот файл реализован корректно:

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

Существует lseek системный вызов, используемый open perl-функцией.

При использовании strace cat /proc/loadavg не было лишних системных вызовов типа 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.

Есть ли способ открыть файлы для чтения в perl5 (без внешних модулей) без вызова дополнительных lseek? (и без использования system("cat < /proc/loadavg"))

Есть ли способ открыть файлы для записи в perl5 без вызова дополнительных lseek?

Есть sysopen, но он тоже выполняет дополнительный поиск: perl -e 'use Fcntl;sysopen(F,"/proc/loadavg",O_RDONLY);sysread(F,$_,2048); print '


person osgx    schedule 02.11.2016    source источник
comment
В Linux-окне, на котором я пробовал, open F, "<:unix", "/proc/loadavg" делает только fstat и устанавливает close на exec   -  person ikegami    schedule 02.11.2016
comment
@ikegami, какая черная магия (<:unix) используется в вашем ответе (может быть полезна ссылка на документ или источник)? В моем тестовом Linux он тоже не искал.   -  person osgx    schedule 02.11.2016
comment
Как указано в документации для open, слои PerlIO задокументированы в PerlIO.   -  person ikegami    schedule 02.11.2016
comment
Проверьте perl -MPerlIO -E'open F, "<", $ARGV[0] or die $!; say for PerlIO::get_layers(\*F);' ~/.bashrc, затем повторите попытку, указав :unix.   -  person ikegami    schedule 02.11.2016
comment
@ikegame, еще есть fstat с my $SET,">:unix",".../driver_command; print $SET $COMMAND."\n"; close $SET; я могу отключить его? Мой скрипт все еще не может общаться с драйвером; но обычный echo SAME_COMMAND > .../driver_command работает, я вижу здесь только лишний fstat.   -  person osgx    schedule 14.11.2016


Ответы (2)


Как вы заметили, встроенный в Perl open маскирует довольно много магии. Если эта магия мешает вам, есть sysopen и POSIX::open(), которые предлагают пониженную степень магии. POSIX::open() в достаточной мере немагический, поскольку возвращает файловые дескрипторы, а не дескрипторы файлов Perl, и для получения данных из него приходится использовать POSIX::read() вместо обычных операторов Perl. Если это недостаточно грубо для ваших обстоятельств, вам может не повезти.

Модуль POSIX является частью основного дистрибутива Perl с самого первого выпуска Perl 5, поэтому, если у вас его нет, ваша установка Perl будет повреждена.

person Calle Dybedahl    schedule 03.11.2016
comment
Calle, пожалуйста, добавьте несколько ссылок на документацию. ikegami рекомендовал вариант <:unix черной магии для открытия, можете ли вы добавить его в качестве другого альтернативного решения к своему ответу? - person osgx; 03.11.2016
comment
Calle, спасибо, POSIX::open + read работает без лишних системных вызовов: 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 read: 0.16 0.09 0.02 ....` - person osgx; 24.11.2016
comment
В конце чтения есть специальный символ '\0'. Как я могу разобрать многострочный из POSIX::read, теперь нет while(<FILE>). - person osgx; 24.11.2016

Если вы хотите перейти на очень низкий уровень и избежать даже mmap из POSIX::open() (а также избежать загрузки огромного модуля POSIX), выполните syscall() самостоятельно. Вероятно, вы захотите require syscall.ph получить значения SYS_open и SYS_read, если вы их не знаете (для меня я знаю, что read, write и open равны 0,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

EDIT:
Относительно комментарий от @osgx,

В конце чтения есть специальный символ \0. Как я могу разобрать многострочный из POSIX::read, теперь нет while(<FILE>).

Обратите внимание, что когда вы 'while(<FILE>)', вы на самом деле просто вызываете read() по одному байту за раз и проверяете '\n' char - или что-то еще, на что установлен ваш $/ (разделитель входных записей) (вы можете подтвердить это через 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

Просто имейте в виду, что если вы стремитесь к переносимости любого вида, syscalling, вероятно, является одним из худших вариантов, но это цена специфичности. (POSIX::open/read/close() в этом смысле тоже ненамного лучше.). Для обеспечения переносимости вам может быть лучше использовать комментарий @ikegami (‹:unix) и игнорировать дополнительные вызовы fstat и fcntl;

person YenForYang    schedule 15.05.2018
comment
чтение, запись и открытие равны 0,1,2. Всегда ли они равны 0,1 и 2 для любой платформы Linux? - person osgx; 15.05.2018
comment
Неа; использование системных вызовов напрямую не является самым переносимым. Попробуйте require syscall.ph, чтобы узнать, какие константы SYS_read, SYS_write, SYS_open вам нужны. Примечание: я ничего не говорил о закрытии файлового дескриптора. Вы можете использовать POSIX::close(), но вы также можете использовать syscall(SYS_close,$fd) (для меня это syscall(3,$fd)). - person YenForYang; 16.05.2018