メモリとは
メモリとは、CPU が直接読み書きできるものです。電源を切るとメモリ上のデータは失われます。※本記事では不揮発性メモリ(SSD, HDD など)をストレージデバイスと表現します。
CPU がプロセスを実行する場合、ストレージデバイスにあるプログラムをメモリ上に移動してから実行します。
メモリには以下の2種類がありますが、本記事のメモリは”メインメモリ”を意味します。
- メインメモリ
- みんなが想像するメモリ。
- みんなが想像するメモリ。
- キャッシュメモリ
- CPU についていることが多いキャッシュメモリ。メインメモリより CPU から高速にアクセスできるため、よく利用するデータを置くことで処理を高速化するために利用します。
- CPU についていることが多いキャッシュメモリ。メインメモリより CPU から高速にアクセスできるため、よく利用するデータを置くことで処理を高速化するために利用します。
free コマンドでメモリ使用率を確認
free コマンドを利用することで以下のようにメモリ使用率の内訳を確認することができます。

- total: システムに搭載されているメモリ
- used: カーネルや各プロセスが利用しているメモリの総量。
- free: 見かけ上の空き容量
- buff/cache: カーネル内にあるバッファキャッシュとページキャッシュの総量
- 上述したキャッシュメモリとは別物
- メインメモリ内のキャッシュ
- available: free + カーネル内メモリ領域の開放可能なメモリ
total used free shared buff/cache available Mem: 8063684 358512 7180624 652 524548 7473952
メモリを実際に使用をしてみる
今回は /dev/shm にマウントされた共有メモリを利用します。
ファイルシス タイプ サイズ 使用 残り 使用% マウント位置
devtmpfs devtmpfs 3.9G 0 3.9G 0% /dev
tmpfs tmpfs 3.9G 0 3.9G 0% /dev/shm
tmpfs tmpfs 3.9G 420K 3.9G 1% /run
tmpfs tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup
/dev/nvme0n1p1 xfs 64G 20G 45G 30% /
tmpfs tmpfs 788M 0 788M 0% /run/user/1000
メモリを2GiB 利用してみます。
total used free shared buff/cache available Mem: 8063684 358512 7180624 652 524548 7473952
2048+0 レコード入力 2048+0 レコード出力 2147483648 バイト (2.1 GB) コピーされました、 0.899953 秒、 2.4 GB/秒
total used free shared buff/cache available Mem: 8063684 362620 5043644 2097804 2657420 5370028
free と Available が 2GiB 減少し、shared, buff/cache が 2GiB 増加したことがわかります。
電源を切るとメモリの内容が消えることを確認
再起動し、メモリの内容が消えることを確認します。
tmpfs tmpfs 3.9G 2.0G 1.9G 53% /dev/shm
tmpfs tmpfs 3.9G 0 3.9G 0% /dev/shm
OOM (Out Of Memory)
OOM はメモリが足りないことです。(プロセスを実行する際に free コマンドの available が足りない状態。)

スワップ:メモリが不足する場合の対処その1
メモリが足りない場合は、使っていないプロセスをメモリから追い出します。これをスワップと言います。なお、追い出す先はストレージデバイス(SSD や HDD など)です。

スワップされたプロセスを実行する場合は、再びメモリに戻す必要があります。ストレージデバイスの読み書きはメモリと比較してクッッッッッッッソ遅いので、スワップが発生するとプロセスの処理が遅くなります。
OOM killer:メモリが不足する場合の対処その2
メモリがいっぱいになった。スワップ領域もいっぱいになった。でも新しくプロセスを立ち上げたい。この時、OS によるプロセス kill 祭りが始まります。これが OOM Killer です。

いわゆる「何もしていないのに、アプリが落ちた」が発生します。
仮想メモリ
プロセスにメモリを割り当てる際には、以下の方式を利用してメモリを管理します。
ページング方式
ページング方式とは、プロセスごとにページテーブルと呼ばれる仮想アドレスと物理アドレスのマッピング表を持つ方法です。
なお、ページサイズは getconf PAGESIZE コマンドで確認可能です。
4096
この場合は、ページサイズは 4Bytes であることがわかりますね。以降の例ではわかりやすいようにページサイズは 100Bytes で説明します。

ちなみに、上記のページテーブルで仮想アドレス 300-400 にアクセスした場合、ページフォールトが発生します。そのまま、新しく割り当てる物理アドレスが見つからない場合はセグメンテーションフォールトが発生します。
セグメント方式
セグメント方式とは、メモリでプロセスを管理する際に、以下のようにセグメント(プロセスやデータの属性)ごとに分けて領域を分けて管理する方法です。

- テキストセグメント
- プログラムの命令コードを格納
- データセグメント
- 初期化済みのグローバル変数を格納
- bss セグメント
- 初期化されていないグローバル変数を格納
- ヒープセグメント
- malloc 関数などで動的に確保するメモリ
- スタックセグメント
- ローカル変数を格納
ページ化セグメンテーション
ページング方式 + セグメント方式でメモリを管理する方法です。
物理メモリに直接プロセスを割り当てる場合以下のような問題が発生するため、ページ化セグメンテーションを利用して仮想メモリにプロセスを割り当てます。
- メモリの断片化
- 別のプロセスやカーネルのメモリに無断アクセス可能
- マルチプロセスが困難
メモリの断片化
ページ化ページ化セグメンテーションが無い時
以下のようにメモリの空き容量が断片化すると、合計空き容量は 200Bytes あるのに、個別の空き容量が 100Bytes しか無いので、200Bytes のプロセス4が起動できない。

ページ化ページ化セグメンテーションがある時
ページテーブルにより、バラバラの物理メモリを1つの大きなメモリと見なせるため、プロセス4が起動可能。

別のプロセスやカーネルのメモリに無断アクセス可能
ページ化ページ化セグメンテーションが無い時
物理メモリ番号を指定するだけでアクセス可能な場合、以下のように勝手にメモリを書き換えられる。

ページ化ページ化セグメンテーションがある時
ページテーブルにアクセス権限があるため、許可のあるメモリにしかアクセスできない。

マルチプロセスの扱いが困難
ページ化ページ化セグメンテーションが無い時
同じアドレスが使えないので、先着順になる。無理やり上書きすると既存のプロセス2が破壊される。

ページ化ページ化セグメンテーションがある時
セグメント(プロセス)ごとにページテーブルを持つことで、同じ仮想アドレスを利用できます。

参考書籍
以下の書籍がとてもわかりやすかったです。
コメント