Почему Linux использует getdents() для каталогов вместо read()?

Я просматривал K&R C и заметил, что для чтения записей в каталогах они использовали:

while (read(dp->fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf))
    /* code */

куда dirbuf была системная структура каталогов, и dp->fd допустимый дескриптор файла. В моей системе dirbuf был бы struct linux_dirent, Обратите внимание, что struct linux_dirent имеет гибкий элемент массива для имени записи, но для простоты предположим, что это не так. (Работа с гибким элементом массива в этом сценарии потребует лишь немного дополнительного стандартного кода).

Linux, однако, не поддерживает эту конструкцию. Когда используешь read() попытаться прочитать записи каталога, как указано выше, read() возвращается -1 а также errno установлен в EISDIR,

Вместо этого Linux выделяет системный вызов специально для чтения каталогов, а именно getdents() системный вызов. Тем не менее, я заметил, что это работает почти так же, как и выше.

while (syscall(SYS_getdents, fd, &dirbuf, sizeof(dirbuf)) != -1)
    /* code */

Что было разумным за этим? Там, кажется, мало / нет выгоды по сравнению с использованием read() как сделано в K&R.

3 ответа

Решение

getdents вернусь struct linux_dirent, Это будет сделано для любого базового типа файловой системы. Формат "на диске" может быть совершенно другим, известным только данному драйверу файловой системы, поэтому простой вызов чтения из пользовательского пространства может не сработать. То есть, getdents может конвертировать из нативного формата для заполнения linux_dirent,

нельзя ли сказать то же самое о чтении байтов из файла с read()? Формат данных на диске на диске не обязательно должен быть одинаковым для всех файловых систем или даже смежным на диске - таким образом, чтение последовательности байтов с диска снова будет чем-то, что я ожидаю делегировать драйверу файловой системы.

Непрерывные данные файла обрабатываются уровнем VFS ["виртуальная файловая система"]. Независимо от того, как FS выбирает организацию списка блоков для файла (например, ext4 использует узлы "inodes": "index" или "information". Они используют организацию "ISAM" ("метод последовательного доступа к индексу")). MS/DOS FS может иметь совершенно другую организацию).

Каждый драйвер FS регистрирует таблицу обратных вызовов функций VFS при запуске. Для данной операции (например, open/close/read/write/seek) есть соответствующая запись в таблице.

Уровень VFS (т. Е. Из системного вызова userpace) будет "вызывать" драйвер FS, и драйвер FS будет выполнять операцию, делая все, что сочтет необходимым для выполнения запроса.

Я предполагаю, что драйвер FS будет знать о расположении данных внутри обычного файла на диске - даже если данные были фрагментированы.

Да. Например, если запрос на чтение состоит в том, чтобы прочитать первые три блока из файла (например, 0,1,2), ФС будет искать информацию индексации для файла и получит список физических блоков для чтения (например, 1000000,200,37) с поверхности диска. Все это прозрачно обрабатывается в драйвере FS.

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

Возможно, более уместно называть это передачей данных инода, поскольку для файлов существуют иноды (то есть индекс имеет информацию индексации для "разброса / сбора" блоков FS для файла). Но драйвер FS также использует это для чтения из каталога. Таким образом, у каждого каталога есть индекс для отслеживания информации индексации для этого каталога.

Таким образом, для драйвера FS каталог очень похож на плоский файл со специально отформатированной информацией. Это каталог "Записи". Это то, что getdents возвращается. Это "сидит на вершине" индексного слоя инода.

Записи каталога могут иметь переменную длину [в зависимости от длины имени файла]. Итак, формат на диске будет (назовите его "Тип A"):

static part|variable length name
static part|variable length name
...

Но... некоторые ФС организуются по-другому (назовите это "Тип B"):

<static1>,<static2>...
<variable1>,<variable2>,...

Таким образом, организация типа A может быть прочитана атомарно read(2) вызов, тип B будет иметь трудности. Итак getdents Вызов VFS обрабатывает это.

Может ли VFS также представить представление каталога "linux_dirent", как VFS представляет "плоское представление" файла?

Что это getdents для.

С другой стороны, я предполагаю, что драйвер FS знает тип каждого файла и, таким образом, может вернуть linux_dirent, когда read () вызывается для каталога, а не для последовательности байтов.

getdents не всегда существовал. Когда директивы были фиксированного размера и был только один формат FS, readdir(3) звонок наверное сделал read(2) внизу и получил серию байтов [что только то, что read(2) обеспечивает]. На самом деле, IIRC, в начале был только readdir(2) а также getdents а также readdir(3) не существует.

Но что вы делаете, если read(2) является "коротким" (например, два байта слишком малы)? Как вы сообщаете это приложению?

Мой вопрос больше похож на то, что драйвер FS может определить, является ли файл каталогом или обычным файлом (и я предполагаю, что это возможно), и поскольку он должен перехватывать все вызовы read (), почему не читается () в каталоге, реализованном как чтение linux_dirent?

read на реж. не перехватывается и преобразуется в getdents потому что ОС минималистская. Он ожидает, что вы узнаете разницу и сделаете соответствующий системный вызов.

Ты сделаешь open(2) для файлов или папок [opendir(3) это обертка и делает open(2) под]. Вы можете читать / писать / искать файлы и искать / получать для директоров.

Но... делаю read для возвращения EISDIR, [Примечание: я забыл это в моих первоначальных комментариях]. В простой модели "плоских данных", которую она предоставляет, нет способа передать / контролировать все getdents может / делает.

Таким образом, вместо того, чтобы позволить неполноценному способу получить частичную / неправильную информацию, ядру и разработчику приложения проще пройти getdents интерфейс.

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

getdents представит атомный вид. Либо файл существует, либо его нет. Это было переименовано или нет. Таким образом, вы не получите "полу модифицированное" представление, независимо от того, сколько "суматохи" происходит вокруг вас. Когда вы спрашиваете getdents за 20 записей вы получите их [или 10, если их будет так много].

Примечание: полезный трюк состоит в том, чтобы "переопределить" количество. То есть скажи getdents Вы хотите 50000 записей [вы должны предоставить место]. Обычно вы получаете около 100 или около того. Но теперь у вас есть атомарный моментальный снимок для полного каталога. Я иногда делаю это вместо зацикливания со счетом 1 -YMMV. Вы по-прежнему должны защищать от немедленного исчезновения, но, по крайней мере, вы можете видеть его (т.е. последующее открытие файла не удается)

Таким образом, вы всегда получаете "целые" записи и нет записи для только что удаленного файла. Это не означает, что файл все еще там, просто он был там во время getdents, Другой процесс может мгновенно стереть его, но не в середине getdents

Если read(2) Если быбыло разрешено, вам нужно было бы угадать, сколько данных нужно прочитать, и не знали бы, какие записи были полностью сформированы в частичном состоянии. Если ФС имела организацию типа B выше, одно чтение не могло бы атомарно получить статическую часть и переменную часть за один шаг.

Было бы философски неправильно тормозить read(2) сделать что getdents делает.

getdents, unlink, creat, rmdir, а также rename (и т. д.) операции блокируются и сериализуются для предотвращения любых несоответствий [не говоря уже о повреждении FS или утечке / потере блоков FS]. Другими словами, все эти системные вызовы "знают друг о друге".

Если pgmA переименовывает "x" в "z", а pgmB переименовывает "y" в "z", они не сталкиваются. Один идет первым, а другой - вторым, но блоки ФС никогда не теряются / не протекают. getdents получает весь вид (будь то "x y", "y z", "x z" или "z"), но он никогда не увидит "x y z" одновременно.

В K&R (на самом деле, Unix через SVr2, по крайней мере, возможно, SVr3), записи каталога составляли 16 байтов, используя 2 байта для inode и 14 байтов для имен файлов.

С помощью read имело смысл, потому что записи каталога на диске были одинакового размера. 16 байтов (степень 2) также имеют смысл, поскольку для вычисления смещений не требуется аппаратное умножение. (Я помню, как кто-то говорил мне в 1978 году, что драйвер диска Unix использовал плавающую точку и работал медленно... но это из вторых рук, хотя и забавно)

Более поздние улучшения в каталогах позволили использовать более длинные имена, что означало, что размеры различались (не было никакого смысла делать огромные записи такими же, как максимально возможное имя). Был предоставлен более новый интерфейс, readdir,

Linux предоставляет интерфейс более низкого уровня. Согласно его странице руководства:

Это не те интерфейсы, которые вас интересуют. Посмотрите на readdir(3) для интерфейса C-библиотеки, соответствующей POSIX. На этой странице описаны интерфейсы системных вызовов голого ядра.

Как показано в вашем примере, getdents системный вызов, полезный для реализации readdir, Манера, в которой readdir реализовано не определено. Нет особой причины, по которой readdir (около 30 лет назад) не могли быть реализованы как библиотечная функция с использованием read а также malloc и аналогичные функции для управления длинными именами файлов, прочитанными из каталога.

Перемещение функций в ядро ​​было сделано (вероятно) в этом случае для повышения производительности. Так как getdents читает несколько записей каталога за раз (в отличие от readdir), что может снизить накладные расходы на чтение всех записей для небольшого каталога (за счет уменьшения количества системных вызовов).

Дальнейшее чтение:

Ваше подозрение верно: было бы более целесообразно, чтобы системный вызов read работал с каталогами и возвращал некоторые стандартизированные данные, а не имел отдельный системный вызов getdents. getdents является лишним и снижает единообразие интерфейса. Другие ответы утверждают, что "чтение" как интерфейс будет в некотором роде уступать "гетентам". Они неверны. Как вы можете заметить, аргументы и возвращаемое значение "read" и "getdents" идентичны; просто "read" работает только с не-каталогами, а "getdents" работает только с каталогами. "getdents" можно легко сложить в "read", чтобы получить единый системный вызов.

Причина, по которой это не так, является исторической. Первоначально "read" работал с каталогами, но возвращал фактическую необработанную запись каталога в файловой системе. Это было сложно проанализировать, поэтому в дополнение к чтению был добавлен вызов getdirents, чтобы обеспечить независимое от файловой системы представление записей каталога. В конце концов, "чтение" по каталогам было отключено. "читать" по каталогам также можно было бы заставить вести себя идентично с getdirents вместо того, чтобы быть выключенным. Это просто не было, возможно, потому что это казалось дублирующим.

В частности, в Linux "чтение" возвращало ошибку при чтении каталогов так долго, что почти наверняка какая-то программа полагается на такое поведение. Таким образом, обратная совместимость требует, чтобы "чтение" в Linux никогда не работало с каталогами.

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