具体的には、以下の3つの目標を実現する必要があります。
UNIXはこれらの問題を克服するために、 仮想記憶という概念を採用しています。
さらに、カーネルは物理メモリよりも大きなサイズのプログラムを実行させたり、 複数のプロセスを効率よく実行するために、 ページングとスワッピングという手段を使います。
図1:物理メモリの配置例
カーネルが起動されると、
カーネルの静的データのために物理メモリ領域が確保されます。
この領域は物理メモリに常駐します。
あるアプリケーションがアクセスするアドレスは、 直接物理メモリのアドレスへ変換されずに、アドレス変換表を利用して、 間接的に物理メモリにアクセスします。 この仕組みを仮想記憶と呼び、 これにより上記の機能が実現できます。
もちろん、仮想記憶も良いことばかりではなくて、
仮想記憶を実現するためには、上記の仮想アドレス空間の管理、 アドレス変換テーブルの管理、物理メモリの管理を実現する必要があります。 加えて、複数のアプリケーションを実行する場合の、 各プロセスの保護や共有の仕組みも実現する必要があります。
メモリ管理機構の性能はシステム全体に影響を与えるため、 その設計は性能に注意を払わなければなりません。 このため、アドレス変換の変換テーブルや変換テーブルのキャッシュは、 ハードウェアで実現されることが多いです。
図2:仮想記憶
仮想記憶を実現する方法には、ページングがあります。
情報は必要なときにのみディスクから読み込まれます。 プログラム内部で使用される仮想アドレスは、 システムにより自動的に物理アドレスにマップされます。 プロセスが、 現在メモリ上にない実行可能ファイルのテキスト・セグメントにアクセスすると、 カーネルはページングを行います。 通常これは、プロセスがすでに必要としないページを置き換える形で行われます。 プログラムのテキスト・セグメントの不要部分のメモリは解放され、 ほかの用途に回すことができます。
プログラムが巨大なデータ領域を使用する場合でも、 これらすべてを同時にアクセスしないのであれば、 すべてが同時にメモリ上に存在する必要はありません。
しかし、プログラムを実行するには、 コードとデータ両方が物理メモリ上にあり、 実アドレスを持っていなければなりません。 したがって、 実行時に仮想メモリのアドレスから物理メモリのアドレスに変換する機構が必要です。
仮想メモリは、多層[階層記憶]の手法を用いて実現されています。 すなわち、メモリに収まらないプログラム部分は、 ローカルなディスク装置、 またはネットワーク上のリモート・ディスクに置かれます。 プログラム実行中に使用しないコードやデータ部は、 ディスクにおきます。
仮想アドレスは、 仮想ページ番号とそのページ内のオフセットから構成されます。 ソフトウェアとハードウェアの技術を結集して、 仮想アドレスをページ・テーブル中から探し出し、 物理ページを生成し、 これとオフセットを組み合わせて物理メモリ上の実アドレスを生成します。
しかし、物理ページが常にすべての仮想アドレス参照に利用できるわけではありません。 ページ・テーブルは、 そのページが現在メモリにあるかを示す、 有効フラグを各ページごとに持っています。 メモリ管理システムは、ページがメモリになければ、それはアクティブでなく、 そのページのディスク上における位置の情報を保持しています。
カーネルは、メモリにない、 つまりアクティブでないページをプロセスが参照すると、 物理メモリにそのページをロードするまで、そのプロセスの実行を一時停止します。
仮想アドレスから物理アドレスへの変換は、以下の順序で行なわれます。
図5:仮想アドレスから物理アドレスへの変換
実際のプロセッサではどうなっているでしょうか?
x86 では2段階のページテーブルを採用しています。
SuperSPARC では3段階のページテーブルを採用しています。
このアドレス変換テーブルは、多くの CPU でハードウェアとして実装されています。 変換テーブルの変換方法には、 ページテーブルの他にハッシュを利用したものもあります。 このハードウェアを MMU(Memory Management Unit) といいます。
プログラムの実行の度にページテーブルを利用して変換すると性能が低下するので、 これを防ぐために、CPU には変換テーブルのキャッシュがあります。 このキャッシュを TLB(Translation Lookaside Buffer) といいます。
キャッシュの実装として一般的なのが、 プロセッサ内のハードウェアキャッシュである、 ページテーブルエントリのキャッシュです。 この場合、プロセッサはいつも直接ページテーブルを読み出すのではなく、 変換が必要なとき、そのページ変換をキャッシュしておきます。
プロセッサ内には、アドレス変換バッファ (TLB) があり、そこにはシステム上の複数のプロセスに関する、 ページテーブルエントリのコピーがキャッシュされています。
仮想アドレスへの参照が行われるとき、プロセッサは合致する TLB エントリを探し、見つかれば、仮想アドレスを直接物理アドレスに変換して、 データに対して適切な処理を実行します。 プロセッサが合致する TLB エントリを見つけられない場合は、 カーネルに対して、TLB 失敗が起こったことをシグナルで伝えます。
システム固有のメカニズムを使用して、 その例外をカーネルの例外処理コードに渡します。 カーネルは、そのシグナルを受けて、 アドレスマッピングのための新しい TLB エントリを生成します。 例外がクリアされると、プロセッサは再度仮想アドレスの変換を試みます。 今度は TLB 内に必要なアドレスに関する有効なエントリがあるので、 問題なく変換されます。
図6:仮想アドレスから物理アドレスへのマッピングに関する抽象モデル
仮想記憶システムでは、使用されるアドレスはすべて仮想アドレスで、 物理アドレスではありません。 仮想アドレスはプロセッサによって物理アドレスに変換されます。 その変換は、 カーネルが管理する変換テーブルに保持された情報を基に行われます。
仮想メモリと物理メモリは、変換を容易にするため、 ページと呼ばれる単位に分割されていて、 サイズはすべて同じです。 ページは、Solaris sprac では 8K バイト、 Intel x86 システム、AIX では、4K バイトです。 ページサイズは pagesize(1) コマンドがあればそれで知ることができます。 ページそれぞれには、他と重複しないページフレーム番号 (Page Frame Number, PFN)が割り振られています。
このようにページ分割されたシステムの場合、仮想アドレスは、 仮想ページフレーム番号(PFN)とオフセットから構成されています。
ページサイズが 4K バイトならば、 仮想アドレスの bit 11 から 0 がオフセットで、 bit 12 以降が仮想ページフレーム番号です。
プロセッサは仮想アドレスに出会うたびに、 仮想ページフレーム番号とオフセット番号を抽出し、 仮想ページフレーム番号を物理ページフレーム番号に変換し、 該当する物理ページの正しいオフセット位置にアクセスします。 プロセッサは、この変換のためにページテーブル(page table)を使用します。
図6は、プロセス X とプロセス Y の2つのプロセスの仮想アドレス空間を示すもので、 どちらもプロセス自身のページテーブルを持っています。 それらのページテーブルは、 プロセスの仮想ページをメモリの物理ページにマップするものです。
この図では、プロセス X の仮想ページフレーム番号 0 が物理ページフレーム番号 1 にマップされ、 プロセス Y の仮想ページフレーム番号 1 が物理ページフレーム番号 4 にマップされています。 ページテーブル内には、次の情報があります。
ページテーブルが有効かどうかを示すフラグ。
対応する物理ページフレーム番号。
ページの利用方法。 書き込み可能か、実行コードを含むか等に関係する情報。
ページテーブルにアクセスする場合、
仮想ページフレーム番号がページテーブルへのインデックスとして使用されます。
仮想ページフレーム番号 5 は、ページテーブルの 6 番目の要素に該当します
(0 が最初の要素に該当するため)。
仮想アドレスを物理アドレスに変換する場合、 プロセッサはまず仮想ページフレーム番号と、 その仮想ページ内のオフセット値を探します。 ページサイズを 2 の n 乗とすることで、マスクとシフト操作で簡単に得られます。
もう一度図6を見てほしい。 ページサイズが 0x2000 バイト(十進数なら 8192)で、 プロセス Y の仮想アドレス空間でのアドレスが 0x2194 だとすると、 アドレスを変換した結果は、仮想ページ番号 1 のオフセット値 0x194 です。
プロセッサは、仮想ページフレーム番号をプロセスのページテーブルへのインデックスとして使用することで、 そのページテーブルのエントリがわかります。 もしそのページテーブルのエントリが有効(valid)なら、 プロセッサはそのエントリから物理ページフレーム番号を取得します。 エントリが無効なら、 プロセスは物理メモリ上に存在しない仮想メモリ領域にアクセスしたことになります。 その場合、プロセッサはアドレスを解決できないので、 カーネルに制御を渡し、問題の解決を依頼します。
プロセスが有効な変換先のない仮想アドレスにアクセスしたことを、 プロセッサがカーネルに伝える方法は、その種類によって異なりますが、 伝達方法がどのようなものであれ、 その現象はページフォルト(page fault)と呼ばれ、 カーネルには、 フォルトとなった仮想アドレスとフォルトが生じた原因が通知されます。
プロセッサは、ページテーブルのエントリが有効な場合、 物理ページフレーム番号を取得し、それにページサイズを掛け、 物理メモリ内のページのベースアドレス(base address)を取得します。 最後に、プロセッサはそのベースアドレスを起点に、 取得すべき命令やデータまでのオフセットを加えることで、 完全な物理アドレスを得ます。
上記の例を再び取り上げると、プロセス Y の仮想ページフレーム番号 1 は、 物理ページフレーム番号 4 にマップされます。 その物理ページフレームは、 アドレス 0x8000(4 * 0x2000)を起点(ベースアドレス)としています。 それに 0x194 バイトオフセット値を加えることで、 0x8194 という物理アドレスが得られます。
仮想アドレスから物理アドレスへのマッピングにこうした方法を用いることで、 仮想ページフレーム番号と物理ページフレーム番号の関係は自由になります。
図6において、 プロセス X の仮想ページフレーム番号 0 は、 物理ページフレーム番号 1 にマップされますが、 仮想ページフレーム番号 7 は、 仮想ページフレーム 0 より大きな番号であるにも関わらず、 物理ページフレーム 0 にマップされます。 すなわち、仮想メモリのページは、物理メモリ内で特定の順番通りに並んでいる 必要がないということです。
仮想メモリに比べて物理メモリは非常に量が少ないので、 カーネルは物理メモリを効率的に使わなければなりません。 そのひとつの方法は、 実行中のプログラムが現在使用している仮想ページだけをロードすることです。
たとえば、データベースプログラムがデータベースに問い合わせたとします。 その場合、データベース全体をメモリにロードする必要はなく、 調べる必要のあるデータレコードだけをロードすればよいでしょう。 またデータベースへの問い合わせが検索の場合、 新規レコードを追加処理するプログラムをロードしても無駄です。 アクセスされた仮想ページだけをメモリにロードするテクニックは、 デマンドページング(demand paging) と呼ばれます。
プロセスが物理メモリ上にない仮想アドレスにアクセスしようとすると、 プロセッサは、参照された仮想ページのページテーブルエントリを見つけられません。
たとえば、 図6 では、プロセス X のページテーブルには、 仮想フレーム番号 2 のエントリがないので、 プロセス X が仮想ページフレーム番号 2 の中にあるアドレスを読もうとしても、 プロセッサは、そのアドレスを物理メモリ上のアドレスに変換できません。 この時点で、プロセッサはページフォルトが発生したことを、 カーネルに通知します。
フォルトとなった仮想アドレスが無効である場合、 それはプロセスが存在しない仮想アドレスにアクセスしたことを意味します。 おそらく、アプリケーションに不具合が起こって、 たとえばメモリ内のランダムなアドレスへ書き込みしようとしたのかもしれません。 この場合、カーネルはそのプロセスを終了させて、 暴走したプロセスからシステム内の他のプロセスを保護します。 この場合、通常プロセスの core が作成されます。
しかし、フォルトとなった仮想アドレスが有効なものでかつ、 参照されたページがそのときメモリ内になかった場合、 カーネルは、ディスク上のイメージから適切なページをメモリ内に持ってこなければなりません 。
ディスクアクセスは、相対的に遅い処理なので、 プロセスは、ページがロードされるまで待つ必要があります。 その際カーネルは、他に実行可能なプロセスがあれば、 それを選んで実行します。 そして、ロードしたページを空いた物理ページフレームに書き込んで、 仮想ページフレーム番号用のエントリをプロセスのページテーブルに追加します。 これによって、プロセスはメモリフォルトが発生した命令から再び実行されます。
今度は、仮想メモリへのアクセスに成功し、 プロセッサも仮想アドレスから物理アドレスへの変換ができるので、 プロセスの実行は継続されます。
プロセスを実行する場合、仮想ページを物理メモリに持ってこなければなりませんが、 利用可能な物理メモリの空きがない場合、カーネルは、 別のページを物理メモリから取り除くことで、そのページのための空間を確保します。
物理メモリから取り除くべきページが、 テキスト・セグメントやデータファイルのコピーであり、 上書きされていない場合、そのページは保存する必要はありません。 プロセスが再度そのページを必要とするときに、 該当するテキスト・セグメントやデータファイルからメモリ内に戻せばよいのです。
しかし、ページが変更されている場合(データ・セグメントなど)、 カーネルはそのページを保存して、 後でそのページにアクセスできるようにしなければなりません。 この種のページは、 ダーティページ(dirty page)と呼ばれ、 メモリから削除するときは、スワップファイルに保存します。
スワップファイルへのアクセスは、 プロセッサとメモリ間のスピードに比べて非常に遅いので、 カーネルは、ページをディスクに書き込む必要がある場合でも、 再使用に備えて出来るだけそれをメモリに保持しようとします。
破棄もしくはスワップするページを決めるためのアルゴリズム (スワップアルゴリズム)の効率が悪い場合、 スラッシング(thrashing)と呼ばれる状態に陥ります。 その場合、ページは間断なくディスクに書き込まれたり読み出されたりするので、 カーネルはそれに時間を取られて本来の仕事が出来ません。
たとえば、もし物理ページフレーム番号 1 ( 図6) が定期的にアクセスされるなら、 そのページはハードディスクにスワップすべきではありません。 プロセスが現在使用しているページセットは、 ワーキングセット(working set)と呼ばれ、 効率の良いスワップスキームとは、 すべてのプロセスが自分のワーキングセットを物理メモリに持つよう手配するスキームです。
カーネル自体が、 仮想メモリ内で実行されるというのはほとんどナンセンスです。 つまりカーネルが自分のページテーブルを管理しなければならないとしたら、 悪夢のような状況になるでしょう。
大部分の汎用プロセッサは、 仮想アドレスモードと同時に物理アドレスモードという概念をサポートしています。 物理アドレスモードでは、ページテーブルは不要で、 プロセッサはこのモードのときはアドレス変換しません。
物理メモリの構造にも書いてありますが、 オペレーティング・システムのカーネルは、物理アドレスモードで実行されます。
カーネルは、プロセスが新しいページを必要としかつ、 未使用ページ、あるいは再使用可能なページがない場合、 メモリ上の既存ページをページアウトし、 これらのメモリを動的に割り当てます。
つまり、ページをディスク上のページング領域に書き出します。 そして、そのページが再度必要になった時に、これを読み込ます。 そのときは他のページを強制的にページアウトします。 このような状況では、使用可能な CPU 時間のうち、 かなりの部分がページフォルト処理に費やされてしまい、 すべてのプロセスの実行効率が低下してしまいます。
この問題を解決する最良の方法は、メモリを増設することです。
では、実際のプロセスのメモリ配置を見てみましょう。
プロセスのメモリは、 図7 のように、テキスト・セグメント、 データ・セグメント、 スタック・セグメントに分類されます。
ここでセグメントとは、 連続したメモリ領域のことで、ページの集合体です。
データ保護
システム上のプロセスは、各々が独自の仮想アドレス空間を持っています。 これらの仮想アドレス空間は、お互いに完全に独立しているので、 あるアプリケーションを実行しているプロセスは、 他のプロセスの実行に影響を与えません。 さらに、メモリエリアはハードウェア上の仮想メモリ機構によって、 書き込みから保護されています。
実行中のプロセスが、自分のセグメント以外を参照しようとしたり、
書きこもうとすると、カーネルから SIGSEGV などのシグナルが送られます。
これを受信したプロセスは、カーネルから強制終了させられます。
図7:プロセスのアドレス空間
このセグメントは、普通読み出し専用で、 同じロードモジュールのプロセス間で共有されます。 例えばシェルは複数のユーザが同時に使いますが、 この場合メモリ上のテキスト・セグメントは1セットだけです。
C 言語の
静的変数 (static)、
大域変数 (extern)、
malloc() で割り当てたヒープ上の変数は、
このセグメントに割り付けられます。
(
自動変数(auto)
は、次のスタック・セグメントに割り付けられます。)
データ・セグメントは、次の3つの部分で構成されます。
データ | 初期値つき静的変数や大域変数が割り付けられます。 データの初期値は、実行可能ファイルに入っています。 static int x=100 ;実行可能ファイル内の x には 100 という整数値のビットパターンが入っています。
|
---|---|
BSS (Block Started by Symbol) |
初期値を指定しない静的変数や大域変数が割り付けられます。 (初期値は 0) static int y ;実行可能ファイルには、BSS の領域はありません。 y は実行時にカーネルが BSS に割り当て、値を 0 に初期化します。 |
ヒープ heap |
malloc() で確保する変数の領域本体が割り付けられます。 ヒープは実行時に大きさが決まる変数を確保するための領域です。
char *p ; p = malloc( 100 );100バイトの領域が、malloc() 実行後、ヒープ上に確保され、 そのアドレスが p に入ります。 |
ヒープ領域の大きさは、brk()
システム・コールや
sbrk()
システム・コールで変えられます。
これらのシステム・コールは、
malloc()
ライブラリ関数からも呼び出されます。
C 言語の自動変数(auto variable) 、関数への引数、関数呼び出しの戻り番地は、ここに割りつけられます。
C 言語で以下のようにすると、
4バイトの領域がスタック・セグメントに割りつけられます。
auto
というキーワードは、普通省略します。
main() { auto int z; }
スタック・セグメントは、普通、アドレス 0xffffffff (32ビットのアドレス空間の場合) から小さい方へ伸びていきます。 この部分は、関数呼び出しが続くと伸びます。 カーネルは、割り当てられた領域を越えると自動的に拡張します。
C 言語では、次のように main()
関数は3つの引数で呼び出されます。
main( int argc; char *argv[], *envp[] ) { }ここで、
argc
は引数の数(argv
の数)、
argv
は引数文字列のベクタの先頭番地、
envp
は環境変数のベクタの先頭番地です。
この引数と環境変数は、スタック・セグメントのスタックの底、
図7
では上にあります
(アドレスが大きいところ)。
また、extern char **environ で参照することも可能です。
何百ものCのライブラリ関数がオペレーティング・システムにより提供されています。 これらのライブラリは、コンパイル、リンク時に実行可能ファイルに連結されます。
リンク・ローダは、参照しているオブジェクトがプログラム・コードにない場合、 ライブラリのリストを探し、 見つかれば実行可能ファイルに、それを連結します。 これをプログラムの全てのオブジェクトに対して行います。
しかし、この手法にはいくつか問題点があります。
このライブラリは、ar コマンドで 複数のオブジェクトファイルをアーカイブして作り、 拡張子は .a です。
通常、システムの c ライブラリ関数は、/usr/lib/libc.a にあります。
共有ライブラリは、プロセスのアドレス空間のどこにでもマップでき、 最小の再配置で実行できる位置独立コードで構成され、 複数のプロセスで実行時に共有されます。
リンカが、 オブジェクト・ファイルとライブラリから実行可能ファイル (a.out) を生成するとき、共有ライブラリから出力ファイルへはテキスト (コード) またはデータをコピーしません。 その代わりに、ダイナミック・ローダが、 実行時にそのライブラリをプロセスのアドレス空間にマップします。
リンカは、プログラムから共有ライブラリ・ルーチンおよびデータへの参照を、 リンケージ・テーブルのエントリにバインドするだけです。 一方、ダイナミック・ローダは、 ライブラリがマップされたときにリンケージ・テーブルのエントリを、 共有ライブラリの関数のアドレスで埋めます。 このリンケージ・テーブルは、関数呼び出しのジャンプ・テーブルとして働きます。
ダイナミック・ローダは、 Solaris では libdl.so、HP-UX では dld.sl で、 それ自身も、共有ライブラリです。
ロード
不完全な実行可能ファイルには、 リンク時に検索した共有ライブラリのパス名リストがあります。 実行時、ダイナミック・ローダは、 プロセスとプログラムにリンクされたすべての共有ライブラリをつなげます。 またダイナミック・ローダは、 リンク時に検出した共有ライブラリでロードされていないものを、ロードします。
共有ライブラリのテキスト・セグメントは、 それを使用するすべてのプロセス間で共有されます。 データ・セグメントおよび BSS セグメントは、ページ単位で共有されます。
動的リンク共有ライブラリを使用する場合、 ライブラリ全体が、プロセスの仮想アドレス空間にマッピングされます。 しかし、ライブラリのテキスト・セグメントは、 プロセス毎に異なった仮想アドレスにマップされるかもしれないが、 そのコピーはメモリに1つしかありません。
プログラムが最初に共有ライブラリのデータまたは BSS ページをアクセスした時点で (読み込みまたは書き込み)、 そのページのコピーが、プロセスのために作成されます。 これをコピー・オン・ライトといいます。
プロセスが共有オブジェクトのページに書き込みを行った場合、 そのページのほかの利用者に影響を与えずに、 その内容を自由に書きかえることができるように、 システムはそのプロセスにページのコピーを割り当てます。
共有ライブラリは、ヒープとスタック・セグメントの間にありますが、 どの番地に割り当てられるかは、システムに依存します。
この動的リンク共有ライブラリの利点としては、次のものが挙げられます。
UNIX では、プロセス間で仮想メモリを共有するシステム・コールである mmap() を使用し、共有ライブラリをマップします。
共有ライブラリは、
cc コマンドから実行される ld コマンドが作成します。
一般的に拡張子は、.so ですが、HP-UX では、.sl です。
cc あるいは ld コマンドのオプションとしては、Solaris/HP cc では -G で、 Linux などで使われる gcc では -G も互換性のため使える場合もありますが、 -shared オプションがそれにあたります。
共有ライブラリの作成
cc -G XXX.c -o 共有ライブラリ名 (Solaris/HP cc) cc -G -xarch=v9 -xcode=pic32 XXX.c -o 共有ライブラリ名 (Solaris 64bit module) gcc -shared XXX.c -o 共有ライブラリ名 (gcc)
共有ライブラリとのリンク
共有ライブラリとのリンクには、2つの方法があります。
[Solaris cc] > cc オブジェクト名リスト -ldl フルパス指定の共有ライブラリ名 > cc オブジェクト名リスト -ldl -L 共有ライブラリの検索パス -l共有ライブラリ [HP-UX] > cc オブジェクト名リスト -ldld フルパス指定の共有ライブラリ名 > cc オブジェクト名リスト -ldld -L 共有ライブラリの検索パス -l共有ライブラリ
後者のリンク方法を使った場合、 プログラムを実行するときは、 LD_LIBRARY_PATH に共有ライブラリ検索パスを設定します。
共有メモリは、 プロセス間通信として最もシンプルで高速なもので、 メッセージやフラグとして使用するの便利です。
UNIX では、共有メモリを含むプロセス間通信機能をまとめて IPC(Inter Process communication) として管理しており、 メッセージキュ、共有メモリ、セマフォが含まれます。
ipcs コマンドで、それらの使用状況をみれます。
共有メモリの使用方法は、
共有メモリは無限にあるわけではありません。 OSにもよりますが、共有メモリのサイズは OS起動時に固定されます。 カーネル・チューニング・パラメータで変更することは可能ですが、 メモリ内の一部を占有することになりますので、 あまり大きく確保すると、プロセス用のメモリが減ります。
Solaris の場合、チューニング・パラメータは /etc/system にあり、 これを変更した場合は、リブートする必要があります。
Linux と Solaris では /proc というディレクトリがあります。 これは現在動いているプロセスのための擬似ディレクトリです。
この直下にある数字から成るディレクトリは PID (Process IDentifier) に対応するもので、 カーネルにあるプロセス制御テーブルを抽象化して見せるインタフェースです。 従って、サイズはディスク上のファイルではないため、0です。 そして、これらの擬似ファイルはカーネルの管理下にありますので、 削除できません。
プロセスが終了すると、/proc 配下からもなくなります。
PID を指定してこれらの情報を操作するツールがいくつかあります。 それらは、/usr/proc/bin/* (Solaris 2.5 以上)にあります。
なお、 /proc の構造体は /usr/include/procfs.h に定義されています。(solaris)
nagano-58] df -k Filesystem Kバイト used avail capacity Mounted on /dev/dsk/c0t0d0s0 192799 18776 154744 11% / /dev/dsk/c0t0d0s6 1015679 615130 339609 65% /usr /proc 0 0 0 0% /proc fd 0 0 0 0% /dev/fd /dev/dsk/c0t0d0s1 145135 55799 74823 43% /var /dev/dsk/c0t0d0s7 1083751 9 1029555 1% /home0
solaris 8 では SGA は Shared に含まれるように変更されました。 solaris 7 までは Private に含まれていました。
oracle の全プロセスが占めるメモリの概算は以下のとおりです。
oracle プロセスメモリ = Private * (oracle プロセス数) + Shared (+ PGA) |
---|
92032dio-5 9:44am] pmap -x 21701 21701: ora_cjq0_ora92032 Address Kbytes Resident Shared Private Permissions Mapped File 00010000 49136 25304 25304 - read/exec oracle text segment 0301A000 488 360 312 48 read/write/exec oracle data segment 03094000 288 160 120 40 read/write/exec [ heap ] 20000000 593920 593920 593920 - read/write/exec/shared [ ism shmid=0x141e5 ] SGA FEB00000 8 8 8 - read/write [ anon ] FEB10000 8 8 8 - read/write [ anon ] FEB50000 8 8 - 8 read/write [ anon ] FEB58000 48 48 8 40 read/write [ anon ] FEB68000 32 32 - 32 read/write [ anon ] FEBB0000 16 16 16 - read/exec libmp.so.2 FEBC4000 8 8 8 - read/write/exec libmp.so.2 FEBD0000 88 72 72 - read/exec libm.so.1 FEBF4000 8 8 8 - read/write/exec libm.so.1 FEC00000 4416 1216 1216 - read/exec libjox9.so FF05E000 192 168 168 - read/write/exec libjox9.so FF0A0000 8 8 8 - read/exec libkstat.so.1 FF0B2000 8 8 8 - read/write/exec libkstat.so.1 FF0C0000 24 24 24 - read/exec librt.so.1 FF0D6000 8 8 8 - read/write/exec librt.so.1 FF0E0000 32 24 24 - read/exec libaio.so.1 FF0F8000 8 8 8 - read/write/exec libaio.so.1 FF100000 688 632 632 - read/exec libc.so.1 FF1BC000 32 32 16 16 read/write/exec libc.so.1 FF1D0000 8 8 8 - read/write/exec [ anon ] FF1E0000 8 8 8 - read/exec libsched.so.1 FF1F2000 8 8 8 - read/write/exec libsched.so.1 FF200000 568 392 392 - read/exec libnsl.so.1 FF29E000 40 40 40 - read/write/exec libnsl.so.1 FF2A8000 24 16 16 - read/write/exec libnsl.so.1 FF2C0000 16 16 16 - read/exec libc_psr.so.1 FF2D0000 8 8 8 - read/write/exec/shared [ anon ] FF2E0000 8 8 - 8 read/write/exec [ anon ] FF2F0000 24 24 24 - read/exec libgen.so.1 FF306000 8 8 8 - read/write/exec libgen.so.1 FF310000 40 40 40 - read/exec libsocket.so.1 FF32A000 8 8 8 - read/write/exec libsocket.so.1 FF330000 8 8 8 - read/exec libskgxn9.so FF340000 8 8 8 - read/write/exec libskgxn9.so FF350000 8 8 8 - read/exec libskgxp9.so FF360000 8 8 8 - read/write/exec libskgxp9.so FF370000 8 8 8 - read/exec libodmd9.so FF380000 8 8 8 - read/write/exec libodmd9.so FF390000 8 8 8 - read/exec libdl.so.1 FF3A0000 8 8 - 8 read/write/exec [ anon ] FF3B0000 160 160 160 - read/exec ld.so.1 FF3E8000 8 8 - 8 read/write/exec ld.so.1 FFBE8000 32 32 16 16 read/write/exec [ stack ] stack segment -------- ------ ------ ------ ------ total Kb 650504 622928 622704 224
上記の出力項目の説明は以下のとおりです。単位は KB です。
項目名 | 説 明 | |
---|---|---|
Address | 仮想メモリ上のアドレス (*) | |
Kbytes | 全サイズ | |
Resident | 常駐部分のサイズ | |
Shared | 共有部分のサイズ | |
Private | 個別部分のサイズ | |
Permissions | メモリ属性 | |
read/exec | テキストセグメント(命令部分) | |
read/write/exec | データセグメントまたはスタックセグメント(データ領域) | |
read/write/exec/shared | 共用メモリ | |
Mapped File | マップしているファイル名 |
(*) 仮想メモリ上のアドレスとは、実行形式を全メモリに展開した場合のアドレスです。 UNIX のメモリ管理は、仮想記憶という概念を利用していますので、 仮想という表現しますが、これらは実際には存在しません。
プロセスがスムーズに動作しているかを知るには、 定時的に vmstat の情報を分析する必要があります。 vmstat は、プロセス、仮想メモリ、トラップ、CPUアクティビティの 正確な統計情報をレポートします。 特に注目すべき項目は赤で示します。
vmstat - 仮想メモリ統計情報のレポート
Solaris の場合の各カラムの見出しおよびその意味は、以下のとおりです。
procs | 各状態にあるプロセス数に関する情報 | |
---|---|---|
r | 実行待ち行列にあるプロセス数 | |
b | 入出力待ちでブロックされたプロセス数
(I/O、ページングなどのリソース) プロセス数が多いことは IOwait のプロセスが多いことです | |
w | スワップアウトされたプロセス数 パフォーマンスが低下しています | |
memory | 仮想メモリおよび実メモリの使用状況に関する情報。 仮想ページは、実行中であるか、または最後の 20 秒間動作しているプロセスに属する場合、アクティブであるとされる (単位 KB) | |
swap | 使用可能な未予約スワップ空間サイズ。 数 MB になると、プロセス起動不可 | |
free | 空き記憶領域 | |
page | ページフォルトとページングに関する情報(単位秒) メモリが不足すると po と sr が多くなる | |
re | フリーリストから利用されたページ数 | |
mf | 軽度のページフォルト数(ページはメモリ内にあるが、マップされていない) | |
pi |
ファイルシステムまたはスワップからページ・インされたページ(キロバイト) ページ・インが多いとパフォーマンスが低下します | |
po | ファイルシステムまたはスワップへページ・アウトされたページ(キロバイト) | |
fr | 削除または解放されたページ(キロバイト) | |
de | 書きこみ後に解放されたページ数 0 以外だとメモリ消費速度が速い | |
sr | ページ・デーモンがスキャンしたページ数 長時間0以外であると、メモリ不足です | |
disk | ディスク動作に関する情報(単位秒) | |
f0 | ディスク入出力数 | |
s1 | ディスク入出力数 | |
s2 | ディスク入出力数 | |
s3 | ディスク入出力数 | |
faults | トラップ/割込み率(単位秒) | |
in | デバイス割込み回数 | |
sy | システム・コール回数 | |
cs | cpu コンテクスト切り替え回数 | |
cpu | CPU の使用割合 | |
us | ユーザ | |
sy | システム | |
id | アイドル状態 |
ozzy-19] vmstat 5 5 procs memory page disk faults cpu r b w swap free re mf pi po fr de sr f0 s1 s1 s1 in sy cs us sy id 0 0 0 14008 21664 0 8 2 7 10 0 0 0 0 1 0 441 4008 611 1 2 98 0 0 0 2109624 52208 0 1 0 0 0 0 0 0 0 1 1 427 4367 729 0 1 98 0 0 0 2109624 52208 0 0 0 0 0 0 0 0 0 1 9 482 4349 730 0 1 98 0 0 0 2109624 52200 0 0 1 0 0 0 0 0 5 1 5 497 4389 735 0 4 96 0 0 0 2109592 52168 0 0 0 0 0 0 0 0 1 1 39 657 4393 728 0 3 97
/* Function : print each addresses FileName : mem.c */ #include <stdlib.h> #include <dlfcn.h> #include <unistd.h> #define SZ_LOOP 4 #define SZ_ALLOCA 512 void prthline(char),prtOSinfo(),recursive(int); extern _etext,_edata,_end; /* libc shared_library_name */ #ifdef HPUX char *libc = "/usr/lib/libc.sl"; #define OS_NAME "HP-UX" #else #ifdef LINUX char *libc = "/lib/libc.so.6"; #define OS_NAME "Linux" #else #ifdef SUN char *libc = "/usr/lib/libc.so"; #define OS_NAME "SunOS" #else char *libc = NULL; #define OS_NAME "Unknown OS. cc -DSUN/HPUX/LINUX" #endif /* SUN */ #endif /* Linux */ #endif /* HP */ int x1=1 ; int pg ; char *prgname; int main(int argc,char *argv[],char *envp[] ) { int i,x3 ; char *x4p ; void *hdl,*vp=0; prgname = argv[0]; if (libc == NULL) { printf("%s\n",OS_NAME); exit(12); } /* get address of malloc() in libc shared_library */ hdl = dlopen(libc,RTLD_LAZY); if (! hdl) printf(" dlopen : %s\n",dlerror()); else { vp = dlsym(hdl,"malloc"); if (! vp) printf(" dlsym : %s\n",dlerror()); } prtOSinfo(); prthline('='); printf(" 0 | \n" ); printf(" | &main = 0x%08x\t(text) \n",&main ); printf(" Etext | 0x%08x\t(text_end)\n",&_etext ); prthline('='); printf(" Data | &x1 = 0x%08x\t(data)\n",&x1 ); if (&pg < &_edata) { printf(" BSS | &pg = 0x%08x\t(bss)\n",&pg ); printf(" Edata | 0x%08x \n",&_edata ); } else { printf(" Edata | 0x%08x \n",&_edata ); printf(" BSS | &pg = 0x%08x\t(bss)\n",&pg ); } printf(" End | 0x%08x \n",&_end ); prthline('='); printf(" Heap | \n" ); for (i=0; i<SZ_LOOP; i++) { x4p = malloc(pg); /* allocate memory from the heap */ printf(" | x4p_%d = 0x%08x\t(heap)\n",i,x4p ); } prthline('='); printf(" ShrL | &malloc = 0x%08x\t(shr_library)\n",vp); prthline('='); recursive( 3 ); printf(" | :\n" ); printf(" | &x3 = 0x%08x\t(auto)\n",&x3 ); printf(" | &argc = 0x%08x\t(auto)\n",&argc ); printf(" | &argv = 0x%08x\t(auto)\n",&argv[0] ); printf(" | &envp = 0x%08x\t(auto)\n",&envp[0] ); printf(" Stack | \n"); prthline('='); exit(0); } void recursive(int n) { int x5 ; void *vp; vp = alloca(SZ_ALLOCA); /* allocate memory from the stack */ printf(" | &n = 0x%08x\t(auto,%d)\n",&n,n ); printf(" | &x5 = 0x%08x\t(auto,%d)\n",&x5,n ); printf(" | vp = 0x%08x\t(auto,%d)\n",vp ,n ); prthline('-'); if ( n<=0 ) return; recursive( n-1 ); } #define SZ_CNT 45 void prthline(char ch) { int i; printf(" *"); for (i=0; i<SZ_CNT; i++) printf("%c",ch); printf("\n"); } void prtOSinfo() { /* get page_size */ pg = (int) sysconf(_SC_PAGESIZE); if (pg >=0 ) { printf("\tOS name : %s\t\tPage_size : %ld(0x%04x) Bytes\n\n", OS_NAME,pg,pg); } else perror(prgname); } |
base.c と main.c は、以下のとおりです。
base.o から共用ライブラリと静的ライブラリを作ります。
mains は共用ライブラリとリンクしますが、maina は静的ライブラリとリンクします。
/* Function : print each addresses FileName : main.c */ #include <stdio.h> #include <dlfcn.h> extern int etext, edata, end ; extern void prthline(); extern int base(); #define SZ_BUF 0x100 /* libc shared_library_name */ #ifdef HPUX char *libc = "/usr/lib/libc.sl"; #else #ifdef LINUX char *libc = "/lib/libc.so.6"; #else #ifdef SUN char *libc = "/usr/lib/libc.so"; #endif /* SUN */ #endif /* Linux */ #endif /* HPUX */ static char *head1= " *------ main address (size) ---------------\n 0 | \n"; int main(int argc,char *argv[],char *envp[]) { char buf[SZ_BUF]; int *fp2; void *vp=0,*hdl; fp2 = (int *)&base; /* get address of malloc() in libc shared_library */ hdl = dlopen(libc,RTLD_LAZY); if (! hdl) printf("dlopen : %s\n",dlerror()); else { vp = dlsym(hdl,"printf"); if (! vp) printf("dlsym : %s\n",dlerror()); } printf(head1); printf(" | &main = 0x%08x\t\t(text) \n",&main ); printf(" | &base = 0x%08x\t\t(text) \n",fp2 ); printf(" Etext | 0x%08x\t\t(text_end)\n",&etext ); prthline(); printf(" Data | \n"); printf(" BSS | &head1 = 0x%08x\t\t(bss)\n",head1 ); printf(" Edata | 0x%08x \n",&edata ); printf(" End | 0x%08x \n",&end ); prthline(); printf(" ShrL | &printf = 0x%08x\t\t(shr_library)\n",vp); prthline(); printf(" | &buf = 0x%08x(0x%04x)\t(auto)\n", &buf[0],sizeof(buf) ); printf(" | &argc = 0x%08x(0x%04x)\t(auto)\n", &argc ,sizeof(argc)); printf(" | &argv = 0x%08x\t\t(auto)\n",&argv[0] ); printf(" | &envp = 0x%08x\t\t(auto)\n",&envp[0] ); printf(" Stack | \n"); prthline(); base(); printf("\nInput return_key , then end"); fgets(buf,SZ_BUF,stdin); exit(0); } |
非常に単純なサンプルですが、
送信プログラムを実行するには、受信プログラムが起動時に表示する、 共有メモリ ID を、引数として指定する必要があります。
/* Function : create shared_memory and read it FileName : shmc.c */ #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define SZ_SHR 0x200 void die_err(char *); int main() { int id; /* id of shared_memory */ char *adr; /* body of shared_memory */ if ((id=shmget(IPC_PRIVATE,SZ_SHR,IPC_CREAT|0666))==-1) { die_err("shmget"); } if ((int)(adr=shmat(id,0,0)) == -1) { die_err("shmat"); } else { printf("shared_buf = 0x%08x\n\n",adr); printf("共有メモリ ID = %d\n",id); while(1) { printf("%s",adr); if (strcmp(adr,"end\n") == 0) break ; sleep(3); } if (shmdt(adr) == -1) die_err("shmdt"); } if (shmctl(id,IPC_RMID,0) == -1) { die_err("shmctl"); } } void die_err(char *msg) { perror(msg); exit(1); } |
セマフォの初期値は 1 で、最初にやって来たプロセスは、 そのカウントが正であることを確認し、そこから 1 を引いて 0 にします。 これでプロセスは、コードかリソースの重要部分を占有したことになり、 それがセマフォによって保護されます。 プロセスがその重要な領域を離れるとき、 セマフォのカウントをインクリメントします。
最善のケースは、その重要領域を所持しようとする他のプロセスが存在しない場合です。
下記の例では、LOCK/LOCK するとデッドロックを体験できます。
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #define SZ_BUF 128 union semun { int val; struct semid_ds *buf; ushort *array; } c_arg; /* semaphore control info */ int sem_get(char *path) { int sid; if ((sid=semget(ftok(path,0),1,0666|IPC_CREAT))== -1) { die_err("semget"); } if (semctl(sid,0,GETVAL,c_arg)==0) { c_arg.val=1; if (semctl(sid,0,SETVAL,c_arg)==-1) { die_err("semctl"); } } else die_err("semctl"); return(sid); } int sem_lock(int sid) { struct sembuf sb; sb.sem_num=0; sb.sem_op=-1; sb.sem_flg=0; if (semop(sid,&sb,1)== -1) { die_err("semop"); } return(1); } int sem_unlock(int sid) { struct sembuf sb; sb.sem_num=0; sb.sem_op=1; sb.sem_flg=0; if (semop(sid,&sb,1)== -1) { die_err("semop"); } return(1); } int main() { int sid,ret; char buf[SZ_BUF]; setbuf(stdout,NULL); if ((sid=sem_get("/tmp/aaa"))==-1) { exit(-1); } printf("sid=%d\n",sid); while(1){ ret = semctl(sid,0,GETVAL,c_arg); printf("<%d> [L]ock / [U]nlock : ",ret); if (fgets(buf,SZ_BUF,stdin) != NULL) { if (tolower(buf[0])=='l') { ret = sem_lock(sid); printf("sem_lock()=%d\n",ret); } else if (tolower(buf[0])=='u') { ret = sem_unlock(sid); printf("sem_unlock()=%d\n",ret); } } } } |
ipcs(1),ldd(1),ld.so.1(1),cc(1),ld(1),ar(1),ipcrm(1),vmstat(1M),proc(1),pagesize(1)
mmap(2),shmctl(2), shmop(2),shmdt(2)
end(3C),malloc(3C),alloca(3C),valloc(3C),free(3C),dlopen(3X),dlsym(3X),dlerror(3X),dlclose(3X)
a.out(4),libdl(4),core(4)
environ(5)
ipcs(1),ldd(1),ld.so.1(1),cc(1),ld(1),ar(1),ipcrm(1),vmstat(1M)
mmap(2),shmctl(2), shmop(2),shmdt(2)
end(3C),malloc(3C),alloca(3C),valloc(3C),free(3C),dlopen(3C),dlsym(3C),dlerror(3C),dlclose(3C)
a.out(4),dld.sl(4),core(4)
environ(5)