Porównanie systemów plików ext4 i ext2

Mateusz Michałowski

Ext4

System plików ext4 jest następcą ext3. Początek ext4 związany jest z rozszerzeniami pisanymi do ext3, które nie zawsze były od razu wdrażane z uwagi na stabilność. Dla ułatwienia rozwoju nowych pomysłów 28 czerwca 2006 zdecydowano się na stworzenie ext4 - nowego systemu plików bazującego na kodzie ext3. Decyzja o oparciu nowego systemu plików na starym rozwiązaniu budziła wiele wątpliwości, między innymi samego Linusa Torvaldsa:

The whole logic of "code sharing is good" is a huge mistake. Shared code is not at all better than individual code snippets, and often much much worse.

(...)

So two separate filesystems are _less_ to maintain than one big one. Even if there's a lot of code that -could- be shared.

(...)

But I would _serious_ suggest that starting anew with a "new" filesystem, and taking the time to actually also get _rid_ of some of the baggage would quite likely be a good idea.

Just as an example: ext3 _sucks_ in many ways. It has huge inodes that take up way too much space in memory. It has absolutely disgusting code to handle directory reading and writing (buffer heads! In 2006!). It's conditional indexing code is horrible. Its performance absolutely sucks when the journal is being drained or something.

Stabilna wersja ext4 została po raz pierwszy dołączona do jądra 2.6.28 wydanego 25 grudnia 2008.

Kompatybilność

Ext4 jest kompatybilny wstecznie z ext2, jak i ext3. Umożliwia to zamontowanie partycji starego typu jako ext4. Oczywiście mechanizmy wymagające zmienionych struktur danych na dysku (w szczególności ekstenty), nie będą działały - a więc wiele optymalizacji wprowadzonych do ext4 nie będzie dostępnych dla tak zamontowanej partycji.

Możliwa jest także prosta konwersja z ext2 i ext3 do ext4, która pozostawia i-węzły niezmienione (używające starego adresowania bloków). W ten sposób na dysku mogą współistnieć pliki korzystające ze starego adresowania pośredniego bloków, jak i pliki używające nowego mechanizmu ekstentów.
Aby rozpoznać, której metody adresowania używa dany i-węzeł należy sprawdzić bit EXT2_EXTENTS_FL w polu i_flags i-węzła.

Ekstenty

Idea ekstentów

Najważniejszą cechą wyróżniającą ext4 od systemu plików ext2, jak i ext3 jest mechanizm ekstentów, zastępujący adresowanie pośrednie bloków znane z poprzednich wersji systemu plików.

Ext4 zamiast adresować pojedyncze bloki, stara się mapować jak największą porcję danych na ciągły obszar bloków na dysku. Aby uzyskać takie mapowanie ext4 potrzebuje 3 wartości: początkowy blok mapowania w pliku, rozmiar mapowanego obszaru (w blokach) oraz początkowy blok danych zapisanych na dysku. Strukturę przechowującą te wartości nazywamy ekstentem.

struct ext4_extent {
        __le32  ee_block;       /* first logical block extent covers */
        __le16  ee_len;         /* number of blocks covered by extent */
        __le16  ee_start_hi;    /* high 16 bits of physical block */
        __le32  ee_start_lo;    /* low 32 bits of physical block */
};

Zyski osiągnięte dzięki ekstentom

Bloki pliku w systemie ext4 numerowane są za pomocą 32 bitów, co ogranicza ich liczbę do 232 bloków 4-bitowych. Daje to maksymalny rozmiar pliku wielkości 16 TB. W standardowym ext2 plik może mieć maksymalnie 2 GB.

Wielkość woluminu jest z kolei ograniczona przez 48-bitowy identyfikator bloku na dysku. Co daje 1 EB przy wielkości bloku 4 KB. Dla porównania ext2 przy rozmiarze bloku 4 KB oferował maksymalną partycję wielkości 16 TB.

Z kolei wielkość ekstentu ograniczona jest przez 215. Ograniczenie to wynika z podziału na grupy bloków, a pojedyncza grupa bloków może mieć maksymalnie wielkość 128 MB. Z racji tego ograniczenia, ostatni bit 16-bitowego rozmiaru ekstentu może być używany w mechanizmie prealokacji.

Mechanizm ekstentów redukuje rozmiar metadanych, przez co sprawia, że działania na dużych plikach są o wiele szybsze. Plik w o wielkości 500 MB w ext4 używa czterech 12-bajtowych ekstentów, podczas gdy na przechowanie adresów bloków tego samego pliku potrzeba ponad 0.5 MB metadanych w ext2. Przewagę nowego rozwiązania widać zwłaszcza przy operacjach wymagających wielu działań na metadanych (np. usuwanie pliku).

Przechowywanie plików o rozmiarze do 512 MB

Metadane służące do przechowywania informacji o ekstentach znajdują się w i-węźle na miejscu 60 bajtów służących w ext2 do przechowywania adresów bloków (12 bloków bezpośrednich i po jednej pozycji dla bloku pojedynczego, podwójnego i potrójnego pośredniego). W obszarze tym zapisane są 4 ekstenty oraz 1 nagłówek je opisujący (12 bajtów na każdą ze struktur).


/*
 * Each block (leaves and indexes), even inode-stored has header.
 */
struct ext4_extent_header {
        __le16  eh_magic;       /* probably will support different formats */
        __le16  eh_entries;     /* number of valid entries */
        __le16  eh_max;         /* capacity of store in entries */
        __le16  eh_depth;       /* has tree real underlying blocks? */
        __le32  eh_generation;  /* generation of the tree */
};

Przechowywanie plików o rozmiarze powyżej 512 MB

W przypadku większych porcji danych budowane jest drzewo. W tym celu używana jest dodatkowa struktura - indeks, zawierający początkową pozycję ekstentu w pliku oraz numer bloku na danych znajdującego się na dysku. Blok ten zawsze zawiera nagłówek opisujący dane oraz może zawierać dalsze indeksy lub ekstenty z danymi.

 * This is index on-disk structure.
 * It's used at all the levels except the bottom.
 */
struct ext4_extent_idx {
        __le32  ei_block;       /* index covers logical blocks from 'block' */
        __le32  ei_leaf_lo;     /* pointer to the physical block of the next *
                                 * level. leaf or next index could be there */
        __le16  ei_leaf_hi;     /* high 16 bits of physical block */
        __u16   ei_unused;
};

Adresowanie bloków w ext2

struktura i-wezla w ext2

Mapowanie bloków w ext4

struktura i-wezla w ext4

Alokacja

Prealokacja - persistent preallocation

Prealokacja jest mechanizmem, którego pozbawione są standardowe systemy ext2 i ext3. Funkcja fallocate() umożliwia zarezerwowanie określonego obszaru na plik, który początkowo nie wykorzystuje całej przestrzeni. Mechanizm jest szczególnie przydatny, przy pracy z wolno rosnącymi plikami lub plikami, do których zapis nie odbywa się sekwencyjnie (np. P2P). Prealokacja chroni także przed sytuacją braku miejsca na dysku na rozszerzenie pliku oraz w pewnych wypadkach pozwala na zmniejszenie fragmentacji danych. Informacja, że plik jest prealokowany jest zawarta w 16 bicie pola opisującego rozmiar extentu (ee_len).

Jednoczesna alokacja wielu bloków - multiblock allocation

W ext2, a także ext3 każdy blok pliku musiał być oddzielnie zaalokowany, co w przypadku dużych plików skutkowało olbrzymią liczbą wywołań funkcji alokującej. Oprócz problemów z wydajnością, powodowało to większą podatność systemu plików na fragmentację (system plików nie wie czy właśnie alokowany blok będzie częścią większego pliku).

Ext4 dysponuje mechanizmem wieloblokowej alokacji (mballoc), która jest konieczna dla zapewnienia ciągłego obszaru bloków ekstentom. W zależności od wielkości pliku alokator używa różnych strategii - dla małych plików (< 16 bloków) stara się by znajdowały się blisko siebie, co przyspieszy ich odczyt. Z kolei duże pliki alokowane są tak, by znajdowały się w jak najbardziej ciągłym obszarze pamięci. Pozwala to rozwiązać problemy związane z wydajnością i fragmentacją pojawiające się w ext2.

Bez względu na to, którą strategię stosuje alokator w ext4 - w pierwszej kolejności sprawdza czy istnieją wolne prealokowane bloki, dopiero w kolejnym kroku stosuje algorytm bliźniaków.

Użycie tego mechanizmu nie wpływa na format danych przechowywanych na dysku.

Opóźniona alokacja - delayed allocation

Zasada działania

Opóźniona alokacja jest agresywną strategią jak najdłuższego przetrzymywania danych zapisywanych na dysk w pamięci podręcznej.

Większość implementacji związanej z opóźnioną alokacją jest obecnie realizowana w warstwie VFS, z myślą o użyciu jej w rozszerzeniach ext2 i ext3 w przyszłości.

W momencie stworzenia pliku, wyszukiwane są wolne bloki w VFS-ie, które mogą pomieścić bloki danych oraz metadanych. Następnie rezerwuje się je, aby mieć pewność, że w momencie faktycznego zapisu będzie wystarczająco miejsca na dysku.

W kolejnym kroku struktury buffer_head mieszczące zawartość pliku w pamięci są oznaczane jako alokowane z opóźnieniem (BH_DELAY). A w momencie wywołania writepage() lub writepages() na brudnych ramkach pliku wykonywana jest alokacja wieloblokowa, w ciągłym obszarze dyskowym. Po udanej alokacji nieużyte bloki zarezerwowane w pierwszym kroku, mogą zostać zwolnione.

Zalety:
Wady:

Dziennik

Sumy kontrolne dziennika - journal checksumming

Dziennik jest jednym z najintensywniej używanych obszarów dysku, z tego powodu uszkodzenia danych w tym obszarze są szczególnie dotkliwe dla całego systemu plików. Ext4 wprowadza system sum kontrolnych dziennika. Suma kontrolna liczona jest dla każdej transakcji i każdego deskryptora grupy bloków. Z jednej strony odbywa się to kosztem większego narzutu na operacje wejścia/wyjścia, ale z drugiej strony pozwala przekształcić dwie fazy zapisu dziennika znane z ext3 do jednej.

Mechanizm barier

Mechanizm ten pozwala na nakazanie sterownikowi dysku zapisania danych w określonym porządku, co zapobiega rozspójnieniu danych w sytuacjach awaryjnych.

Nanosekundowe znaczniki czasowe

Ext4 wprowadza znaczniki czasowe z dokładnością do nanosekundy. Konsekwencją dodania rozszerzonych znaczników czasowych jest także zwiększenie domyślnego rozmiaru i-węzła ze 128 bitów do 256 bitów.

Nowe znaczniki czasowe przesuwają również problem roku 2038 o kolejne 204 lata.

Dodatkowe pola w i-węźle w stosunku do ext3:

       __le32  i_ctime_extra;  /* extra Change time      (nsec << 2 | epoch) */
       __le32  i_mtime_extra;  /* extra Modification time(nsec << 2 | epoch) */
       __le32  i_atime_extra;  /* extra Access time      (nsec < 2 | epoch) */
       __le32  i_crtime;       /* File Creation time */
       __le32  i_crtime_extra; /* extra FileCreationtime (nsec << 2 | epoch) */
       __le32  i_version_hi;   /* high 32 bits for 64-bit version */

Odnośniki