blog

Linuxシステムノート メモリー管理

物理メモリは常に連続したページ単位のブロックで構成されていると考えてください。物理ページには0から始まる番号を付けることができるので、各物理ページにはページ番号が付きます。物理アドレスは連続しているの...

Oct 22, 2020 · 6 min. read
シェア

物理メモリ管理

排他的メモリ空間の原理

メモリはすべて番号のついたチャンクに分割されています。このアドレスは、物理的なメモリーの位置を示す実際のアドレスです。各コンピュータの物理アドレスはプロセスからは見えず、誰もこの物理アドレスに直接アクセスすることはできません。オペレーティング・システムはプロセスに仮想アドレスを割り当てます。すべてのプロセスはこのアドレスを同じものとして見ており、中のメモリは0から番号が振られています。

プログラム内部で、命令が書き込まれるアドレスは仮想アドレスです。例えば、10Mという位置のメモリ領域があった場合、オペレーティングシステムは異なるプロセスの仮想アドレスを異なるメモリの物理アドレスにマッピングする仕組みを提供します。プログラムが仮想アドレスにアクセスしたいときは、カーネルのデータ構造を別の物理アドレスに変換することで、異なるプロセスが実行されたときに異なる物理アドレスに書き込まれるようにし、競合が起きないようにしています。

仮想アドレス空間の計画

メモリ管理システムは少なくとも3つのことをしなければなりません:

  1. 仮想メモリ空間は、各プロセスが独立した、干渉しない仮想アドレス空間を見るように管理されます;
  2. 物理メモリ管理、メモリアクセスには、ユーザーランドのプロセスは仮想アドレスを使用し、カーネルランドも基本的に仮想アドレスを使用します;
  3. メモリマッピング、仮想メモリと物理メモリのマッピングと関連付けの必要性

仮想アドレスは2つあり、その一部はカーネルスペースと呼ばれるカーネルに使用され、一部はユーザースペースと呼ばれるプロセスに使用されます。ユーザー空間は一番下のロー・アドレスにあり、カーネル空間は一番上のハイ・アドレスにあります。

  1. ヒープセグメント

    ヒープはより高いアドレスに成長し、動的にメモリを割り当てるために使われる領域で、mallocが割り当てられる場所です。
  2. メモリマッピングセグメント

    バイナリの実行ファイルがダイナミック・リンク・ライブラリに依存している場合、このアドレスを使ってファイルをメモリにマッピングします。
  3. スタック・アドレス・セグメント

    メインスレッドの関数呼び出しに使用される関数スタックです。

通常のプロセスがさらにカーネル空間にアクセスしたい場合、その方法はありません。より高位の特権作業を行う必要がある場合は、システムコールを呼び出してカーネルに入る必要があります。カーネルの中に入ると、別の視点があります。先ほどまでは、空間全体が自分のもので、他のプロセスは存在しないと考えていた普通のプロセスの視点でした。もちろん、他のプロセスも同じように考えています。つまり、プロセスによって0号室から29号室にあるものは違うということです。

しかし、カーネルに入ると、どのプロセスから入ってきても、同じカーネル空間と同じプロセスのリストが表示されます。カーネルスタックは個別に使用されますが、知ろうと思えば、各プロセスのカーネルスタックがどこにあるかを知ることができます。そのため、パブリックなデータ構造にアクセスしたい場合は、保護をロックする必要があります。言い換えれば、異なるプロセスがカーネルに入るとき、彼らは同じ部屋であるルーム30から39に入ります。カーネルコードは多くの権限を持っているが、唯一のカーネル空間内の仮想アドレスの範囲を使用することができます、つまり、カーネルコードは、カーネルのデータ構造にアクセスするには、ほとんどの場合、仮想アドレスを使用しています。使用できるのは30番から39番までで、0番から29番までは使用できません。そしてたくさんのプロセスがあります。あなたは今カーネルの中にいますが、現在どのプロセス番号0を参照しているのかわかりません。

カーネル内部には、カーネルコード、テキストセグメント、データセグメント、BSSSegmentもあり、カーネルブートについて話すときには、カーネルコードもELFフォーマットであることを忘れないでください。

セグメンテーション・メカニズム

セグメンテーションメカニズムにおける仮想アドレスは、セグメントセレクタとセグメントオフセットの2 つの部分から構成されます。セグメントセレクタは、先ほど説明したセグメントレジスタに格納されます。セグメントセレクタの最も重要な部分はセグメント番号で、セグメントテーブルのインデックスとし て使用されます。セグメントテーブ ルは、セグメントのベースアドレス、セグメント境界、特権レベルを保持します。仮想アドレスのセグメント内オフセットは、0 とセグメント境界の間になければなりません。セグメント内オフセットが正当であれば、セグメントベースアドレスにセグメント内オフセットを加算して物理メモリアドレスを取得します。

たとえば、上の仮想空間は、0 から 3 までの番号の、次の 4 つのセグメントに分割されます。各セグメントにはセグメント・テーブルのエントリーがあり、物理空間では下図の右側のように配置されます。セグメント2のオフセット600の仮想アドレスにアクセスしたい場合、物理アドレスは、セグメント2のベースアドレス2000+オフセット600=2600と計算できます。

Linuxでは、セグメント・テーブルはセグメント記述子テーブルと呼ばれ、グローバル記述子テーブルGDTに配置されます。

Linuxでは、ページングと呼ばれる、仮想アドレスから物理アドレスへの別の変換方法を採用しています。

物理メモリについては、オペレーティング・システムが同じサイズのページに分割するため、管理が容易です。 たとえば、あるメモリ・ページが長期間使用されない場合、スワップアウトと呼ばれる方法で一時的にハードディスクに書き出すことができます。それが必要になったら、スワップ・インと呼ばれる方法で再びロードすることができます。これにより、利用可能な物理メモリのサイズを拡大し、物理メモリの利用率を向上させることができます。

このスワップイン・アウトはページ単位で行われます。このテーブルには、各ページの開始アドレスとページ内のオフセットが格納され、メモリ内の各位置にアクセスできるリニアアドレスが形成されます。仮想アドレスは、ページ番号とページ内のオフセットの2つの部分に分かれています。ページ番号は、各物理ページが配置されている物理メモリのベースアドレスが格納されているページテーブルへのインデックスとして機能します。このベース・アドレスとページ内オフセットの組み合わせが物理メモリ・アドレスを形成します。

次の図は、仮想メモリのページが物理メモリのページにマッピングされる、ページテーブ ルの簡単な例を示しています。の32ビット環境では、仮想アドレス空間は4GBで、これを4KBのページに分割すると1Mページになります。各ページ・テーブル・エントリを格納するには4バイトが必要なので、4GBのマッピングされた空間全体では、マッピング・テーブルを格納するために4MBのメモリが必要になります。各プロセスが独自のマッピングテーブルを持つ場合、100プロセスで400MBのメモリが必要になります。これはカーネルにとっては少し大きいです 。ページテーブルのすべてのエントリーはあらかじめ構築されていなければならず、連続していなければなりません。もし連続していなければ、仮想アドレスのページ番号から対応するページテーブルのエントリーを見つけることはできません。

ではどうすればいいのでしょうか?4Gのスペースにはマッピングを格納するために4Mのページテーブルが必要です。この4Mを1K 4Kに分割し、各4Kはページ上に配置することができますので、1K 4Kは1Kページであり、この1Kページはまた、ページディレクトリテーブルと呼ばれる、管理のためのテーブルを必要とし、このページディレクトリテーブルは1K項目を持っており、各項目は4バイト、ページディレクトリテーブルのサイズも4Kです。

ページ・ディレクトリには1K個のエントリがあり、10ビットを使用して、ページ・ディレクトリのどのエントリにアクセスしているかを示すことができます。この項目は実際にはページテーブルエントリーの全ページ、つまり4Kページテーブルエントリーに対応します。各ページ・テーブル・エントリも4バイトなので、1ページ全体で1Kページ・テーブル・エントリがあります。10ビットで、どのページテーブルエントリにアクセスしているかを示すことができます。 ページテーブルエントリの1つは、4Kサイズのデータの1ページに対応し、12ビットで、このページ内の任意の位置を特定することができます。

これは32ビットに加算され、最初の10ビットがページ・ディレクトリ・テーブル内の項目を探すのに使われることを意味します。最初の10ビットはページ・ディレクトリ・テーブル内の項目を探すのに使われ、この項目に対応するページ・テーブルから1k個の項目が取り出され、中間の10ビットはページ・テーブル内の項目を探すのに使われ、この項目からデータが格納されているページが取り出され、最後の12ビットはデータがアクセスされるページ内の特定の位置を探すのに使われます。この場合、4GBのアドレス空間をマッピングするには、4MB+4KBのメモリが必要になりますが、この方が大きいのでは、と思われるかもしれません。 もちろん、ページが一杯になれば大きくなりますが、多くの場合、プロセスにはそれほど多くのメモリは割り当てられません。

例えば、上の図で、このプロセスにデータ・ページが1つだけ割り当てられているとします。ページ・テーブルだけが使用される場合、このプロセスにも1Mのページ・テーブル・エントリ、合計4Mのメモリが必要ですが、ページ・ディレクトリが使用される場合、ページ・ディレクトリには全部で1Kの割り当てが必要で、4Kのメモリを占有しますが、その中の1つの項目だけが使用されます。ページテーブルエントリーに関しては、そのデータページを管理できるページテーブルエントリーページ、つまり4Kまでしか割り当てる必要がないので、メモリを大幅に節約できます。

もちろん、64ビットシステムでは、2レベルでは間違いなく不十分なので、グローバル・ページ・ディレクトリ・エントリPGD、上位ページ・ディレクトリ・エントリPUD、中間ページ・ディレクトリ・エントリPMD、ページ・テーブル・エントリPTEの4レベルになります。

概要

ユーザーランドのメモリマッピング

ユーザー状態とカーネル状態の分割

プロセスの仮想アドレス空間は、プロセスから見ると本当にメモリなので、task_structから見てみましょう。これはメモリを管理するための構造体mm_structを持っています。


構造体mm_structの内部には、このメンバ変数があります:


仮想メモリ空間全体を2つに分ける必要があると言いましたが、1つはユーザー・アドレス空間、もう1つはカーネル・アドレス空間です。では、この2つの部分の境界線はどこにあるのでしょうか?これはtask_sizeによって定義されます。

32ビットシステムの場合、最大2^32=4Gのアドレスが可能で、ユーザー状態の仮想アドレス空間は3G、カーネル状態の仮想アドレス空間は1Gです。64ビットシステムの場合、同じ48ビットだけが仮想アドレスに使用され、カーネル空間とユーザー空間はともに128Tです。カーネル空間とユーザー空間は大きなギャップによって隔離されています。

ページの割り当て

小さなメモリの割り当て

カーネル状態の仮想空間は、特定のプロセスとは関係ありません。 すべてのプロセスは、システムコールを介してカーネルに入った後、同じ仮想アドレス空間を参照してください。合計1Gの32ビットカーネルの仮想アドレス空間は、直接マップされた領域として知られている最初の896Mの大部分を占めています。

いわゆる直接マップされた領域は、つまり、空間のこの部分は連続であり、物理メモリは、実際には、物理メモリの場所を取得するには、仮想メモリアドレスから3Gを引いた、非常に単純なマッピング関係です。

Read next

フロントエンドのグラフィックスは、クロスがあるパスを決定する方法

可視化アプリケーションでは、パスが交差しているかどうかを判断する必要がよくあります。パスが交差するかどうかによって、多角形が単純多角形かどうか、交通ルートが交差点を持っているかどうかなどを判断することができます。 この問題の本質は、2つの線分が交差するかどうかを判断することです。経路は線分で構成されているので、隣接する線分に加えて判定さえすれば、他に2つの線分が交差することはなく、JS生成...

Oct 22, 2020 · 3 min read