Что это за вспомогательные файловые дескрипторы?

Пока ковыряюсь /proc/self папка для моего mksh раковину я обнаружил странную вещь: в /proc/self/fd/* есть все стандартные файловые дескрипторы (0 для stdin, 1 для stdout и 2 stderr), некоторые файловые дескрипторы, но также есть и некоторые дополнительные - 24, 25, 3. И я технически могу перечислить их с глобусом в оболочке:

$ for fd in /proc/self/fd/* ; do echo $fd ; done                                                      
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
/proc/self/fd/24
/proc/self/fd/25
/proc/self/fd/3

Но когда я пытаюсь stat их или использовать find на них они сообщаются как несуществующие.

$ find  /proc/self/fd/*                                                                               
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
find: ‘/proc/self/fd/24’: No such file or directory
find: ‘/proc/self/fd/25’: No such file or directory
/proc/self/fd/3

То же самое происходит в bash, но только с одним вспомогательным файловым дескриптором.

$ for fd in /proc/self/fd/* ; do echo $fd; done
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
/proc/self/fd/255
/proc/self/fd/3

$ find /proc/self/fd/*
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
find: ‘/proc/self/fd/255’: No such file or directory
/proc/self/fd/3

Вопрос: что это за дополнительные файловые дескрипторы? Какова их цель?

1 ответ

Решение

Прощупывание /proc/self это сложный бизнес, так как он меняется для каждого процесса. Когда вы делаете /proc/self/fd/*оболочка расширяет подстановочный знак, поэтому она перечисляет свои собственные файловые дескрипторы. Но когда они передаются другой команде, как find или же lsпути теперь будут для этого процесса " /proc/selfи может иметь или не иметь fds с этими числами.

Еще хитрее, оболочка может открывать файловые дескрипторы во время расширения по шаблону.

Сравнивая с /proc/$$/fd может быть освещающим:

bash:

$ ls -l /proc/self/fd /proc/$$/fd/* &
[1] 5172
$ lrwx------ 1 muru muru 64 Jan  1 20:16 /proc/4932/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  1 20:16 /proc/4932/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  1 20:16 /proc/4932/fd/2 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  1 20:16 /proc/4932/fd/255 -> /dev/pts/1

/proc/self/fd:
total 0
lrwx------ 1 muru muru 64 Jan  1 20:24 0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  1 20:24 1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  1 20:24 2 -> /dev/pts/1
lr-x------ 1 muru muru 64 Jan  1 20:24 3 -> /proc/5172/fd

[1]+  Done                    ls --color=auto -l /proc/self/fd /proc/$$/fd/*

Отправив его на задний план, я получил bash для печати PID, и вы можете видеть, что /proc/self/fd/3 указывает на ls' своя /proc/<PID>/fd, который он открыл для сканирования. Записи с 4932, OTOH, предназначены для fds bash, а специальный - 255. Объяснение можно найти в этом посте SO:

Открытые файлы: 0 (стандартный вывод), 1 (стандартный вывод) и 2 (стандартный вывод). 255 - это маленькая хитрость, которую использует bash, чтобы сохранить их копии, когда они перенаправляются. Это специфично для bash.

Источник: https://books.google.com/books?id=wWjqCF9HLfYC&pg=PA231

С mksh:

$ ls -l /proc/self/fd /proc/$$/fd/*   &
[1] 5075
$ lrwx------ 1 muru muru 64 Jan  1 20:22 /proc/5074/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  1 20:22 /proc/5074/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  1 20:22 /proc/5074/fd/10 -> /dev/tty
lrwx------ 1 muru muru 64 Jan  1 20:22 /proc/5074/fd/2 -> /dev/pts/1

/proc/self/fd:
total 0
lrwx------ 1 muru muru 64 Jan  1 20:22 0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  1 20:22 1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  1 20:22 2 -> /dev/pts/1
lr-x------ 1 muru muru 64 Jan  1 20:22 3 -> /proc/5075/fd

[1] + Done                 ls -l /proc/self/fd /proc/$$/fd/* 

Практически то же самое, за исключением того, что дополнительный fd равен 10, и я бы поспорил, что это по той же причине, что и bash, поскольку исходный код указывает, что fd 10 и далее используется оболочкой.

У меня не было двух или трех дополнительных fds, но это могло произойти из-за любого количества вещей, происходящих во время расширения по шаблону, или из-за фоновых заданий или по какой-то другой неясной причине.

Если я буду управлять вашим for петля, я получаю эфемерный FD 3:

$ for fd in /proc/$$/fd/* ; do ls -l $fd ; done
lrwx------ 1 muru muru 64 Jan  2 17:39 /proc/6012/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  2 17:39 /proc/6012/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  2 17:39 /proc/6012/fd/2 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  2 17:39 /proc/6012/fd/255 -> /dev/pts/1
ls: cannot access '/proc/6012/fd/3': No such file or directory

И вот, используя strace отследить исполнение:

strace -e open -o log bash -c 'for fd in /proc/$$/fd/* ; do : ; done'

Мы увидим, что третий FD, на самом деле, /proc/<PID>/fd:

$ tail log
open("/usr/lib/libreadline.so.7", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libncursesw.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/dev/tty", O_RDWR|O_NONBLOCK)     = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/gconv/gconv-modules.cache", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/lib/gconv/gconv-modules", O_RDONLY|O_CLOEXEC) = 3
open("/proc/9975/fd/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
+++ exited with 0 +++

Теперь вопрос, почему этот fd не появился в более ранних ls тесты? Похоже, фон был как-то связан с этим:

$ ls -l /proc/$$/fd/*   &
[1] 10091
$ lrwx------ 1 muru muru 64 Jan  2 17:46 /proc/10076/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  2 17:46 /proc/10076/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  2 17:46 /proc/10076/fd/2 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  2 17:46 /proc/10076/fd/255 -> /dev/pts/1

[1]+  Done                    ls --color=auto -l /proc/self/fd /proc/$$/fd/*
$ ls -l /proc/$$/fd/* 
ls: cannot access '/proc/10076/fd/3': No such file or directory
lrwx------ 1 muru muru 64 Jan  2 17:46 /proc/10076/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  2 17:46 /proc/10076/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  2 17:46 /proc/10076/fd/2 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  2 17:46 /proc/10076/fd/255 -> /dev/pts/1

Передний план ls показывает недостающий фд.

Теперь снова отслеживание с strace:

strace -fe open,execve,fork -o log bash -ic 'ls -l /proc/self/fd /proc/$$/fd/* &'

Мы видим:

10731 execve("/usr/bin/bash", ["bash", "-ic", "ls -l /proc/$$/fd/* &"], [/* 67 vars */]) = 0
10731 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
# snip
10734 open("/proc/10731/fd/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
10734 execve("/usr/bin/ls", ["ls", "--color=auto", "-l", "/proc/10731/fd/0", "/proc/10731/fd/1", "/proc/10731/fd/2", "/proc/10731/fd/255"], [/* 68 vars */]) = 0

Обратите внимание на изменение в PID. Кажется, что расширение подстановки происходит после разветвления, но расширение переменной происходит до этого. Итак, fd 3 существует, но в другом процессе. Теперь я использую self вместо $$вы увидите 3 и 255:

$ strace -fe open,execve -o log bash -ic 'ls -l /proc/self/fd/* &'
[1] 10790
ls: cannot access '/proc/self/fd/255': No such file or directory
ls: cannot access '/proc/self/fd/3': No such file or directory
lrwx------ 1 muru muru 64 Jan  2 18:04 /proc/self/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  2 18:04 /proc/self/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan  2 18:04 /proc/self/fd/2 -> /dev/pts/1

добавление

Соответствующий ответ на сайте Unix & Linux Stackexchange цитирует ответ из списка рассылки:

Fd 255 используется внутри как соединение с tty, так что это не мешает использованию exec для перемещения fds. Bash также выделяет высокие fds при обработке подстановки процесса `<(foo) 'по той же причине.

Андреас Шваб

Другие вопросы по тегам