差分

移動先: 案内検索

記憶管理

4,965 バイト追加, 2022年11月17日 (木) 15:17
/* 動的なメモリ領域確保 */
== 実メモリ ==
まず混乱を避けるためにメモリというあまりにも普段使い慣れた言葉を明確にしましょう。この節で使うメモリという言葉は、秋葉原で売っている部品のメモリのことではなく、オペレーティングシステムが管理し参照できる主記憶や仮想記憶もすべて含めた記憶領域の意味です。ですから、ここでのメモリという言葉は概念的でもあり、また実際に存在するものでもある両方の性質を持ちます。ここではハードウェアのメモリにあたるものとして実メモリという言葉という言葉を使います。まず混乱を避けるためにメモリというあまりにも普段使い慣れた言葉を明確にしましょう。この節で使うメモリという言葉は、ハードウェア部品のメモリのことではなく、オペレーティングシステムが管理し参照できる主記憶や仮想記憶もすべて含めた記憶領域の意味です。ですから、ここでのメモリという言葉は概念的でもあり、また実際に存在するものでもある両方の性質を持ちます。ここではハードウェアのメモリにあたるものとして実メモリという言葉を使います。
タが展開されていて、それに対し正しいアクセス制御できなければシステムは
正常に動かなくなってしまいます。
 
 
実メモリはいうまでもなく限定された貴重な資源です。実メモリはプログラムが使うだけでなく、I/Oのキャッシュに使ってI/Oパフォーマンスを改善することにも使います。
もしプログラムの中で既にアクセスされないメモリ空間を実メモリにかかえていていて、実メモリをキャッシュに使えないような場合は、せっかくの実メモリが有効に使えていない状態になってしまいます。
そこでプログラムが抱えたまま使っていないメモリ空間を外部記憶装置に送り出し、その分の実メモリをI/Oのキャッシュに利用するならば、全体のパフォーマンスが向上するはずです。このようにプログラムに使うメモリ空間を管理するだけでなくシステム全体を通して実メモリを有効に使うことが重要になります。
この書き込みや読み込みをする単位をページといい、サイズは32ビットアーキテクチャーのCPUは4KBのページサイズで、64ビットアーキテクチャーのCPUは8KBのものがほとんどです。この書き込みや読み込みをする単位をページと呼びます。扱えるページのサイズはCPU依存<ref>これらはCPUのアーキテクチャーに依存するので異なる場合があります。例えばIBM POWER5+ や POWER6 プロセッサーは64KBのページサイズも利用することが出来ます。 ([https://www.ibm.com/developerworkssupport/communityknowledgecenter/wikisssw_aix_72/home?lang=en#!/wiki/Welcome%20to%20High%20Performance%20Computing%20%28HPC%29%20Centralperformance/multiple_page_size_support.html Multiple pagesize support])</64KB%20pages%20on%20Linux%20for%20Power%20systems 64KB pages on Linux for Power systems ref><ref>x86-64(IBMサイトIntel 64)アーキテクチャーの場合2MBと1GBの拡大したページサイズが使えます。([https://software.intel.com/sites/default/files/managed/a4/60/325384-sdm-vol-3abcd.pdf Intel 64 and IA-32 Architectures Software Developer’s Manual])
</ref>
ですが、多くのCPUの最小ページサイズが4KBなのでLinuxのページサイズはデフォルトで4KBとなっています。
==== MMU ====
[[File:Memory_fig2.png|thumb|left|300px|仮想記憶のモデル]]
 
プロセスに割り当てられているメモリリソースとしてのメモリのアドレスは仮想的なアドレス空間に割り当てられます。仮想アドレス空間での仮想アドレスは、実際の実メモリの物理的な意味でのアドレスとは一致しません。
 
仮想アドレス空間と実メモリアドレス空間のマッピングは、それを管理する MMU (Memory Management Unit) というハードウェアによってマップされています。
仮想記憶の機能があっても、もし実メモリが十分余裕があれば、わざわざスワップのための設定をする必要がありません。またプラクティカルには実メモリは、スワップ先である外部記憶装置よりも高価であり、そのスワップのオーバーヘッドを考えても利益があると考えた場合に有効です。またスワップする先が用意できなければ当然できません。たとえばCD-ROMからブートするKNOPPIXではデフォルトではスワップを使っていません。さて、先ほど説明したように記憶空間はページと呼ばれる単位で分割されています。主記憶と補助記憶とのやりとりはページ単位で行われ、ページイン、ページアウトを行います。一般的にはハードディスク上にスワップ用のパーティションを取って、そこをスワップ先に指定します。多くの場合はインストール時にセットアップするようになっています。通常はパーティションをマウントするための情報を書く/etc/fstabにスワップパーティションの割り当ての記述が作られているはずです。
先ほど説明したように記憶空間はページと呼ばれる単位で分割されています。主記憶と補助記憶とのやりとりはページ単位で行われ、ページイン、ページアウトを行います。  一般的にはハードディスク上にスワップ用のパーティションを取って、そこをスワップ先に指定します。多くの場合はインストール時にセットアップするようになっています。通常はパーティションをマウントするための情報を書く/etc/fstabにスワップパーティションの割り当ての記述が作られているはずです。 <pre> /dev/hda3 none swap sw ,pri=0 0 0</pre>
スワップの状態は/proc/swapsを見るとわかります。下の例はサイズが1453872K (1.45GB)バイトである/dev/hda3パーティションがスワップに使われているという意味です。
<pre class="bash">
% cat /proc/swaps
Filename Type Size Used Priority
/dev/hda3 partition 1453872 0 -1
</pre>
たとえばswapf01という名前のスワップファイルを作ってシステムにスワップファイルとして登録する時は次のようにします。
<CODEpre>
# /bin/dd if=/dev/zero of=swapf01 bs=8192 count=256
# /sbin/mkswap swapf01 2048
# sync
# swapon swapf0
</CODEpre>
;補足: Linux 2.6.32でスワップについて試したことのメモランダム [[linuxのswapについて私が知っている二、三の事柄]]
 
 
==== zswap ====
 
zswap[https://www.kernel.org/doc/Documentation/vm/zswap.txt]は、 Linux kernel version 3.15 から入った仮想メモリ圧縮 (Virtual Memory Compression)を利用したスワップで、外部記憶装置にページをスワップするのではなく、実メモリ上に作った圧縮ブロックデバイスにページをスワップする機能でです。実メモリ上にスワップできなくなった時に、これまでのスワップ機能で用意しているスワップ領域にスワップを始めます。
 
 
 
ディスクなどメモリから比較すると低速なストレージへのI/Oが少なくなるため、頻繁にページアウトが発生しスワップファイルへのI/Oがボトルネックになるようなケースでは有用な機能です。フラッシュメモリを使ったSSDのような書き換え上限があるような外部記憶装置上にスワップファイルを作ると頻繁なスワップはメディアの劣化が進むためzswapが有効であるという議論もあります。圧縮処理が行われるため、その分、CPUに負荷がかかります。ただし、それがI/Oのオーバーヘッドによる処理の低下と比較し、相対的にどちらに利益があるかは、個々のシステム構成によって変化する議論であることは注意してください。
== 局所参照性 ==
* プログラム: malloctest.c #include <stdlib.h> main() { char *p; size_t areasizesyntaxhighlight lang=1024*1024*512; if ((p'C' line=(char *)malloc(areasize)) == NULL) { perror("malloc1"); } sleep(10); free(p); } 実行例: $ cc malloctest.c $ ./a.out & $ ps axu | grep a.out hironobu 29813 0.0 0.0 525528 284 pts/8 S 17:32 0:00 ./a.out ---この並びは下の様になっています--- USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
char *p;
size_t areasize=1024*1024*512;
if ((p=(char *)malloc(areasize)) == NULL) {
perror("malloc");
}
sleep(10);
free(p);
}
</syntaxhighlight> * 実行例 <pre class="bash">$ cc malloctest.c ./a.out &[1] 93627$ ps -o user,pid,vsize,rss,cmd USER PID VSZ RSS CMDhironobu 87439 10752 6392 bashhironobu 93627 526788 580 ./a.outhironobu 93628 9928 3440 ps -o user,pid,vsize,rss,cmd </pre>  このプログラムは512MBメモリ領域をmalloc(3)を使いアロケーションしています。実行時の状況をpsで見てみると、VSZが525528、RSSが284となっています。VSZはVirtual を使いアロケーションしています。実行時の状況をpsで見てみると、VSZが526788、RSSが580となっています。VSZはVirtual memory Sizeのことで仮想記憶もふくめて全部のメモリサイズです。RSSはReal Set Sizeのことで、実メモリ(物理的なメモリ)で専有しているメモリサイズです。つまり、このプログラムは約523.5MBもの記憶領域を取っているにもかかわらず、実際の実メモリ上では284KBしか取られていません。 で専有しているメモリサイズです。つまり、このプログラムは約527MBもの記憶領域を取っているにもかかわらず、実際の実メモリ上では580KBしか取られていません。
次の ps コマンドを実行してみましょう。するとRSSでソートされて、USER / COMMAND / RSS / VSZの順で表示されます。
<pre class="bash">
$ ps -Ao user,args,rss,vsize --sort rss
USER COMMAND RSS VSZ root [keventd] 0 0 root [ksoftirqd_CPU0] 0 0
...
hironobu emacs20 -geometr 9076 11112/usr/bin/emacs23 15732 42140 canna hironobu gedit 23204 173232 hironobu /usropt/sbingoogle/chrome/chrome - 107380 359600</cannas 17540 19152pre>
keventdやksoftirqd_CPU0などRSSとVSZが両方0のものはカーネルレベルで動いているスレッドです。ここではユーザレベルのプロセスであるemacs20と/usr/sbin/cannaserverのメモリの使い方に着目しましょう。emacs20はRSSは9076KBで、仮想記憶も含めたサイズは11112KBです。多くの場合、十分に実メモリに余裕があっても、プロセス中で生成された記憶空間がすべて実メモリの上に載っているわけではありません。必要のあるもののみ実メモリ上にページインされます。CD-ROMから立ち上げるため仮想記憶のスワップ先がないKNOPPIXでも同じです。RSSもVSZも2つとも表示されて両者に差があります。keventdやksoftirqd_CPU0などRSSとVSZが両方0のものはカーネルレベルで動いているスレッドです。ここではユーザレベルのプロセスであるemacs23のメモリの使い方に着目しましょう。emacs20はRSSは15732KBで、仮想記憶も含めたサイズは42140KBです。多くの場合、十分に実メモリに余裕があっても、プロセス中で生成された記憶空間がすべて実メモリの上に載っているわけではありません。必要のあるもののみ実メモリ上にページインされます。
;調べてみよう: vmstatで観察してみましょう。vmstatは仮想記憶のステータス観察するためのツールです。1秒毎に表示するオプションで動作させながら、先程のプログラムを改造し徐々に記憶を取るようなプログラムにして動かし、観察してみましょう。
<pre class="bash">
% vmstat 1 <- 1秒毎に表示
procs memory swap io system cpu
2 1 0 29964 1140 368 2128 316 124 79 310 485 817 37 9 54
...
</pre>
 
== コピーオンライトとその実際 ==
 
 
Linuxのカーネルではプロセスが親から引き継がれたメモリ領域や実行コードなどを引き継いでいても、そこに書き換えが発生するまで、実際のメモリ領域はとりません。書き込みがあって始めてメモリ領域を確保し、中身をコピーして別なものにします。このことをコピーオンライト(CoW: Copy on Write)といいます。このような仕組みにより、高速に新しいプロセスを生成したり、あるいはメモリの効率的な利用が出きるようにしています。
 
 
/proc/(プロセスid)/smapsは、カーネル内の該当プロセスのメモリ利用状況を表示するAPIです。それを使って実験したいと思います。
dashはDebian版軽量シェルでシステムのシェルスクリプトを実行するのに使われます。
さて、$$はシェル自身のプロセスidですので、/proc/$$/smapsとすると現在使っているシェルのメモリ利用状況がわかります。
注目するのは Shared_Clean と Private_Clean の値です。
Shared_Clean はシェアしているメモリです。
Private_Clean は自らのメモリです。
クリーンな、という意味は、まだそのページは変更されていない(書き込みがおこっていない)という意味です。
まず最初、dashを起動してdashシェル環境でメモリ利用状況をみます。
この時、引き継ぐものがないのでPrivate_Cleanが76kB (= Rssの値と同じ)、Shared_Cleanが0kBとなっています。
次にdashの中でさらにdashを起動します。つまり最初のdashが親プロセスとなった新しいdashです。
この上で両方の値をみるとPrivate_Cleanが0kB、Shared_Cleanが76kB(= Rssの値と同じ)となっています。
つまり、子プロセス側となったdashのメモリは、この時点ではシェアしているものを使っていることがわかります。
 
<pre class="bash">
$ dash
$ cat /proc/$$/smaps | head -9
08048000-08060000 r-xp 00000000 08:01 4825 /bin/dash
Size: 96 kB
Rss: 76 kB
Pss: 76 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 76 kB
Private_Dirty: 0 kB
Referenced: 76 kB
$ dash
$ cat /proc/$$/smaps | head -9
08048000-08060000 r-xp 00000000 08:01 4825 /bin/dash
Size: 96 kB
Rss: 76 kB
Pss: 34 kB
Shared_Clean: 76 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 76 kB
</pre>
== mmap ==
* プログラムmmap.c <syntaxhighlight lang='C' line="1" >#include <stdio.h>#include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> int main(void) { int fd; struct stat st; char *p; int i; fd=open("dat",O_RDWR); fstat(fd, &st); p=mmap(0,st.st_size,(PROT_READ|PROT_WRITE),MAP_PRIVATE,fd,0); for(i=0; i < st.st_size -1 ; i++) { printf("[%c]",p[i]); } close(fd); }</syntaxhighlight> * 実行<pre class="bash"> $ echo -n 'abcdefg' > dat
$ cc mmap.c
$ ./a.out
[a][b][c][d][e][f][g]$
</pre>
ファイルもメモリも、さらにデバイスすらも単純な1つの記憶空間として扱おうというコンセプトが単一レベル記憶です。
現在、オペレーティングシステムレベルでサポートしているのはIBM社のSystem iシリーズ<ref>
IBMのサイトにあるIBM System iの単一レベル記憶の解説 : http://www-06.ibm.com/systems/jp/i/seminar/reconf/reconf1.shtml
</ref>
ぐらいしかないので、
インターネット上で説明を探すとIBM System iの機能=単一レベル記憶の定義みたいな説明しかありませんが、
実は、その歴史は古く1960年代に作られたMultics上に既に実装されています。実は、その歴史は古く1960年代に作られたMultics上に既に実装<ref>Multics and More VMhttps://users.cs.duke.edu/~chase/cps210-archive/slides/multics-vm.pdf</ref>されています。
UNIXは単一レベル記憶を前提としているデザインはありませんが、UNIXは単一レベル記憶を前提としているデザインはありませんが、現在のUNIX系のオペレーティングシステムはmmapを実装することによってファイルもメモリも、そしてデバイスも同様に扱うことができる利益を得ています。現在のUNIX系のオペレーティングシステムはmmapを実装することによってファイルもメモリも、例としては今時のLinuxやその他UNIX系のオペレーティングシステムでは実行バイナリやライブラリを動かすとき、一々ファイルの中身を読み込む動作をせず、実行するバイナリデータを内部でマップしてしまいます。そしてデバイスも同様に扱うことができる利益を得ています。ですから古典的なUNIXで言われるような、「実行バイナリファイルをメモリに読み込み実行する」という表現は、少なくとも現在のLinuxの実行時の表現としては適切ではないという状況になっています。あとデバイスの利用例としては、malloc()のいくつかの実装例としては今時のLinuxやその他UNIX系のオペレーティングシステムでは実行バイナリやライブラリを動かすとき、<ref>一々ファイルの中身を読み込む動作をせず、The GNU Allocator実行するバイナリデータを内部でマップしてしまいます。https://www.gnu.org/software/libc/manual/html_node/The-GNU-Allocator.htmlですから古典的なUNIXで言われるような、「実行バイナリファイルをメモリに読み込み実行する」という表現は、</ref>少なくとも現在のLinuxの実行時の表現としては適切ではないという状況になっています。あとデバイスの利用例としては、malloc()のいくつかの実装では内部で記憶領域を確保するときでは内部で記憶領域を確保するとき/dev/zeroをmmapでオープンして使っています。mmapの機能の背景にある単一レベル記憶というキーワードは知っていて損はないでしょう。
== 脚注 ==