<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-03-02T18:08:06+01:00</updated><id>/feed.xml</id><title type="html">obrotowy’s blog</title><entry><title type="html">Jak działa system plików Ext2?</title><link href="/filesystems/ext2/2026/03/02/ext.html" rel="alternate" type="text/html" title="Jak działa system plików Ext2?" /><published>2026-03-02T00:00:00+01:00</published><updated>2026-03-02T00:00:00+01:00</updated><id>/filesystems/ext2/2026/03/02/ext</id><content type="html" xml:base="/filesystems/ext2/2026/03/02/ext.html"><![CDATA[<h1 id="wstęp">Wstęp</h1>
<p>Systemy plików z rodziny Ext to najczęściej wykorzystywane systemy plików we wszelakich Linuksach.
W tym artykule opiszę zasadę działania systemu plików Ext2 pokazując jak realizowane są w nim operacje odczytu plików.</p>

<p>Dlaczego skupiam się na Ext2 a nie Ext4? Otóż dlatego, że Ext4 stanowi rozwinięcie Ext2 o mechanizm księgowania (dodany w Ext3), większe limity rozmiarów plików czy dokładniejsze przechowywanie daty utworzenia pliku.
Podstawy działania tych systemów są jednak identyczne.</p>

<h1 id="struktura-partycji-ext">Struktura partycji Ext</h1>
<p>Partycja Ext2 składa się z:</p>
<ul>
  <li>pustego 1KB</li>
  <li>Superbloku (począwszy od offsetu 0x400) - zawierającego podstawowe informacje o systemie plików</li>
  <li>tablicy deskryptora grup bloków (począwszy od offsetu 0x800)</li>
  <li>grupy bloków w których skład wchodzą bloki zawierające:
    <ul>
      <li>mapy bitowe użycia i-węzłów i bloków</li>
      <li>tablice i-węzłów</li>
      <li>dane plików</li>
      <li>mogą także zawierać redundantne kopie superbloku czy tablicy deskryptorów grup bloków</li>
    </ul>
  </li>
</ul>

<p><img src="/partition_scheme.png" alt="Schemat partycji Ext" /></p>

<h2 id="superblok">Superblok</h2>
<p>Superblok zawiera najpotrzebniejsze informacje odnośnie systemu plików.
Choć nie ma potrzeby pamiętać całej jego struktury na pamięć, warto mieć z tyłu głowy nastepujące pola, gdyż przydadzą się w dalszej części tego wpisu:</p>
<ul>
  <li>log2_block_size -&gt; block_size = 1024 « log2_block_size</li>
  <li>blocks_per_group</li>
  <li>inodes_per_group
    <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">superblock_t</span> <span class="p">{</span>
<span class="kt">uint32_t</span> <span class="n">total_inodes</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">total_blocks</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">root_reserved_blocks</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">total_unallocated_blocks</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">total_unallocated_inodes</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">block_no_of_superblock</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">log2_block_size</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">log2_fragment_size</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">blocks_per_group</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">fragments_per_group</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">inodes_per_group</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">last_mount_time</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">last_write_time</span><span class="p">;</span>
<span class="kt">uint16_t</span> <span class="n">mounts_since_last_fsck</span><span class="p">;</span>
<span class="kt">uint16_t</span> <span class="n">fsck_frequency</span><span class="p">;</span> <span class="c1">// In mount times</span>
<span class="kt">uint16_t</span> <span class="n">signature</span> <span class="o">=</span> <span class="mh">0xEF53</span><span class="p">;</span>
<span class="kt">uint16_t</span> <span class="n">fs_state</span><span class="p">;</span>
<span class="kt">uint16_t</span> <span class="n">error_handling</span><span class="p">;</span>
<span class="kt">uint16_t</span> <span class="n">version_minor</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">last_fsck_time</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">os_id</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">version_major</span><span class="p">;</span>
<span class="kt">uint16_t</span> <span class="n">user_id_reserved</span><span class="p">;</span>
<span class="kt">uint16_t</span> <span class="n">group_id_reserved</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Opcjonalnie istnieją też "rozszerzone" pola superbloku, które z premedytacją pominąłem</span>
</code></pre></div>    </div>
  </li>
</ul>

<h2 id="tablica-deskryptorów-grup-bloków-block-group-descriptor-table">Tablica deskryptorów grup bloków (Block Group Descriptor Table)</h2>
<p>Tablica deskryptorów grup bloków zawiera informacje odnośnie poszczególnej grupy bloków, z perspektywy dalszej części wpisu istotne jest pole <code class="language-plaintext highlighter-rouge">inode_table_ptr</code> które wskazuje, gdzie znajduje się tablica i-węzłów wewnątrz opisywanej grupy.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">bgd_t</span> <span class="p">{</span>
  <span class="kt">uint32_t</span> <span class="n">block_usage_bitmap_ptr</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">inode_usage_bitmap_ptr</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">inode_table_ptr</span><span class="p">;</span>
  <span class="kt">uint16_t</span> <span class="n">unallocated_blocks</span><span class="p">;</span>
  <span class="kt">uint16_t</span> <span class="n">unallocated_inodes</span><span class="p">;</span>
  <span class="kt">uint16_t</span> <span class="n">directories_count</span><span class="p">;</span>
  <span class="kt">uint8_t</span> <span class="n">unused</span><span class="p">[</span><span class="mi">14</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="sposób-przechowywania-plików">Sposób przechowywania plików</h1>
<p>O pliku powinniśmy myśleć jako o surowym ciągu bajtów (np. znaków ASCII w przypadku plików tekstowych). Pozostałe informacje o pliku takie jak jego nazwa, rozmiar, uprawnienia czy typ to <strong>metadane o pliku</strong>. W systemach plików Ext metadane o pliku rozrzucone są w 2 miejsca:</p>
<ul>
  <li><strong>Katalog nadrzędny</strong> zawiera informacje nt. nazwy, typu i numer i-węzła opisującego ten plik</li>
  <li><strong>i-węzeł</strong> zawiera m.in. typ, uprawnienia, rozmiar i najważniejsze: adresy bloków z danymi (treścią) tego pliku</li>
</ul>

<h2 id="czym-jest-katalog">Czym jest katalog?</h2>
<p>Intuicyjnie katalog możemy nazwać “zbiorem plików” i jest to całkiem trafna definicja. W systemach plików Ext katalog jest plikiem, którego treścią jest lista plików zawartych w tym folderze.</p>

<p>Choć poniżej przedstawiam całą strukturę wpisu w takiej liście, najważniejszą informacją na ten moment to fakt, że przechowywana jest nazwa oraz i-węzeł opisujący konkretny plik.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">dir_entry_t</span> <span class="p">{</span>
  <span class="kt">uint32_t</span> <span class="n">inode_n</span><span class="p">;</span>     <span class="c1">// Numer i-węzła opisującego plik</span>
  <span class="kt">uint16_t</span> <span class="n">entry_size</span><span class="p">;</span>  <span class="c1">// Rozmiar wpisu wewnątrz listy</span>
  <span class="kt">uint8_t</span> <span class="n">file_name_size</span><span class="p">;</span>
  <span class="kt">uint8_t</span> <span class="n">type_indicator</span><span class="p">;</span>
  <span class="kt">char</span> <span class="n">file_name</span><span class="p">[</span><span class="n">file_name_size</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="czym-jest-i-węzeł">Czym jest i-węzeł?</h2>
<p><strong>i-węzeł</strong> to struktura opisująca plik. Najważniejszą informacją przechowywaną w i-węźle jest położenie treści opisywanego pliku.
Pola <code class="language-plaintext highlighter-rouge">direct_block_ptr</code>, <code class="language-plaintext highlighter-rouge">{singly,doubly,triply}_indirect_block_ptr</code> przechowują numery bloków (względem początku partycji), w którym znajdują się dane pliku.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">inode_t</span> <span class="p">{</span>
  <span class="kt">uint16_t</span> <span class="n">type_and_permissions</span><span class="p">;</span>
  <span class="kt">uint16_t</span> <span class="n">user_id</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">size</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">last_access_time</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">creation_time</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">last_modification_time</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">deletion_time</span><span class="p">;</span>
  <span class="kt">uint16_t</span> <span class="n">group_id</span><span class="p">;</span>
  <span class="kt">uint16_t</span> <span class="n">hard_links_count</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">disk_sectors_used</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">flags</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">os_specific_value</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">direct_block_ptr</span><span class="p">[</span><span class="mi">12</span><span class="p">];</span>
  <span class="kt">uint32_t</span> <span class="n">singly_indirect_block_ptr</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">doubly_indirect_block_ptr</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">triply_indirect_block_ptr</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">generation_number</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">reserved</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
  <span class="kt">uint32_t</span> <span class="n">fragment_address</span><span class="p">;</span>
  <span class="kt">uint32_t</span> <span class="n">os_specific_value_2</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Wskaźniki pośrednie wskazują na blok zawierający liste bloków danych (lub kolejnych list w przypadku wskaźników podwójnie lub potrójnie pośrednich).
<img src="/Ext2-inode.gif" alt="An ext2 inode with indirect and double indirect data blocks visualised by timtjtim" /></p>

<h1 id="praktyka">Praktyka</h1>
<p>Zakładam, że dysponujemy funkcją</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">read_block</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">block_n</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">dst</span><span class="p">);</span>
</code></pre></div></div>
<p>realizującą odczyt pojedyńczego bloku o wskazanym numerze.</p>

<h2 id="odnalezienie-konkretnego-i-węzła">Odnalezienie konkretnego i-węzła</h2>
<p>Jako, że tablica i-węzłów jest rozdzielona między wszystkie grupy bloków, najpierw musimy obliczyć, w której grupie znajduje się interesujący nas i-węzeł. Musimy także pamiętać, że i-węzły numerujemy od 1! Robimy to następującym wyrażeniem:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>block_group = (inode_n - 1) / INODES_PER_GROUP
</code></pre></div></div>
<p>Musimy także znać indeks i-węzła względem tej grupy bloków:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>index_in_block_group = (inode_n - 1) % INODES_PER_GROUP
</code></pre></div></div>

<p>Teraz pozostaje nam policzyć, który blok tablicy musimy odczytać:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>block_index = (index_in_block_group * INODE_SIZE) / BLOCK_SIZE
</code></pre></div></div>

<p>A także indeks naszego i-węzła wewnątrz tego bloku:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>index_in_block = (inode_n - 1) % (BLOCK_SIZE / INODE_SIZE)
</code></pre></div></div>

<p>Mając te wszystkie dane odczytanie i-węzła wygląda następująco:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">inode_t</span> <span class="nf">get_inode</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">inode_n</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">const</span> <span class="kt">uint32_t</span> <span class="n">inodes_per_block</span> <span class="o">=</span> <span class="n">BLOCK_SIZE</span> <span class="o">/</span> <span class="n">INODE_SIZE</span><span class="p">;</span>
  <span class="k">const</span> <span class="kt">uint32_t</span> <span class="n">block_group</span> <span class="o">=</span> <span class="p">(</span><span class="n">inode_n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">/</span> <span class="n">INODES_PER_GROUP</span><span class="p">;</span>
  <span class="k">const</span> <span class="kt">uint32_t</span> <span class="n">index_in_block_group</span> <span class="o">=</span> <span class="p">(</span><span class="n">inode_n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="n">INODES_PER_GROUP</span><span class="p">;</span>
  <span class="k">const</span> <span class="kt">uint32_t</span> <span class="n">block_index</span> <span class="o">=</span> <span class="p">(</span><span class="n">index_in_block_group</span> <span class="o">*</span> <span class="n">INODE_SIZE</span><span class="p">)</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span><span class="p">;</span>
  <span class="k">const</span> <span class="kt">uint32_t</span> <span class="n">index_in_block</span> <span class="o">=</span> <span class="p">(</span><span class="n">inode_n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="p">(</span><span class="n">inodes_per_block</span><span class="p">);</span>

  <span class="n">inode_t</span> <span class="n">inode_table</span><span class="p">[</span><span class="n">inodes_per_block</span><span class="p">];</span>
  <span class="n">read_block</span><span class="p">(</span><span class="n">bgdt</span><span class="p">[</span><span class="n">block_group</span><span class="p">].</span><span class="n">inode_table_ptr</span> <span class="o">+</span> <span class="n">block_index</span><span class="p">,</span> <span class="n">inode_table</span><span class="p">);</span>
  <span class="k">return</span> <span class="n">inode_table</span><span class="p">[</span><span class="n">index_in_block</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="odczytanie-pliku-o-znanym-numerze-i-węzła">Odczytanie pliku o znanym numerze i-węzła</h2>
<p>Wiemy już, jak odczytać i-węzeł znając jego numer.
Teraz wystarczy nam odczytać bloki wskazywane przez ten i-węzeł. Zakładam natomiast, że plik jest na tyle mały, że mieści się w blokach adresowanych bezpośrednio (max rozmiar = 12 bloków &gt;= 12KB).</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">read_file</span><span class="p">(</span><span class="k">const</span> <span class="n">inode_t</span><span class="o">&amp;</span> <span class="n">file</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">output_buf</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">buf_size</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">const</span> <span class="kt">size_t</span><span class="o">&amp;</span> <span class="n">fsize</span> <span class="o">=</span> <span class="n">file</span><span class="p">.</span><span class="n">size</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">fsize</span> <span class="o">&gt;</span> <span class="n">BLOCK_SIZE</span> <span class="o">*</span> <span class="mi">12</span><span class="p">)</span>
    <span class="c1">// Plik nie mieści się wewnątrz bezpośrednio adresowanych bloków</span>
    <span class="n">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">exception</span><span class="p">();</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">fsize</span> <span class="o">&gt;</span> <span class="n">buf_size</span><span class="p">)</span>
    <span class="c1">// Bufor jest mniejszy od pliku</span>
    <span class="n">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">exception</span><span class="p">();</span>
  <span class="kt">int</span> <span class="n">whole_blocks</span> <span class="o">=</span> <span class="n">fsize</span> <span class="o">/</span> <span class="n">BLOCK_SIZE</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">remainder</span> <span class="o">=</span> <span class="n">fsize</span> <span class="o">%</span> <span class="n">BLOCK_SIZE</span><span class="p">;</span>

  <span class="c1">// Odczyt w całości zapełnionych bloków</span>
  <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span><span class="o">&lt;</span><span class="n">whole_blocks</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">read_block</span><span class="p">(</span><span class="n">file</span><span class="p">.</span><span class="n">direct_block_ptr</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">output_buf</span><span class="o">+</span><span class="n">i</span><span class="o">*</span><span class="n">BLOCK_SIZE</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="c1">// Odczyt ostatniego bloku, który nie jest w całości zapełniony</span>
  <span class="kt">char</span> <span class="n">tmp_buf</span><span class="p">[</span><span class="n">BLOCK_SIZE</span><span class="p">];</span>
  <span class="n">read_block</span><span class="p">(</span><span class="n">file</span><span class="p">.</span><span class="n">direct_block_ptr</span><span class="p">[</span><span class="n">whole_blocks</span><span class="p">],</span> <span class="n">tmp_buf</span><span class="p">);</span>
  <span class="n">memcpy</span><span class="p">(</span><span class="n">output_buf</span> <span class="o">+</span> <span class="n">whole_blocks</span><span class="o">*</span><span class="n">BLOCK_SIZE</span><span class="p">,</span> <span class="n">tmp_buf</span><span class="p">,</span> <span class="n">remainder</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="wykorzystując-powyższe-operacje-jesteśmy-w-stanie-zaimplementować-podstawowe-operacje-odczytu-takie-jak-ls-cat-czy-tree-żeby-wylistować-zawartość-katalogu-głównego-ls--musimy-odczytać-plik-opisywany-przez-i-węzeł-o-numerze-2-stąd-możemy-się-dowiedzieć-który-i-węzeł-opisuje-folder---a-więc-możemy-zrealizować-ls-folder-możemy-także-zrealizować-cat-testtxt-i-cat-folderpliktxt">Wykorzystując powyższe operacje, jesteśmy w stanie zaimplementować podstawowe operacje odczytu takie jak <code class="language-plaintext highlighter-rouge">ls</code>, <code class="language-plaintext highlighter-rouge">cat</code> czy <code class="language-plaintext highlighter-rouge">tree</code>. Żeby wylistować zawartość katalogu głównego (<code class="language-plaintext highlighter-rouge">ls /</code>) musimy odczytać plik opisywany przez i-węzeł o numerze 2. Stąd możemy się dowiedzieć, który i-węzeł opisuje <code class="language-plaintext highlighter-rouge">folder</code> - a więc możemy zrealizować <code class="language-plaintext highlighter-rouge">ls /folder</code>. Możemy także zrealizować <code class="language-plaintext highlighter-rouge">cat test.txt</code> i <code class="language-plaintext highlighter-rouge">cat /folder/plik.txt</code>.</h3>]]></content><author><name></name></author><category term="filesystems" /><category term="ext2" /><summary type="html"><![CDATA[Wstęp Systemy plików z rodziny Ext to najczęściej wykorzystywane systemy plików we wszelakich Linuksach. W tym artykule opiszę zasadę działania systemu plików Ext2 pokazując jak realizowane są w nim operacje odczytu plików.]]></summary></entry></feed>