← вся теориятеория к собесу · Linux и bash

Файловая система Linux: inode и ссылки

Имя файла — это ссылка на данные, а не сами данные. Замер: файл на 300 мегабайт поднял занятое место с 28751 до 29051 мегабайта. Удаляем его через rm, пока процесс держит файл открытым: du по каталогу показывает 1 мегабайт, а df по разделу всё те же 29051, ни мегабайта не вернулось. Место освободилось только когда процесс остановили. Данные живут, пока на них есть ссылка или открытый дескриптор.

разбор писал Даниил, автор Сеньорчика

Файловая система: имена, ссылки, место

Замер на живом разделе. Создал файл на 300 мегабайт - занятое место выросло с 28751 до 29051 мегабайта. Оставил процесс с открытым файлом и удалил файл командой rm. После удаления du по каталогу показывает 1 мегабайт, а df по разделу - те же 29051, ни мегабайта не вернулось. Как только я остановил процесс, стало 28751.

Стержень: имя - это ссылка на файл, а не сам файл; данные живут, пока на них есть ссылка или открытый дескриптор. И место на разделе состоит из двух независимых ресурсов.

// Формулировки: «чем жёсткая ссылка отличается от символической?», «df показывает полный диск, du - нет», «место есть, а файл не создаётся»

Имя, номер файла и две ссылки

Файл в Linux устроен так: есть запись с метаданными и указателями на блоки данных, у неё свой номер, а имя в каталоге - всего лишь ссылка на этот номер. Проверил командой stat: у файла original.txt номер 4484904 и счётчик ссылок 2, у сделанной на него жёсткой ссылки hard.txt - тот же номер 4484904 и тот же счётчик. Это буквально одно и то же, просто под двумя именами.

Символическая ссылка устроена иначе: это отдельный файл со своим номером (у меня 4484905), внутри которого лежит текст пути. Размер у неё 12 байт - ровно столько букв в слове original.txt.

// Дальше я удалил original.txt. Через жёсткую ссылку данные читаются как ни в чём не бывало, счётчик ссылок стал 1. Через символическую - ошибка «нет такого файла»: путь, на который она указывала, исчез. Отсюда и разница в применении: символическая переживает переезд на другой раздел и умеет указывать на каталог, а жёсткая не умеет ни того, ни другого, зато держит данные живыми.

номер файла (inode)
запись с метаданными и указателями на блоки; имя - лишь ссылка на неё
счётчик ссылок
сколько имён указывает на файл; блоки освобождаются при нуле

Удалили файл, а место не вернулось

Вернусь к замеру из начала - это самый частый ночной инцидент. Блоки данных освобождаются при двух условиях сразу: имён на файл не осталось И его не держит открытым ни один процесс. Дескриптор - это открытая ссылка процесса на файл, и она привязана к самому файлу, а не к имени.

Поэтому картина выходит такая: логи «почистили» командой rm, имя из дерева исчезло - и du, который ходит по именам, показывает пусто. А сервис держит дескриптор и продолжает писать в файл, у которого больше нет имени: df, который считает занятые блоки, показывает переполнение. Расхождение этих двух команд и есть диагноз.

// Найти виновника просто. В списке открытых файлов процесса есть пометка о удалении - у меня было видно строку вида 3 -> /data/big.log (deleted). Лечится перезапуском сервиса или командой переоткрыть лог, но никак не повторным rm. В моём замере после остановки процесса место вернулось ровно те 300 мегабайт, что он держал.

дескриптор
открытая ссылка процесса на файл; держит блоки занятыми даже без имени
df против du
занятые блоки раздела против суммы по именам; расхождение - удалённый открытый файл

Байты и записи о файлах - разные ресурсы

Второй замер, и он удивляет всех. Сделал раздел на 64 мегабайта, но всего с 40 записями о файлах. Создал 39 пустых файлов - на сороковом система ответила «No space left on device», то есть «место кончилось». А df по месту в этот момент показывает: занято 0 из 64 мегабайт, свободно всё. И только df по записям объясняет: занято 40 из 40.

Дело в том, что у файловой системы два независимых ресурса: блоки для данных и таблица записей о файлах, по одной на файл. На распространённой файловой системе ext4 их число фиксируется при создании раздела и потом не растёт. Миллионы мелких файлов - кэши, сессии, письма - выедают таблицу, и запись падает с ошибкой «нет места» при свободных гигабайтах.

// Поэтому при любом «нет места» проверяют оба ресурса, а не один. Лечение - удалить мелочь или пересоздать раздел с другим соотношением. На файловой системе XFS записи выделяются по мере надобности, поэтому там в это упираются реже. И заодно держите в голове права: маска новых файлов обычно такая, что файлы создаются с правами 644, каталоги 755; особый бит на общем каталоге запрещает удалять чужие файлы.

таблица записей о файлах
по одной записи на файл; на ext4 её размер фиксирован при создании раздела
«нет места» при свободном диске
кончились записи о файлах, а не байты - проверяется отдельной командой

Как отвечать: «df показывает переполнение, du - нет. Куда делось место?»

Почти наверняка это удалённый файл, который держит открытым живой процесс. Имени в дереве уже нет, поэтому du его не видит - он ходит по именам. А блоки заняты, и df это честно показывает, потому что считает занятое на разделе. Я это воспроизводил: создал файл на триста мегабайт, оставил процесс с открытым дескриптором, удалил файл - du по каталогу показал один мегабайт, а df не сдвинулся ни на мегабайт. Как только процесс остановили, ровно триста мегабайт вернулись. Типичный сценарий в проде - логи удалили командой rm, а сервис продолжает в них писать. Ищу виновника по списку открытых файлов, там у таких записей стоит пометка об удалении, дальше перезапускаю сервис или заставляю его переоткрыть лог. И обязательно проверяю второй ресурс - записи о файлах: бывает, что место кончилось не в байтах, а в них, и тогда df по месту показывает свободный диск.

Ответ объясняет механику через два разных счётчика, подкрепляет её собственным замером и добавляет вторую частую причину.

На чём валятся

  • Пытаются вернуть место повторным rm, хотя файл держит открытый дескриптор процесса.
  • Ищут причину «нет места» только в байтах: на моём разделе было свободно 64 МБ и ноль записей о файлах.
  • Считают символическую ссылку копией данных - после удаления цели остаётся битая ссылка.
  • Делают жёсткую ссылку на каталог или через границу разделов и удивляются отказу.

Проверь себя

Пять вопросов из банка по этой подтеме. Всего их 8, остальные разбираются в тренажёре.

  1. #dvo_linux_fs1 / 5
    df показывает раздел забитым, а du по всем каталогам даёт вдвое меньше. Куда делось место?
    A)du не считает скрытые файлы и каталоги с точкой
    B)Место держит удалённый файл, открытый живым процессом
    C)df приплюсовывает зарезервированные под root проценты
    D)Кончились inode, и df показывает это как занятое место
    показать ответ и разбор
    +B)Место держит удалённый файл, открытый живым процессом

    // разбор: Пока процесс держит открытым дескриптор, ядро не освобождает блоки удалённого файла: имени в дереве нет (du не видит), а место занято (df видит). Классика с логами, которые «почистили» через rm, пока сервис пишет в тот же fd. Ищут через lsof +L1 или ls -l /proc/PID/fd | grep deleted, лечат перезапуском сервиса либо переоткрытием файла.

  2. #dvo_linux_fs2 / 5
    df -h показывает 60% занято, но touch падает с No space left on device. Что проверишь первым?
    A)Квоту пользователя на этом разделе
    B)Резерв под root на файловой системе
    C)Свободные inode через df -i
    D)Права на каталог и контекст SELinux
    показать ответ и разбор
    +C)Свободные inode через df -i

    // разбор: Место на ФС — два независимых ресурса: блоки данных и inode, по одному на файл. Миллионы мелких файлов (сессии, кэш, очереди) выедают таблицу inode при свободных байтах, и запись падает с ENOSPC. На ext4 число inode задаётся при mkfs и на живой ФС не растёт: чистят мелочь или пересоздают раздел с другим соотношением.

  3. #dvo_linux_fs3 / 5
    У пользователя есть права rw на файл, но rm выдаёт Permission denied. Файл лежит в чужом каталоге. Почему?
    A)На файле стоит атрибут immutable, выставленный через chattr +i
    B)Удаление имени зависит от прав на КАТАЛОГ (w+x), а не на файл
    C)Файл в этот момент держит открытым какой-то другой процесс
    D)Чужие файлы обычному пользователю не отдают, нужен root
    показать ответ и разбор
    +B)Удаление имени зависит от прав на КАТАЛОГ (w+x), а не на файл

    // разбор: Имя файла хранится в каталоге, поэтому создание, удаление и переименование — это запись в КАТАЛОГ, и для них нужны права w+x на каталог, а не на файл. Права на файл (rw) управляют его содержимым, но не тем, можно ли отвязать имя. Отсюда же смысл липкого бита на /tmp: там всем rwx, но t-бит запрещает удалять чужие файлы.

  4. #dvo_linux_fs4 / 5
    ls -lh показывает файл на 10G, а du -h на нём — 20M. Диск не переполнен. Что это за файл?
    A)Файл прозрачно сжимается на лету самой файловой системой
    B)ls врёт из-за кэша метаданных, реальный размер и есть 20M
    C)Разреженный файл: записаны только блоки с данными, дыры места не занимают
    D)Это символическая ссылка на большой файл в другом месте
    показать ответ и разбор
    +C)Разреженный файл: записаны только блоки с данными, дыры места не занимают

    // разбор: Разреженный файл (sparse) хранит только реально записанные блоки, а «дыры» между ними места не занимают — читаются как нули. Отсюда расхождение: ls -l показывает логический размер (до последнего байта), du — физически занятые блоки. Так устроены образы дисков, qcow2, файлы БД. Осторожно при копировании: без поддержки sparse (cp --sparse=always, tar -S) дыры «раздуются» в реальные нули.

  5. #dvo_linux_fs5 / 5
    df показывает 100% занято, приложение падает с No space left, но из-под root touch в том же разделе проходит. Почему?
    A)В ext4 по умолчанию 5% блоков зарезервировано под root
    B)root на самом деле пишет в tmpfs в памяти, а не на этот диск
    C)У приложения включена дисковая квота, которой нет у root
    D)root пишет мимо файловой системы прямо в блочное устройство
    показать ответ и разбор
    +A)В ext4 по умолчанию 5% блоков зарезервировано под root

    // разбор: ext2/3/4 по умолчанию резервируют 5% блоков под uid 0 — чтобы при переполнении не легли системные сервисы и root мог разгрести. Поэтому обычный процесс упирается в «диск полон» на ~95%, а root ещё пишет. На больших разделах под данные эти 5% — десятки гигабайт впустую, их уменьшают через tune2fs -m 1. Если и root не может писать — тогда уже реально ноль байт или кончились inode.

дальше

Теорию прочитал. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.