журналируемая ФС по умолчанию — extent-деревья вместо блочных указателей и JBD2 против «полного fsck после отключения питания»the journaling default — extent trees instead of block pointers, and JBD2 against "full fsck after a power cut"

ext4: структуры на диске и алгоритмы работыext4: on-disk structures and working algorithms

ext4 — третье поколение семейства ext, унаследовавшее от ext3 журналирование, но решившее две его главные проблемы: адресацию файла через списки блочных указателей (медленно и лимитировано для больших файлов) и обязательный полный fsck после сбоя на больших разделах. Extent-деревья описывают целые диапазоны смежных блоков одной записью, а checksums журнала позволяют безопасно откатить только незавершённые транзакции вместо сканирования всего раздела.ext4 is the third generation of the ext family, inheriting journaling from ext3 but fixing its two biggest problems: addressing a file through lists of block pointers (slow and limited for large files) and a mandatory full fsck after a crash on large partitions. Extent trees describe entire ranges of contiguous blocks with a single entry, and journal checksums allow safely rolling back only incomplete transactions instead of scanning the whole partition.

Что этоWhat it is

ext4 расширяет ext3, добавляя extent-адресацию, поддержку разделов до 1 ЭиБ (эксбибайт) и файлов до 16 ТиБ, отложенное выделение блоков (delayed allocation) для лучшей группировки данных на диске, чексуммы журнала и метаданных, а также наносекундные временные метки. Формат совместим «в одну сторону»: раздел ext4 можно смонтировать драйвером ext3/ext2 при отключённых новых возможностях, но не наоборот.ext4 extends ext3 by adding extent-based addressing, support for partitions up to 1 EiB (exbibyte) and files up to 16 TiB, delayed block allocation for better on-disk data grouping, journal and metadata checksums, and nanosecond timestamps. The format is compatible "one way": an ext4 partition can be mounted by an ext3/ext2 driver with the new features disabled, but not the other way around.

параметрparameterext2ext3ext4
журналированиеjournaling—JBDJBD2 (+ checksums транзакций)(+ transaction checksums)
адресация блоковblock addressingпрямые/косвенные указателиdirect/indirect pointersпрямые/косвенные указателиdirect/indirect pointersextent-деревьяextent trees
макс. размер файлаmax file size2 ТиБTiB2 ТиБTiB16 ТиБTiB
макс. размер томаmax volume size32 ТиБTiB32 ТиБTiB1 ЭиБEiB
выделение блоковblock allocationнемедленноеimmediateнемедленноеimmediateотложенное (delalloc)delayed (delalloc)
временные меткиtimestampsсекундыsecondsсекундыsecondsнаносекундыnanoseconds

Как это устроено внутриHow it's built internally

Весь раздел ext4 разбит на block group — фиксированные по размеру диапазоны блоков, каждый со своей копией служебных структур:An entire ext4 partition is split into block groups — fixed-size ranges of blocks, each with its own copy of the bookkeeping structures:

/dev/sdX (раздел ext4) group 0 group 1 group 2 ... group 0 — содержимое superblock (копия) group descriptor table (копия) block bitmap inode bitmap inode table фикс. кол-во inode data blocks файлы, каталоги, extent-деревья
Каждая block group хранит bitmap занятости блоков, bitmap занятости inode-ов, фиксированную по размеру таблицу inode-ов и сами данные. Суперблок и таблица дескрипторов групп физически дублируются в нескольких группах — с flex_bg резервные копии размещаются реже, чем в каждой группе, но принцип избыточности сохраняется.Every block group holds a block-occupancy bitmap, an inode-occupancy bitmap, a fixed-size inode table, and the actual data. The superblock and group descriptor table are physically duplicated across several groups — with flex_bg, backups sit less frequently than in every single group, but the redundancy principle holds.

Каждый inode адресует свои данные не списком отдельных блоков (как в ext2/3), а деревом extent — записей вида «начиная с логического блока N, здесь идут M физически смежных блоков»:Each inode addresses its data not with a list of individual blocks (as in ext2/3) but with an extent tree — entries of the form "starting at logical block N, here come M physically contiguous blocks":

inode.i_block[0..3] 60 байт: ext4_extent_header + до 4 extent-записей (корень дерева, depth=0) маленький файл ext4_extent ee_block: логич. блок ee_len: длина диапазона ee_start: физич. блок большой / фрагментированный файл ext4_extent_idx указывает на дочерний блок (depth > 0, до 5 уровней) дочерний блок 4 КиБ: заголовок + до 340 ext4_extent-записей по 12 байт каждая
Корень дерева всегда лежит внутри самого inode, без отдельного блока на диске — для файлов, умещающихся в 4 extent-записи (обычно означает не более 4 фрагментов на диске), больше ничего не требуется. Фрагментированные или очень большие файлы наращивают дерево вглубь (до 5 уровней), где каждый дочерний блок в 4 КиБ вмещает до 340 extent-записей по 12 байт.The tree root always lives inside the inode itself, with no separate on-disk block — for files fitting into 4 extent entries (usually meaning no more than 4 fragments on disk), nothing else is needed. Fragmented or very large files grow the tree deeper (up to 5 levels), where each 4 KiB child block holds up to 340 extent entries of 12 bytes each.

Практическое следствие: один extent описывает произвольно длинный диапазон смежных блоков одной записью, а не одним указателем на блок, как в ext2/3. Файл в 128 МиБ, лежащий одним смежным куском, адресуется одной extent-записью вместо тысяч отдельных блочных указателей — отсюда меньше метаданных, меньше обращений к диску при последовательном чтении и меньше глубина дерева для типичных файлов.The practical consequence: one extent describes an arbitrarily long range of contiguous blocks with a single entry, not one pointer per block as in ext2/3. A 128 MiB file laid out as one contiguous chunk is addressed by a single extent entry instead of thousands of individual block pointers — hence less metadata, fewer disk accesses on sequential reads, and shallower trees for typical files.

Журнал JBD2 защищает не данные файлов по умолчанию, а метаданные — от того, чтобы сбой питания посреди многошаговой операции (например, создание файла: выделить inode + записать запись каталога + обновить bitmap) не оставил файловую систему в противоречивом состоянии:The JBD2 journal protects not file data by default but metadata — so that a power loss mid-way through a multi-step operation (say, creating a file: allocate an inode + write a directory entry + update the bitmap) doesn't leave the filesystem in a contradictory state:

режим (data=)mode (data=)что журналируетсяwhat's journaledкомпромиссtrade-off
ordered (по умолчанию)(default)только метаданные; данные гарантированно пишутся на диск раньше, чем метаданные, которые на них ссылаютсяmetadata only; data is guaranteed to be written to disk before the metadata referencing itбаланс: после сбоя не будет мусора в новых файлах на месте старых данных, но сами данные не журналируютсяa balance: no garbage from old data will appear in new files after a crash, but the data itself isn't journaled
journalи метаданные, и данные — всё проходит через журналboth metadata and data — everything goes through the journalсамый безопасный режим, но каждый блок данных фактически пишется на диск дваждыthe safest mode, but every data block is effectively written to disk twice
writebackтолько метаданные; данные пишутся без гарантии порядка относительно метаданныхmetadata only; data is written with no ordering guarantee relative to metadataбыстрее ordered, но после сбоя новый файл иногда может показать старые данные другого файлаfaster than ordered, but after a crash a new file can sometimes expose another file's old data

При монтировании после аварийного завершения ядро (а не e2fsck) само проигрывает журнал: сканирует записи транзакций, проверяет их checksum, и останавливается на первой повреждённой или неполной — все транзакции до этой точки применяются, всё после отбрасывается. Именно поэтому обычный ребут после сбоя занимает секунды, а не проход по всему разделу: полный e2fsck нужен только тогда, когда сам журнал повреждён или файловая система помечена как «грязная» без валидного журнала для восстановления.On mount after an unclean shutdown, the kernel (not e2fsck) replays the journal itself: it scans transaction records, verifies their checksums, and stops at the first corrupted or incomplete one — everything before that point is applied, everything after is discarded. That's why an ordinary reboot after a crash takes seconds rather than a full partition scan: a full e2fsck is only needed when the journal itself is corrupted or the filesystem is marked dirty with no valid journal to recover from.

Extent-дерево, описанное выше, адресует данные файла — но каталог в ext4 тоже файл, и поиск имени внутри него устроен отдельным механизмом. По умолчанию (фича dir_index) содержимое каталога хранится как htree — хэш-дерево, спрятанное внутри обычных блоков каталога так, что старый линейный код (ext2) видит те же блоки как «пустые» и просто их пропускает:The extent tree described above addresses a file's data — but a directory in ext4 is also a file, and looking up a name inside it works through a separate mechanism. By default (the dir_index feature) a directory's contents are stored as an htree — a hash tree hidden inside ordinary directory blocks so that old linear (ext2) code sees the same blocks as "empty" and simply skips them:

линейный каталог (мало файлов) "." ".." "passwd" "shadow" ... поиск = просмотр всех записей подряд, пока не найдено совпадение имени htree-каталог (dir_index, много файлов) dx_root hash → block map hash("passwd") dx_node (опционально, 2-й уровень) leaf block линейный список записей с этим хэшем поиск = вычислить хэш имени один раз, дойти до листа за 1-2 хопа, там уже линейно сравнить совпадения по хэшу старый линейный код ext2 видит блоки dx_root/dx_node как "пустые" (запись на inode 0) и просто их пропускает — поэтому htree обратно совместим на чтение без модификации формата каталога
Линейный каталог требует O(n) сравнений имён при поиске — приемлемо для десятков файлов, плохо для десятков тысяч (частый случай: почтовые maildir, кэш-директории). htree превращает поиск в вычисление одного хэша плюс переход по 1-2 уровням дерева, с сохранением полной обратной совместимости на уровне формата.A linear directory requires O(n) name comparisons per lookup — fine for dozens of files, bad for tens of thousands (a common case: mail maildirs, cache directories). htree turns a lookup into computing one hash plus walking 1-2 tree levels, while keeping full backward compatibility at the format level.

Поиск имени в htree-каталоге вычисляет хэш искомого имени и использует его как ключ — по сути тот же алгоритм, что и поиск в B-дереве, только ключом служит не само имя, а его хэш. При коллизии хэшей (разные имена с одинаковым хэшем) код продолжает сканировать соседние листовые блоки в порядке дерева, пока не переберёт все записи с совпадающим хэшем.Looking up a name in an htree directory computes the hash of the sought name and uses it as a key — essentially the same algorithm as a B-tree lookup, except the key is the name's hash rather than the name itself. On a hash collision (different names with the same hash), the code keeps scanning adjacent leaf blocks in tree order until it's checked every entry with a matching hash.

Поле i_block в inode, описанное выше как «корень extent-дерева», на самом деле переиспользуется под разные вещи в зависимости от флагов самого inode — i_flags определяет, как именно интерпретировать эти 60 байт:The i_block field in the inode, described above as "the extent tree root", is actually repurposed for different things depending on the inode's own flags — i_flags determines exactly how to interpret those 60 bytes:

i_flags битbitзначениеmeaningэффектeffect
EXT4_EXTENTS_FLданные адресуются extent-деревомdata is addressed via an extent treeстандартный режим для ext4; без этого флага inode интерпретируется по старой ext2/3 схеме блочных указателейthe standard ext4 mode; without this flag the inode is interpreted using the old ext2/3 block-pointer scheme
EXT4_INLINE_DATA_FLданные файла лежат прямо внутри inode, без отдельного блокаthe file's data lives directly inside the inode, with no separate blockдля очень маленьких файлов (символические ссылки, файлы в пару десятков байт) экономит целый блок на диске и одно обращение к немуfor very small files (symlinks, files a few dozen bytes long) saves an entire disk block and one access to it
EXT4_IMMUTABLE_FLфайл нельзя изменять, даже root-у, без снятия флагаthe file can't be modified, even by root, without clearing the flagвыставляется/снимается через chattr +i/-i; используется для защиты системных бинарников и конфигов от случайной или вредоносной перезаписиset/cleared via chattr +i/-i; used to protect system binaries and configs from accidental or malicious overwriting
EXT4_APPEND_FLзапись возможна только в конец файлаwrites are only allowed at the end of the filechattr +a — типичное применение: log-файлы, куда можно дописывать, но нельзя редактировать или обрезать уже записанноеchattr +a — a typical use: log files that can be appended to but not edited or truncated
EXT4_ENCRYPT_FLсодержимое и имя файла зашифрованы через fscryptthe file's contents and name are encrypted via fscryptвыставляется автоматически при создании файла внутри каталога с активной fscrypt-политикой шифрованияset automatically when a file is created inside a directory with an active fscrypt encryption policy

Наносекундные временные метки, заявленные в таблице выше как преимущество ext4 над ext2/3, физически не помещаются в оригинальные 32-битные поля времени, унаследованные от ext2 — недостающие биты досеканд хранятся в отдельных дополнительных полях расширенного inode (i_extra_isize), появившихся вместе с увеличением размера inode со 128 до 256 байт. Это тот случай, где новая возможность формально описывается одним пунктом в списке фич, а на деле требует расширения самой структуры на диске, а не только новой логики поверх старой.The nanosecond timestamps claimed above as an ext4 advantage over ext2/3 don't physically fit in the original 32-bit time fields inherited from ext2 — the missing sub-second bits live in separate extra fields of the extended inode (i_extra_isize), introduced alongside the inode size growing from 128 to 256 bytes. This is a case where a new capability is formally a single line in a feature list, but actually requires extending the on-disk struct itself, not just new logic layered on top of the old one.

Помимо базового набора, у ext4 есть менее часто включаемые фичи, каждая из которых меняет что-то в структурах выше, а не просто добавляет опцию монтирования:Beyond the base feature set, ext4 has less commonly enabled features, each of which changes something in the structures above rather than just adding a mount option:

фичаfeatureчто делаетwhat it doesкомпромиссtrade-off
bigallocкаждый бит в bitmap блоков адресует не один блок, а целый кластер (степень двойки блоков) — тот же bitmap покрывает намного больший объём данныхeach bit in the block bitmap addresses not a single block but a whole cluster (a power-of-two number of blocks) — the same bitmap covers a much larger amount of dataбыстрее выделение и меньше фрагментация для очень больших файлов, но минимальная единица выделения растёт — маленькие файлы расходуют место менее эффективно; фича долго оставалась экспериментальнойfaster allocation and less fragmentation for very large files, but the minimum allocation unit grows — small files waste space less efficiently; the feature stayed experimental for a long time
fscryptшифрование содержимого и имён файлов на уровне отдельного каталога, разными ключами для разных каталогов — без отдельного блочного слоя вроде dm-cryptencrypts file contents and filenames at the level of an individual directory, with different keys for different directories — with no separate block layer like dm-cryptметаданные inode (владелец, размер, время) остаются незашифрованными; гранулярность на уровне каталога, а не всего раздела, — основа шифрования профилей в Androidinode metadata (owner, size, timestamps) stays unencrypted; granularity is per-directory, not whole-partition — the basis for profile encryption on Android
casefoldрегистронезависимое сравнение имён внутри отдельно помеченного каталога (флаг +F), при этом само имя на диске хранится в исходном регистреcase-insensitive name comparison inside an individually flagged directory (the +F flag), while the on-disk name itself keeps its original caseнужна для совместимости с приложениями, ожидающими регистронезависимую ФС (Wine, часть Android); совместно с fscrypt заработала не сразу — первые версии не поддерживали шифрование и casefold на одном каталоге одновременноneeded for compatibility with applications expecting a case-insensitive filesystem (Wine, parts of Android); combining it with fscrypt didn't work from day one — early versions didn't support encryption and casefold on the same directory at once

Как это видно на практикеHow it's visible in practice

командаcommandчто происходитwhat happens
mkfs.ext4 -O ^has_journal /dev/sdX1создаёт ext4-раздел без журнала (де-факто ext2 с extent-адресацией) — используется для одноразовых/временных разделов, где журналирование только замедляет записьcreates an ext4 partition with no journal (effectively ext2 with extent addressing) — used for throwaway/temporary partitions where journaling only slows writes down
tune2fs -l /dev/sdX1выводит содержимое суперблока: включённые фичи (extent, 64bit, metadata_csum), число inode, дату последней проверки — прямой взгляд на структуру без монтированияdumps the superblock contents: enabled features (extent, 64bit, metadata_csum), inode count, last check date — a direct look at the structure without mounting
filefrag -v file.binпоказывает реальную extent-карту файла: сколько физических фрагментов и на каких блоках — практическая демонстрация того, во что превращается «непрерывный» файл после многократной записи и фрагментацииshows a file's actual extent map: how many physical fragments and at which blocks — a practical demonstration of what a "contiguous" file turns into after repeated writes and fragmentation
mount -o data=journal /dev/sdX1 /mntпереключает режим журналирования данных на самый безопасный (и самый медленный) — обычно используется на разделах с критичными к целостности данными вроже баз данных, где двойная запись оправданаswitches the data journaling mode to the safest (and slowest) — typically used on partitions with data-integrity-critical workloads like databases, where the double write is worth it
dumpe2fs -h /dev/sdX1заголовок суперблока плюс список group descriptor — видно, сколько block group физически создано и где лежат резервные копии суперблокаthe superblock header plus the group descriptor list — shows how many block groups physically exist and where the superblock's backup copies sit

Грабли и мифыGotchas & myths

ordered mode не журналирует данные — только их порядок записиordered mode doesn't journal data — only the write order

Стандартный режим data=ordered защищает от одной конкретной проблемы (утечки чужих старых данных в новый файл через несинхронизированные метаданные), но не даёт полной гарантии сохранности только что записанных данных при сбое — если приложению критична гарантия «данные точно на диске», нужен явный fsync(), а не только надежда на журнал.the standard data=ordered mode protects against one specific problem (another file's stale data leaking into a new file via unsynced metadata), but doesn't fully guarantee just-written data survives a crash — if an application needs the guarantee "the data is definitely on disk," it needs an explicit fsync(), not just faith in the journal.

extent-дерево не отменяет фрагментацию, просто описывает её компактнееthe extent tree doesn't eliminate fragmentation, it just describes it more compactly

Extent-адресация решает проблему представления смежных блоков одной записью, но если файл дописывается много раз вперемешку с другими файлами, extent-ов становится много и дерево растёт вглубь — filefrag на «старом» часто дописываемом файле легко покажет десятки фрагментов даже на ext4.extent-based addressing solves the problem of representing contiguous blocks with a single entry, but if a file is repeatedly appended to interleaved with other files, extents multiply and the tree grows deeper — filefrag on an "old", frequently appended file can easily show dozens of fragments even on ext4.

journal checksums — не то же самое, что checksums данныхjournal checksums aren't the same as data checksums

Функция metadata_csum защищает метаданные (inode, дескрипторы групп, каталоги) и записи журнала от повреждения — но ext4, в отличие от Btrfs или ZFS, не хранит сквозные чексуммы содержимого обычных файлов: тихое повреждение бита в середине файла данных ext4 сам по себе не обнаружит.the metadata_csum feature protects metadata (inodes, group descriptors, directories) and journal records from corruption — but unlike Btrfs or ZFS, ext4 doesn't keep end-to-end checksums of ordinary file contents: a silent bit flip in the middle of a data file won't be detected by ext4 itself.

ТерминыTerms

block group
фиксированный по размеру диапазон блоков раздела ext4 со своей копией bitmap-ов, таблицы inode и (не всегда) резервной копией суперблока — единица, вокруг которой строится вся структура на диске.a fixed-size range of blocks within an ext4 partition, with its own copy of the bitmaps, inode table, and (not always) a superblock backup — the unit around which the whole on-disk structure is built.
extent
запись вида «начиная с логического блока N — M физически смежных блоков», описывающая целый диапазон одной структурой вместо отдельного указателя на каждый блок.an entry of the form "starting at logical block N — M physically contiguous blocks", describing an entire range with one struct instead of a pointer per block.
JBD2 (Journaling Block Device 2)
слой журналирования, используемый ext4 (и рядом других файловых систем) — записывает транзакции метаданных до их применения на основное место на диске, чтобы после сбоя можно было безопасно откатить незавершённые.the journaling layer used by ext4 (and several other filesystems) — records metadata transactions before they're applied at their real on-disk location, so incomplete ones can be safely rolled back after a crash.
delayed allocation (delalloc)
отложенное до момента реальной записи на диск выделение физических блоков под данные — позволяет ядру группировать выделение по большему объёму данных и уменьшать фрагментацию по сравнению с немедленным выделением при каждой write().postponing the physical block allocation for data until it's actually flushed to disk — lets the kernel batch allocation over a larger amount of data and reduce fragmentation compared to allocating immediately on every write().
flex_bg
фича, группирующая bitmap-ы и таблицы inode нескольких соседних block group вместе физически на диске — снижает число seek-ов при операциях, затрагивающих метаданные многих групп разом.a feature grouping the bitmaps and inode tables of several adjacent block groups together physically on disk — reduces the number of seeks for operations touching many groups' metadata at once.
metadata_csum
фича, добавляющая контрольные суммы к структурам метаданных (group descriptor, inode, записи каталога) — обнаруживает повреждение метаданных, не затрагивая содержимое обычных файлов.a feature adding checksums to metadata structures (group descriptors, inodes, directory entries) — detects metadata corruption without covering ordinary file contents.
htree / dir_index
хэш-дерево для поиска имени файла внутри большого каталога за O(log n) вместо линейного перебора; спрятано внутри обычных блоков каталога так, что старый линейный код воспринимает их как «пустые» и полностью обратно совместимо на чтение.a hash tree for finding a filename inside a large directory in O(log n) instead of a linear scan; hidden inside ordinary directory blocks so old linear code sees them as "empty" — fully backward-compatible for reading.
inline_data
режим, при котором содержимое очень маленького файла хранится прямо внутри полей inode, без выделения отдельного блока на диске — экономит блок и одно обращение к диску для файлов в несколько десятков байт.a mode where a very small file's contents live directly in the inode's fields, with no separate disk block allocated — saves a block and one disk access for files a few dozen bytes long.
bigalloc
режим выделения блоков кластерами (степень двойки блоков за один бит bitmap-а) вместо поблочного — уменьшает фрагментацию для очень больших файлов ценой более грубой минимальной единицы выделения.a block allocation mode using clusters (a power-of-two number of blocks per bitmap bit) instead of per-block — reduces fragmentation for very large files at the cost of a coarser minimum allocation unit.
fscrypt
шифрование на уровне файловой системы, гранулярностью в отдельный каталог, без отдельного блочного слоя — метаданные inode остаются незашифрованными, шифруются только содержимое и имена файлов.filesystem-level encryption granular to an individual directory, with no separate block layer — inode metadata stays unencrypted, only file contents and names are encrypted.