Как Linux различает реальные и неиспользуемые (например: устройства) файлы?

Это вопрос довольно низкого уровня, и я понимаю, что это может быть не лучшее место, чтобы спросить. Но это выглядело более уместным, чем любой другой сайт SE, поэтому здесь.

Я знаю, что в файловой системе Linux некоторые файлы действительно существуют , например: /usr/bin/bash – это тот, который существует. Однако (насколько я понимаю) некоторые из них фактически не существуют как таковые и являются более виртуальными файлами, например: /dev/sda , /proc/cpuinfo и т. Д. Мои вопросы (они два, но тоже тесно связанные с отдельными вопросами):

  • Как ядро ​​Linux выясняет, являются ли эти файлы реальными (и, следовательно, считывают их с диска) или нет, когда выдается команда чтения (или такая)?
  • Если файл не является реальным: в качестве примера чтение из /dev/random вернет случайные данные, а чтение из /dev/null вернет EOF . Как это работает, какие данные следует читать из этого виртуального файла (и, следовательно, что делать, когда / если данные записываются в виртуальный файл тоже) – есть ли какая-то карта с указателями для разделения команд чтения / записи, подходящих для каждого файла, или даже для самого виртуального каталога? Таким образом, запись для /dev/null может просто вернуть EOF .

Итак, здесь есть два разных типа вещей:

  1. Обычные файловые системы, которые хранят файлы в каталогах с данными и метаданными, знакомым образом (включая софт-ссылки, жесткие ссылки и т. Д.). Они часто, но не всегда, поддерживаются блочным устройством для постоянного хранения (tmpfs живет только в ОЗУ, но в остальном идентичен нормальной файловой системе). Семантика их знакома; читать, писать, переименовывать и т. д., все работают так, как вы ожидаете.
  2. Виртуальные файловые системы различного типа. /proc и /sys являются примерами здесь, также как и пользовательские файловые системы FUSE, такие как sshfs или ifuse . В них гораздо больше разнообразия, потому что на самом деле они просто ссылаются на файловую систему с семантикой, которая в некотором смысле «обычна». Таким образом, когда вы читаете файл под /proc , вы фактически не получаете доступ к определенному фрагменту данных, который был сохранен чем-то другим, записывающим его раньше, как в обычной файловой системе. Вы по сути делаете вызов ядра, запрашивая некоторую информацию, которая генерируется «на лету». И этот код может делать все, что ему нравится, так как это просто некоторая функция, реализующая семантику read . Таким образом, у вас есть странное поведение файлов в /proc , например, например, притворяясь символьными ссылками, когда они на самом деле не являются.

Ключ в том, что /dev на самом деле, как правило, один из первого рода. В современных дистрибутивах нормально, что /dev является чем-то вроде tmpfs, но в старых системах было нормально, что он был простым каталогом на диске без каких-либо специальных атрибутов. Ключ в том, что файлы под /dev являются узлами устройств, типом специального файла, подобного FIFO или Unix-сокетам; узел устройства имеет основное и второстепенное число, а чтение или запись их выполняет вызов драйвера ядра, так же как чтение или запись FIFO вызывает ядро ​​для буферизации вывода в трубе. Этот драйвер может делать все, что захочет, но обычно это как-то касается аппаратного обеспечения, например, для доступа к жесткому диску или воспроизведения звука в динамиках.

Чтобы ответить на исходные вопросы:

  1. Есть два вопроса, связанных с тем, существует ли «файл» или нет; это то, является ли файл узла устройства буквально существующим, и поддерживает ли его код ядра. Первый разрешается так же, как что-либо в обычной файловой системе. Современные системы используют udev или что-то в этом роде, чтобы наблюдать за аппаратными событиями и автоматически создавать и уничтожать узлы устройства под /dev соответственно. Но более старые системы, или легкие пользовательские сборки, могут просто иметь все свои узлы устройства буквально на диске, созданные заблаговременно. Между тем, когда вы читаете эти файлы, вы делаете вызов кода ядра, который определяется основными и второстепенными номерами устройств; если они не являются разумными (например, вы пытаетесь прочитать блок-устройство, которого не существует), вы получите некоторую ошибку ввода-вывода.

  2. Как это работает, какой код ядра вызывается для какого файла устройства. Для виртуальных файловых систем, таких как /proc , они реализуют свои собственные функции read и write ; ядро просто вызывает этот код в зависимости от того, в какой точке монтирования он находится, и реализация файловой системы заботится обо всем остальном. Для файлов устройств он отправляется на основе основных и младших номеров устройств.

Вот список файлов /dev/sda1 на моем почти обновленном сервере Arch Linux:

 % ls -li /dev/sda1 1294 brw-rw---- 1 root disk 8, 1 Nov 9 13:26 /dev/sda1 

Таким образом, запись в каталоге /dev/ для sda имеет номер inode, 1294. Это реальный файл на диске.

Посмотрите, где обычно отображается размер файла. Вместо этого появляется «8, 1». Это номер основного и младшего устройства. Также обратите внимание на «b» в разрешениях файла.

Файл /usr/include/ext2fs/ext2_fs.h содержит эту структуру (фрагмент) C:

 /* * Structure of an inode on the disk */ struct ext2_inode { __u16 i_mode; /* File mode */ 

Эта структура показывает нам структуру диска на диске на диске. В этой структуре много интересного; взгляните на него долго.

Элемент i_mode struct ext2_inode имеет 16 бит, и он использует только 9 для прав пользователя / группы / другого, разрешения на чтение / запись / выполнение и еще 3 для setuid, setgid и sticky. У него есть 4 бита, чтобы различать такие типы, как «простой файл», «ссылка», «каталог», «именованный канал», «семейный сокет Unix» и «блочное устройство».

Ядро Linux может следовать обычным алгоритма поиска каталогов, а затем принимать решение на основе разрешений и флагов в элементе i_mode . Для «b», блокировать файлы устройств, он может найти основные и младшие номера устройств и традиционно использовать основной номер устройства для поиска указателя на некоторую функцию ядра (драйвер устройства), которая имеет дело с дисками. Номер младшего устройства обычно используется, как говорят, номер устройства шины SCSI или номер устройства EIDE или что-то в этом роде.

Некоторые другие решения о том, как обращаться с файлом типа /proc/cpuinfo создаются на основе типа файловой системы. Если вы выполните:

 % mount | grep proc proc on /proc type proc (rw,nosuid,nodev,noexec,relatime) 

вы можете видеть, что /proc имеет тип файловой системы «proc». Чтение из файла в /proc приводит к тому, что ядро ​​выполняет что-то другое в зависимости от типа файловой системы, так же как открытие файла в файловой системе ReiserFS или DOS приведет к тому, что ядро ​​будет использовать разные функции для поиска файлов и поиска данных файлов.

В конце концов, они все файлы для Unix, это красота абстракции.

То, как файлы обрабатываются ядром, теперь это разная история.

/ proc и nowadays / dev и / run (aka / var / run) являются виртуальными файловыми системами в ОЗУ. / proc – это интерфейс / окна для переменных и структур ядра.

Я рекомендую прочитать «Ядро Linux» http://tldp.org/LDP/tlk/tlk.html и драйверы устройств Linux, третье издание https://lwn.net/Kernel/LDD3/ .

Мне также понравился дизайн и реализация операционной системы FreeBSD http://www.amazon.com/Design-Implementation-FreeBSD-Operating-System/dp/0321968972/ref=sr_1_1

Посмотрите соответствующую страницу, относящуюся к вашему вопросу.

http://www.tldp.org/LDP/tlk/dd/drivers.html

В дополнение к ответам @ RuiFRibeiro и @ BruceEdiger, различие, которое вы делаете, не является тем отличием, которое делает ядро. На самом деле у вас есть разные файлы: обычные файлы, каталоги, символические ссылки, устройства, сокеты (и я всегда забываю несколько, поэтому я не буду пытаться составить полный список). Вы можете получить информацию о типе файла с ls : это первый символ в строке. Например:

 $ls -la /dev/sda brw-rw---- 1 root disk 8, 0 17 nov. 08:29 /dev/sda 

«B» в самом начале сигнализирует, что этот файл является блочным устройством. Тире, означает обычный файл, 'l' символическую ссылку и так далее. Эта информация хранится в метаданных файла и доступна, например, через системный вызов stat , поэтому ядро ​​может читать по-другому файл и символическую ссылку, например.

Затем вы делаете другое различие между «реальными файлами», такими как /bin/bash и «виртуальными файлами», такими как /proc/cpuinfo но ls сообщает как обычные файлы, так что разница имеет другой вид:

 ls -la /proc/cpuinfo /bin/bash -rwxr-xr-x 1 root root 829792 24 août 10:58 /bin/bash -r--r--r-- 1 root wheel 0 20 nov. 16:50 /proc/cpuinfo 

Случается, что они принадлежат к разным файловым системам. /proc является точкой монтирования procfs псевдо-файловой системы, тогда как /bin/bash находится на обычной файловой системе диска. Когда Linux открывает файл (он делает это по-разному в зависимости от файловой системы), он заполняет file структуры данных, который имеет среди других атрибутов структуру нескольких указателей на функции, которые описывают, как использовать этот файл. Таким образом, он может реализовывать различные типы поведения для файлов разных типов.

Например, это операции, объявленные /proc/meminfo :

 static int meminfo_proc_open(struct inode *inode, struct file *file) { return single_open(file, meminfo_proc_show, NULL); } static const struct file_operations meminfo_proc_fops = { .open = meminfo_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; 

Если вы посмотрите на определение meminfo_proc_open , вы увидите, что эта функция заполняет буфер в памяти информацией, возвращаемой функцией meminfo_proc_show , задачей которой является сбор данных об использовании памяти. Затем эту информацию можно прочитать в обычном режиме. Каждый раз, когда вы открываете файл, meminfo_proc_open функция meminfo_proc_open и обновляется информация о памяти.

Все файлы в файловой системе являются «реальными» в том смысле, что они позволяют осуществлять ввод / вывод файлов. Когда вы открываете файл, ядро ​​создает файловый дескриптор, который является объектом (в смысле объектно-ориентированного программирования), который действует как файл. Если вы читаете файл, дескриптор файла выполняет свой метод чтения, который, в свою очередь, будет запрашивать файловую систему (sysfs, ext4, nfs и т. Д.) Для данных из файла. Файловые системы представляют собой единый интерфейс для пользовательского пространства и знают, что делать, чтобы обрабатывать чтения и записи. Файловые системы в свою очередь просят другие слои обрабатывать свои запросы. Для обычного файла, скажем, файловой системы ext4, это будет включать поиск в структурах данных файловой системы (что может включать чтение диска) и, в конечном счете, чтение с диска (или кеша) для копирования данных в буфер чтения. Для файла в sysfs, как правило, просто sprintf () что-то в буфер. Для узла блока dev он попросит драйвер диска прочитать некоторые блоки и скопировать их в буфер (основные и младшие номера указывают файловой системе, какой драйвер должен делать запросы).