Есть ли в Linux системный слой/скрипт, который управляет открытием файлов?

Есть ли в Linux системный слой/скрипт, который управляет открытием файлов?

Есть ли в Linux слой/скрипт, который обрабатывает запросы программ на открытие файлов?

Например, когда вы открываете дескриптор файла в bash: exec 3 <>/documents/foo.txtили ваш текстовый редактор открывает/documents/foo.txt

Я не могу поверить, что редактор может «просто открыть файл» для чтения/записи самостоятельно.

Я скорее представляю себе это как запрос к "слою" (скрипт init.d?), который изначально может открывать только определенное количество файлов и ведет учет открытых файлов с указанием их типов доступа, процессов, с помощью которых они открываются и т. д.

решение1

Этот уровень находится внутри ядра в Linux и других системах, которые не слишком отходят от исторической архитектуры Unix (а также в большинстве не-Unix операционных систем).

Эта часть ядра называетсяУровень VFS (виртуальной файловой системы). Роль VFS заключается в управлении информацией об открытых файлах (соответствие междудескрипторы файлов,открыть описания файлови записи каталога), для анализа путей к файлам (интерпретация /, .и ..) и для отправки операций над записями каталога соответствующему драйверу файловой системы.

Большинство драйверов файловых систем также находятся в ядре, ноПРЕДОХРАНИТЕЛЬДрайвер файловой системы позволяет делегировать эту функциональность за пределы ядра. Операции файловой системы также могут включать код пользовательского пространства, если это делает хранилище более низкого уровня, например, если файловая система диска находится напетлевое устройство.

решение2

Открытие файлов в Linux осуществляется непосредственно ядром.Но есть несколько вещей, которые вы можете сделать, чтобы повлиять на этот процесс и изучить его.


Схема стека хранения 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

Короче говоря, вот что происходит, когда программа записывает данные в файл.

  1. Программа запрашивает у ядра openфайл, указанный по пути, для записи.
  2. Ядро устанавливает некоторые внутренние структуры и делегирует часть задачи открытия файла драйверу, специфичному для типа файловой системы. Затем ядро ​​возвращает дескриптор файла, который является просто целым числом (например, 3), программе.
  3. Программа запрашивает у ядра writeпоследовательность байтов (например, строку) для файла, на который ссылается файловый дескриптор.
  4. Ядро снова делегирует работу драйверу.
  5. Шаги 3 и 4, вероятно, повторяются несколько раз.
  6. Программа запрашивает у ядра доступ к closeфайлу, на который ссылается файловый дескриптор.
  7. Ядро снова делегирует работу драйверу, а затем уничтожает внутренние структуры.

Вот довольно минималистичная ассемблерная программа, которая записывает «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

Надеюсь, все получится.

(Примечание: я не выполняю обработку ошибок. Это всего лишь игрушечный код.)

Связанный контент