あなたが混乱している理由がわかります。この図は少しわかりにくく、実際には正しくない場合があります。
まず、カーネルがページレベルより下のメモリアロケータを必要とする理由について考えてみましょう。これはおそらくすでにご存じのことでしょうが、完全を期すために説明します。
ページは、メモリ操作の典型的な「単位」です。ユーザースペースアプリケーションがメモリを割り当てるか、ファイルなどのメモリマップを行うと、通常、マシンのページサイズの倍数になります。いくつかの例外があります。Windowsは、CPUのページサイズに関係なく、仮想メモリ割り当てユニットとして64kを使用します。それでも、このように考えましょう。
最近のCPUでは、ユーザー空間コードに関する限り、フラットなアドレス空間があります。これは実際には仮想メモリシステムによって提供される錯覚です。OSは、RAM内の任意の場所(スワップメモリまたはメモリマップファイルの場合はRAM内にない可能性があります)のページを提供し、それらを連続した仮想アドレス空間にマップします。
これらすべての要点は、オペレーティングシステム自体のいくつかの特別なケース(おそらくDMAバッファー、ブート時にセットアップされるいくつかの特別なデータ構造、ああそしてカーネルイメージ自体)を除いて、オペレーティングシステムのカーネルはおそらくページより大きいRAMのブロックを管理します。これは、ページに関する限り、すべての割り当てと割り当て解除が同じサイズであることを意味するため、非常に単純化されます。また、マクロレベルで外部の断片化を効果的に排除します。
ただし、カーネルは独自のデータ構造を実装する必要もあります。そのためには、異なる種類のメモリアロケータが必要です。これらのデータ構造は通常、個々のオブジェクトのコレクションと考えることができます(たとえば、オブジェクトは「スレッド」または「ミューテックス」である場合があります)。これらのオブジェクトのサイズは、通常、1ページのサイズよりはるかに小さくなります。
したがって、たとえば、プロセスのセキュリティ資格を表すオブジェクト(たとえば、POSIXのユーザーIDとグループIDを考えると)は16バイト程度しかありませんが、「プロセス」または「スレッド」は最大でサイズは1kb。明らかに、これらの小さなレコードにページ全体を使用したくないので、ページの上にアロケータを実装するのがアイデアです。
下位レベルの割り当てシステムは、ページレベルのアロケーターと同じ問題の多くを満たす必要があります。それは、適度に高速でなければならず(マルチコアシステムを含む)、断片化を最小限に抑えたいなどです。しかし、より重要なのは、格納するデータ構造の種類に応じて、調整可能で構成可能であることです。
一部のデータ構造は本質的に「キャッシュのような」ものです。たとえば、多くのオペレーティングシステムは、ファイルシステムオブジェクトへのパス名のキャッシュを維持して、ディレクトリルックアップの長いチェーンを回避します(Unix語では「名前キャッシュ」または「nameiキャッシュ」と呼ばれます)。これらのオブジェクトは正確さではなくパフォーマンスのためにのみ必要であるため、メモリが不足していてページフレームをすばやく解放する必要がある場合は、(理論的には)エントリでいっぱいのページ全体を忘れることがあります。
他のデータ構造は、メモリが不足していてすぐには必要ない場合に、ディスクにスワップできます。しかし、スワッピングや仮想メモリシステムを制御するデータ構造でそれをしたくないのです!
一部のデータ構造はペナルティなしでメモリ内を移動できるため(たとえば、誰もポインタで参照しない場合)、必要に応じて断片化を回避するために自分自身を「圧縮」できます。
したがって、スラブアロケータの主な考え方は、ページには同じ「タイプ」のデータ構造のみを格納する必要があるということです。これにより、すべてのボックスがチェックされます。ページ内の各オブジェクトは同じサイズであるため、外部の断片化はありません。同じ「タイプ」のオブジェクトは、同じパフォーマンス要件と同じセマンティクスを持っています。
ちなみに、割り振りについても同様です。一部のタイプのオブジェクトでは、そのオブジェクトを割り当てるためにすぐに利用できるメモリがない場合は、おそらく待機しても問題ありません。開いているファイルを表すオブジェクトがその一例です。ファイルを開くことは、多くの場合、負荷の高い操作であるため、もう少し待つことはそれほど害にはなりません。
他の種類のオブジェクト(たとえば、これから一定の時間内に発生しなければならないリアルタイムイベントを表すオブジェクト)の場合、待機する必要はありません。そのため、いくつかのタイプのオブジェクトが過剰に割り当てられる(たとえば、予備の空きページがいくつかある)ことは理にかなっており、リクエストを待機せずに満たすことができます。
基本的に、オブジェクトの種類ごとに独自のアロケータを持たせることができます。アロケータは、そのオブジェクトのニーズに合わせて構成できます。これらのオブジェクトごとのアロケータは、混乱して「キャッシュ」と呼ばれます。オブジェクトのタイプごとに1つのキャッシュを割り当てます。(はい、通常は「キャッシュのキャッシュ」も実装します。)各キャッシュには、同じタイプのオブジェクトのみが格納されます(たとえば、スレッド構造のみ、またはアドレス空間構造のみ)。
次に、各キャッシュが「スラブ」を管理します。スラブは、同じタイプのオブジェクトの配列を含むページフレームです。スラブは、「完全」(使用中のすべてのオブジェクト)、「空」(使用中のオブジェクトなし)、または「部分的」(一部の使用中のオブジェクト)の場合があります。
スラブアロケーターはすべての部分スラブの空きリストを維持するため、部分スラブはおそらく最も興味深いものです。(完全なスラブと空のスラブにはフリーリストは必要ありません。)オブジェクトは最初に部分スラブから(そしておそらく「最も完全な」部分スラブから最初に)割り当てられ、不要なページの割り当てを回避しようとします。
スラブ割り当ての良い点は、これらの割り当てポリシーオプション(およびメモリセマンティクス)のすべてを、オブジェクトの種類ごとに調整できることです。キャッシュには、空のスラブのプールを保持するものと、保持しないものがあります。セカンダリストレージにスワップできるものとできないものがあります。
Linuxには、コンパクト性、キャッシュの使いやすさ、または生の速度が必要かどうかに応じて、3種類の異なるスラブアロケーターがあります。数年前にこれに関する良いプレゼンテーションがあり、トレードオフをよく説明しています。
Solarisスラブアロケータ(詳細については、ペーパーを参照してください)には、パフォーマンスをさらに高めるための詳細がいくつかあります。まず、Solarisでは、ページフレームの割り当てを含め、すべてがスラブの割り当てで行われます。(これは、サイズがページの半分より大きいオブジェクトを割り当てるためのSolarisのソリューションです。)これは、スラブ割り当てスペースにスラブアロケータをネストすることにより、小さなオブジェクトを管理します。
Solarisの一部のオブジェクトは、複雑で高価な構築と破棄を必要とするため(たとえば、カーネルロックを持つオブジェクト)、「部分的に解放」される(つまり、構築されるが割り当てられない)可能性があります。Solarisはまた、CPUごとにフリーリストを維持することにより、フリースラブの割り当てを最適化し、一部の操作が完全に待機しないようにします。
汎用的な割り当てをサポートするため(たとえば、コンパイル時にサイズがわからない配列の場合)、ほとんどのマクロカーネルタイプのオペレーティングシステムには、オブジェクトタイプではなくオブジェクトサイズを表すキャッシュもあります。たとえば、FreeBSDは、サイズが2バイトの累乗である4〜256の不明なオブジェクトのキャッシュを維持します。
スラブの割り当ては、さまざまな種類のデータのニーズに合わせて調整できる非常に柔軟なフレームワークであることがわかります。ページングと競合しませんが、ページングを補完します(Solarisでは、ページフレームはスラブで割り当てられます)。
これがお役に立てば幸いです。何か説明が必要な場合はお知らせください。