Есть ли в Linux слой/скрипт, который обрабатывает запросы программ на открытие файлов?
Например, когда вы открываете дескриптор файла в bash: exec 3 <>/documents/foo.txt
или ваш текстовый редактор открывает/documents/foo.txt
Я не могу поверить, что редактор может «просто открыть файл» для чтения/записи самостоятельно.
Я скорее представляю себе это как запрос к "слою" (скрипт init.d?), который изначально может открывать только определенное количество файлов и ведет учет открытых файлов с указанием их типов доступа, процессов, с помощью которых они открываются и т. д.
решение1
Этот уровень находится внутри ядра в Linux и других системах, которые не слишком отходят от исторической архитектуры Unix (а также в большинстве не-Unix операционных систем).
Эта часть ядра называетсяУровень VFS (виртуальной файловой системы). Роль VFS заключается в управлении информацией об открытых файлах (соответствие междудескрипторы файлов,открыть описания файлови записи каталога), для анализа путей к файлам (интерпретация /
, .
и ..
) и для отправки операций над записями каталога соответствующему драйверу файловой системы.
Большинство драйверов файловых систем также находятся в ядре, ноПРЕДОХРАНИТЕЛЬДрайвер файловой системы позволяет делегировать эту функциональность за пределы ядра. Операции файловой системы также могут включать код пользовательского пространства, если это делает хранилище более низкого уровня, например, если файловая система диска находится напетлевое устройство.
решение2
Открытие файлов в Linux осуществляется непосредственно ядром.Но есть несколько вещей, которые вы можете сделать, чтобы повлиять на этот процесс и изучить его.
Системные вызовы
Начиная сверху, вы можете увидеть, что интерфейс, который приложения используют для взаимодействия с файлами,системные вызовы.
Открыть,читатьиписатьделай то, что от тебя ожидают, покастатвозвращает информацию о файле, не открывая его.
Вы можете изучить использование программой системных вызовов, связанных с файлами, с помощью strace:
$ strace -e trace=%file /bin/ls /etc
[...]
stat("/etc", {st_mode=S_IFDIR|0755, ...}) = 0
openat(AT_FDCWD, "/etc", O_RDONLY...) = 3
Это анализирует системные вызовы, вызванные ls /etc
, показывая, что stat
и openat
вызываются для /etc
каталога.
Вы можете быть удивлены, почему мы вызываем файловые операции в каталоге. В UNIX каталоги тоже являются файлами. Фактическивсе есть файл!
Файловые дескрипторы
Вас, возможно, интересует openat() = 3
вывод выше.
В UNIX открытые файлы представлены в видедескриптор файла, который является уникальным представлением открытого файла определенным процессом. Файловые дескрипторы 0, 1 и 2 обычно зарезервированы длястандартные потоки(пользовательский ввод/вывод), поэтому первым открытым файлом будет файл 3.
Вы можете получить список открытых файловых дескрипторов для заданного процесса, используяlsof
(лясторучкаффайлы):
$ cat /dev/urandom > /dev/null &
[1] 3242
$ lsof -p 3242
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
...
cat 3242 user 0u CHR 136,0 0t0 3 /dev/pts/0
cat 3242 user 1w CHR 1,3 0t0 1028 /dev/null
cat 3242 user 2u CHR 136,0 0t0 3 /dev/pts/0
cat 3242 user 3r CHR 1,9 0t0 1033 /dev/urandom
В FD
столбце отображается номер дескриптора файла, а также доступ.
Вы также можете использоватьfuser
для поиска процессов, содержащих определенные файлы:
$ fuser /dev/urandom
/dev/urandom: ... 3242 ...
Информация о процессе псевдофайловой системы - /proc
Сейчас вы, возможно, задаетесь вопросом:но как lsof
узнать, какие файлы открыты в первую очередь??
Ну что ж, давайте посмотрим!
$ strace -e trace=%file lsof -p 3242
...
stat("/proc/3242/", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc/3242/stat", O_RDONLY) = 4
...
openat(AT_FDCWD, "/proc/3242/fd", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 4
readlink("/proc/3242/fd/0", "/dev/pts/0", 4096) = 10
lstat("/proc/3242/fd/0", {st_mode=S_IFLNK|0700, st_size=64, ...}) = 0
stat("/proc/3242/fd/0", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
openat(AT_FDCWD, "/proc/3242/fdinfo/0", O_RDONLY) = 7
...
Так что lsof
знает, какие файлы открыты... чтением других файлов! А именно, каталог /proc/3242/fd
. Все, что находится ниже, /proc
является "поддельной" файловой системой, хранящейся в ядре. Вы можете ls -l
увидеть ее структуру.
Влияние на открытие файла
Существует несколько методов, которые можно использовать для управления открытием файлов, хотя они не так просты, как простая замена какого-либо скрипта.
Если вы хотите изменить способ хранения файлов или доступа к ним, например, обеспечить их шифрование, кэширование, распределение по нескольким дискам или что-то подобное, то, скорее всего, уже существуеткартограф устройствакоторый соответствует вашим потребностям.
Если вам нужен точный контроль над открытием файлов в определенном каталоге/монтировании, вы можете написать простуюПРЕДОХРАНИТЕЛЬфайловую систему и смонтировать ее.
На уровне программы/процесса вы можете использоватьLD_PRELOADдля изменения вызовов библиотеки C и предотвращения выполнения ими обычных системных вызовов.
Самый сложный, но и самый гибкий способ — написать собственный драйвер файловой системы.
решение3
Управление доступом к файлам — это, пожалуй, первая и самая важная функция операционной системы. DOS, одна из старейших операционных систем на персональных компьютерах, означает Disk Operating System (дисковая операционная система). Она позволяла программам напрямую обращаться к оборудованию в большинстве случаев, но не к файлам. Программам приходилось использовать вызовы DOS, а DOS управлял помещением данных в файлы и извлечение их для программы. Только дисковые утилиты могли получать доступ к жесткому диску и файлам напрямую под DOS.
Современные операционные системы с защищенным режимом, такие как Linux, обрабатывают доступ к файлам так же, как DOS, но они также требуют, чтобы любой доступ к чему-либо за пределами самой программы (или любой другой программы, с которой она настроена на совместное использование памяти) проходил через ядро (Linux — это ядро).
Ваша программа в Linux может вызвать функцию в библиотеке C для чтения или записи данных в файл. Затем библиотека C выполняет свою часть организации доступа к данным в файле, продолжая работать в том же контексте, что и ваша программа. Затем библиотека C вызовет ядро (Linux) с правильной функцией для доступа к файлу, что переключает ЦП в режим Ring 0 или привилегированный режим. Теперь ЦП запускает драйвер файловой системы Linux и программное обеспечение драйвера жесткого диска в привилегированном режиме, которое напрямую обращается к оборудованию для доступа к файлу. Данные копируются в область памяти, куда библиотека C указала Linux поместить данные, и ЦП переключается обратно в пользовательский режим с контекстом безопасности вашей программы, а библиотека C возобновляет работу и выполняет всю необходимую обработку этих данных, а затем возвращается к выполнению вашей программы.
решение4
Короче говоря, вот что происходит, когда программа записывает данные в файл.
- Программа запрашивает у ядра
open
файл, указанный по пути, для записи. - Ядро устанавливает некоторые внутренние структуры и делегирует часть задачи открытия файла драйверу, специфичному для типа файловой системы. Затем ядро возвращает дескриптор файла, который является просто целым числом (например, 3), программе.
- Программа запрашивает у ядра
write
последовательность байтов (например, строку) для файла, на который ссылается файловый дескриптор. - Ядро снова делегирует работу драйверу.
- Шаги 3 и 4, вероятно, повторяются несколько раз.
- Программа запрашивает у ядра доступ к
close
файлу, на который ссылается файловый дескриптор. - Ядро снова делегирует работу драйверу, а затем уничтожает внутренние структуры.
Вот довольно минималистичная ассемблерная программа, которая записывает «Hello World!» в файл greeting.txt:
.text
.globl _start
_start:
# Open and possible create file
mov $2, %rax # syscall 'open'
mov $path_start, %rdi # path
mov $0101, %rsi # create + write
mov $400, %edx # only user gets read permissions
syscall
mov %rax, %r10 # file descriptor
# Write string to file
mov $1, %rax # syscall 'write'
mov %r10, %rdi # file descriptor
mov $msg_start, %rsi # start of data
mov $msg_length, %edx # length of data
syscall # perform syscall
# Close file
mov $3, %rax # syscall 'close'
mov %r10, %rdi # file descriptor
syscall
# Exit program
mov $60, %rax # syscall 'exit'
syscall # perform syscall
.section .rodata
path_start:
.string "greeting.txt\0"
path_end:
path_length = path_end - path_start
msg_start:
.string "Hello World!\n"
msg_end:
msg_length = msg_end - msg_start
Сохраните код в write.s и соберите его с помощью
as -o write.o write.s
ld -o write write.o
а затем запустить с
./write
Надеюсь, все получится.
(Примечание: я не выполняю обработку ошибок. Это всего лишь игрушечный код.)