🖥️

メモリの仕組みと排他制御

2024/02/25に公開

物理メモリと仮想メモリ

コンピュータのメモリ管理には、物理メモリと仮想メモリの概念があります。

物理メモリ

物理メモリとは、実際に存在するメモリチップ上のメモリ空間です。

仮想メモリ

仮想メモリとは、論理メモリとも呼ばれ、オペレーティングシステム(OS)によって管理されるメモリ空間です。
仮想メモリは、物理メモリよりも大きなアドレス空間を提供し、プロセスが使用するメモリアドレスを抽象化します。
この抽象化により、各プロセスは物理メモリの実際の構成や他のプロセスのメモリ使用状況を意識することなくメモリを利用できるようになります。

メリット

仮想メモリを使用する最大の利点の一つは、各プロセスが独自の仮想アドレス空間を持つことです。
この仮想アドレス空間において、各プロセスは0から始まる連番のアドレスがOSから提供されたと認識します。
これにより、以下のような利点があります。

独立性と安全性

各プロセスは、他のプロセスのメモリ空間にアクセスできません。
そのため、プロセス間でデータを誤って上書きするリスクが低減し、システムの安全性と安定性が向上します。

プログラミングの簡単化

プログラマーは、物理メモリの配置や他のプロセスのメモリ使用状況を意識する必要がなくなります。
これによりソフトウェア開発が簡単化されます。

メモリ保護

OSは、各プロセスが使用する仮想メモリ空間を管理し、プロセスが許可されていないメモリ領域にアクセスすることを防ぎます。
これにより、意図しないメモリ領域へのアクセスや、悪意のあるプロセスによるデータの破壊を防ぐことができます。

柔軟なメモリ管理

物理メモリが限られている状況でも、仮想メモリを通じてディスク上のスワップスペースを活用することで、プロセスはより多くのメモリを使用できるようになります。
これにより、物理メモリのサイズに依存することなく、大規模なアプリケーションを実行することが可能になります。

メモリマッピング

物理メモリと仮想メモリ間のマッピングはOSによって管理されます。
このシステムは、メモリ管理ユニット(MMU)と呼ばれるハードウェアに補助されるのが一般的です。
MMUは、仮想アドレス(プロセスによって使用されるアドレス)を物理アドレス(物理メモリ上の実際の位置)に変換する役割を持ちます。
この仕組みにより、複数のプロセスが同時に実行されている環境でも、各プロセスが独立したメモリ空間を持っているかのように動作することが可能になります。

メモリスワップ

仮想メモリは、プロセスに対して、物理メモリ空間よりも大きなメモリ空間を提供することができます。
これは、仮想メモリを補助記憶装置(HDD)のスワップ領域に作られるスワップファイルにマッピングすることで実現します。
物理メモリに空き領域がない場合に、アクセス頻度の少ない情報をスワップファイルに移動させ、そのスワップファイルに仮想メモリをマッピングすることで、プロセスは物理メモリよりも大きなメモリ空間を持っているかのように動作できます。

ページとフレーム

仮想メモリシステムではメモリがページと呼ばれる固定サイズのブロックに分割され、物理メモリシステムではメモリがフレームと呼ばれるページと同じサイズのブロックに分割されます。
仮想ページと物理フレーム間のマッピングはOSによって動的に行われ、必ずしもアドレス的に連続しているわけではありません。

OSは、仮想アドレスを物理アドレスにマッピングするためにページテーブルを使用します。
ページテーブルには、各仮想ページが物理メモリ内のどのフレームにマッピングされているかの情報が含まれています。
これにより、プロセスが使用する仮想アドレスは、実際には異なる物理フレームにマッピングされたデータに対応します。

共有メモリ

通常、仮想メモリ上の異なるページが物理メモリ上の同じフレームを指すことはありません。
ページングシステムでは、ページとフレームが同じサイズで分割がおこなわれ、ページテーブルを通じて仮想アドレスと物理アドレスの間の対応付けが行われますが、この対応付けは一意に管理されています。
つまり、ある仮想ページがある物理フレームにマッピングされる場合、その物理フレームはその仮想ページに占有されます。

しかし、共有メモリの実現のために、OSは異なるプロセスの仮想アドレスを同じ物理アドレスにマッピングすることがあります。
共有メモリとは、異なるプロセス間でデータを共有するためのメカニズムです。
異なるプロセスの仮想ページが同一の物理フレームを指すようにマッピングすることで、簡単にプロセス間でデータを共有できます。

他にも、メモリマッピングされたファイルや同一コードの実行など、異なるプロセス間でメモリを共有するためのメカニズムがあります。
ファイルの内容を物理メモリにマッピングする際、異なるプロセスがそのファイルの同じ部分を読み書きするために、そのファイルの内容を表す仮想メモリが物理メモリの同一フレームにマッピングされることがあります。
異なるプロセスが同じ実行コード(ライブラリやプラグインなど)を使用する場合、そのコードセグメントは物理メモリ内で一度だけ格納され、複数のプロセスの仮想アドレス空間からアクセスされることがあります。

一般的なページング操作では、異なる仮想ページが同じ物理フレームを指すことはありませんが、このような特別な場合には、OSのメモリ管理機能によってメモリマッピングが明示的に制御されます。

メモリの排他制御

共有メモリのように、異なるプロセスが同じフレームにアクセスする場合、読み書きの衝突を避けるために排他制御が必要になります。
排他制御は、複数のプロセスが同時にメモリにアクセスすることを制御するための仕組みです。
排他制御の実現には、主にOSレベルの制御とCPUレベルの制御の二つが関わります。

OSレベルの排他制御

OSレベルの排他制御は、オペレーティングシステムが提供する同期プリミティブを使用して実現されます。
同期プリミティブとは、複数のプロセスやスレッドが共有リソース(メモリやファイルなど)に安全にアクセスできるようにするための基本的な手法のことです。
これらは、データの整合性を保持し、競合状態やデッドロックなどの問題を防ぐためにOSによって提供されます。
主な同期プリミティブとして、ミューテックス、セマフォ、条件変数などがあります。

ミューテックス(Mutex)

ミューテックスは、同時に1つのスレッドのみがリソースにアクセスできるようにするためのロックメカニズム手法です。
ミューテックスを獲得したスレッドのみがリソースを使用でき、使用が終わったらミューテックスを解放して他のスレッドがアクセスできるようにします。

セマフォ(Semaphore)

セマフォは、リソースに同時にアクセスできるスレッド数を制限する手法です。
同時にリソースにアクセスできるスレッド数を表すカウンタを持っており、スレッドがリソースを使用する際にはカウンターが減少し、リソースの使用が終わるとカウンターが増加します。

セマフォによって、リソースが利用可能になった瞬間に待機中のスレッドが即座にリソースを利用できるようになり、リソースがアイドル状態になるのを防ぐことでリソースを効率的に使用できます。
これは、例えば、データベースのコネクションプールやファイルへの同時アクセス数を制限する場合などに使用されます。

条件変数(Condition Variable)

条件変数は、特定の条件が満たされるまでスレッドを待機させるために使用される手法です。
通常、条件変数はミューテックスと組み合わせて使用されます。
特定の条件が満たされるのを待っている間にミューテックスのロックを解放し、条件が満たされたらスレッドを再び実行状態に戻すことで、他のスレッドがリソースを使用できるようにします。

CPUレベルの排他制御

CPUレベルの排他制御は、ハードウェアによって提供されるアトミック操作を使用して実現されます。
アトミック操作とは、ある操作や処理が排他制御下で単一のスレッドだけで行われていることであり、他のスレッドによる干渉を受けないことを保証する操作のことです。
このように、他のスレッドによる干渉を受けず、ある操作や処理が途中で中断されることなく最後まで完了することが保証される性質をアトミック性(原子性)といいます。

アトミック操作は、操作対象のビット数やデータ構造によって、その利用可能性や挙動が異なります。
これは、アトミック操作がハードウェアによって直接サポートされているか、または特定の操作に対してどのような命令が利用可能かに依存します。
例えは、多くのプロセッサは、標準的な整数型に対してインクリメント、デクリメント、加算、減算、比較して交換など、基本的な書き込み操作の原子性を保証しています。
しかし、構造体やクラスのような複雑なデータ構造に対しては、アトミック操作が直接サポートされていない場合があります。

メモリオーダリング

アトミック操作を組み合わせることで、ロック状態のないデータ共有を実現できますが、一方でメモリオーダリングの問題が発生することがあります。
メモリオーダリングとは、CPUによるメモリへのアクセス順序のことであり、コンパイル時にコンパイラによって生成されるものと、実行時にCPUによって生成されるものの2種類が存在します。
ここで、CPUは高速化のためにOut-of-order実行されるため、投機実行によってLoad命令は早く実行され、ストアバッファリングによってStore命令は遅れて実行されることがあります。
つまり、キャッシュメモリやメモリバンクといった異なるタイプのメモリのバスを最大限に有効活用するために、メモリ操作の順序を入れ替えます。
このようにプログラムの記述順序とは異なる順序でメモリアクセスを行うことがあり、意図した通りの順序で実行させるにはメモリバリア(フェンス命令)が必要となります。
これがメモリオーダリング問題です。

なお、メモリバンクとは、メモリシステムにおいてデータを格納するための物理的または論理的な構成単位です。
メモリは複数のバンクに分割されることがあり、各バンクには独立してアクセスできるため、並行してデータを読み書きすることができます。
これにより、効率的なメモリアクセスと管理が可能になります。

GitHubで編集を提案

Discussion