- ベストアンサー
非再入可能なプログラム
非再入可能なプログラムの意義がわかりません。 別にどのプログラムも再入可能にしておけばいいと思うのですが 非再入可能にする意味ってあるのでしょうか? 呼出したプログラムが、ちゃんと呼び出し元に戻らないと どんなプログラムでも困ると思うのですが。。
- みんなの回答 (11)
- 専門家の回答
質問者が選んだベストアンサー
いちおう、最初に言い訳。 汎用機は学生の時の「言語実習」で Pascal のプログラムを作らされた時に使ったことがあるだけなので、「まったく」詳しくありません。(そのときも、あまりのレスポンスの悪さに、自宅でデバッグまでしてからエディタで入力して課題を提出していました) ミニコンはクラブ保有のものがありましたが、「起動と停止がめんどくさい」ので、パソコン上で OS を使ってコンパイラやアセンブラを使っていました。(当時は、BASIC インタープリタを使うことが、プログラムだと信じられていましたが) さて、 「ただ、このバッチから呼ばれる共通ルーチン(アベンドルーチンなど)はどのモジュールから呼ばれることもあります」 アベンドは、Abnormal End ですね? OS レベルに近い、もしくは、OS 内部というのは、実はリエントラントになっていない部分が存在しています。OS はマルチタスク、マルチスレッドをサポートしていても、です。これは「プログラムがめんどくさいから」ではなくて「逐次処理されることを保証したいから」です。つまり、「同時に呼び出しても、必ずどちらかの処理を先に処理してから次の処理を実行する」というふうにさせたいわけです。 プログラマレベルでは「好きな時に呼び出してかまわないルーチン」のように見えますが、実際には逐次処理されることになります。(そのルーチンに飛び込む前にたとえばセマフォを獲得しないといけないように OS 内部を作っておく、セマフォを獲得できないときはそこでスリープして、先に入ったプログラムがルーチンを抜けてセマフォを解放すると、次のプログラムがセマフォを獲得してルーチンに突入できる、とか) このようなルーチンであれば、リエントラント可能に作る必要はありません。 もう一度簡単にまとめると、「プログラマからは同時呼び出し可能なルーチンに見えても、実際に内部構造がそうなっているとは限らない」となります。 OS のレスポンスを上げるためにはこういうプリミティブな「クリティカルセクション」をいかに少なくできるか、というところがあります。 ですから「変数域の上書きがされる可能性があると思ってよいのでしょうか?」という心配はしなくてもいいと思います。仕様書にしたがって呼び出せば、OS 側で「よしなにはかって」くれますよ。 追記 さきほど、ルーチンをリエントラントとしない理由として、「メモリの節約」をあげるのを忘れていました。仮想記憶があるとはいえ、汎用機では「潤沢なメモリ」を前提にプログラムを書くことはできないかもしれません。(この文章を書いているパソコンは 6GB のメモリを積んでいて、フリーメモリが 4GB 以上もありますし、最近プログラムしていても「とりあえず 4GB」はメモリがあると思っていてかまわないので、ついついメモリを節約することを失念していました)
その他の回答 (10)
- chukenkenkou
- ベストアンサー率43% (833/1926)
#10です。 #9の(2)を少し訂正。 (2)リユーザブル ディスク上のライブラリから一旦ローカルな領域に上げたら、再度、呼び出した場合、ローカルな領域内に既に上げているプログラムを再使用する。 プログラムの処理部分で作業領域の初期化を行っていないと、前回の復帰時の作業領域の内容がそのまま残っている。 例えば5個のバッチジョブを同時実行し、~以降は変更ありません。
- chukenkenkou
- ベストアンサー率43% (833/1926)
#7、#9です。 整理します。 (1)非リユーザブル 毎回、ディスク上のライブラリから、ローカル領域に上がる。 (2)リユーザブル ディスク上のライブラリから一旦ローカルな領域に上げたら、再度、呼び出した場合、前回の復帰時の作業領域の内容がそのまま残っている。 例えば5個のバッチジョブを同時実行し、ディスク上のライブラリにある同じサブルーチンを呼んだ場合、各バッチジョブが動いているローカル領域にそのサブルーチンがそれぞれ上がる。 →ディスク上のライブラリで見れば、同じサブルーチンが共用されているが、メモリ上での共用ではない。 (3)リエントラント ディスク上のライブラリから、システム共通領域(あるいはマルチタスクが動くローカル領域)に上がり、処理部はマルチタスクでメモリ上共用されるが、作業領域は各タスク毎に独立した領域を確保・解放する。 →DBMSなどメモリ常駐で動くプログラムでは、初期処理でOSレベルの作業領域の確保を行い、その後に繰り返し動く多様なサブルーチンでは、その領域を使い回すといったことを行っている。 メモリ上での共用がないのに、不必要にリエントラントにすると、作業領域の確保・解放が繰り返され、フラグメンテーションの重大要因になる。
- chukenkenkou
- ベストアンサー率43% (833/1926)
#7回答者です。 #6さんへの追加質問ですが、汎用機の経験が長かったので横から回答します。 >ただ、このバッチから呼ばれる共通ルーチン(アベンドルーチンなど)は >どのモジュールから呼ばれることもあります。 >この共通ルーチンが動的に呼ばれる場合、共通ルーチンがロード上に >展開されますが、リエントラント仕様でないと変数域の上書きが >される可能性があると思ってよいのでしょうか? >それとも、複数のモジュールから呼ばれてメモリ上に展開する場合は、 >それぞれ別の領域に展開されるのでしょうか?(リエントラント不要) バッチなどは、すべてジョブ固有領域といったローカルな領域で実行され、そこから呼ばれるサブルーチンなども、システム共通領域に事前に上げておくといったことをしなければ、ローカルな領域にローディングされます。 DB/DC製品、DBなどとのミドルなどでずっとメモリ常駐された状態で、マルチタスクでオンラインなりバッチなりから繰り返し要求を受けて処理するようなプログラムは、リエントラントである必要があります。 逆にマルチタスクで動かないのに、不要にリエントラントにしたサブルーチンを繰り返しバッチやオンラインのユーザ空間から呼んだりすると、処理部分とは別に確保される変数などの作業領域が繰り返し確保・解放されることになり、フラグメンテーションが多発します。
- chukenkenkou
- ベストアンサー率43% (833/1926)
リエントラントの場合、処理部分はメモリ上で共用され、変数などの作業領域はリエントラントされる度に個々に確保・解放されます。 つまり、n回のリエントラントが発生した場合、所要メモリは、処理部分長+リエンラント回数×作業領域長になります。 そのため、リエントラント前提のプログラムは、変数宣言などの時に初期値を設定するのは意味がなく(コンパイラによっては、エラーになる場合もある)、処理部分で初期設定する必要があります。 プログラムを、ロードライブラリ(ハードディスク)からメモリに上げる処理は、かなりオーバーヘッドになります。そのため、繰り返し使用されるプログラムを、事前にメモリ上に上げメモリ常駐させて繰り返し使用するプログラムが、リエントラントに適しています。 その一方で、繰り返し作業領域の確保・解放が行われるので、フラグメンテーションが起こるというデメリットがあります。
- GOOD-Fr
- ベストアンサー率32% (83/256)
マシン語を使える人なら、実は疑問に思いません。 「非再入可能」というのは、一般用語なのかもしれませんが、意味不明な日本語なので「リエントラント可能」と「リエントラント可能でない」と言い換えて説明します。 リエントラント可能でなくてもよい場合、あるサブルーチン、関数が使う変数はどこに取ってもかまいません。簡単に言えば「アドレス固定」でかまわないのです。つまり、「Aという変数は常に100番地にある」という考え方です。 が、リエントラントなプログラムではそうはいきません。このルーチンを半分ぐらい実行しているうちに、他のところからもこのルーチンを呼び出されてしまう、というのが、「リエントラント可能」ですから、「A という変数はいつも 100番地」というのは、都合が悪いのです。ふたつのルーチンで同一番地にアクセスしてお互いに書き換えてしまうわけですから。 これを回避するには、このルーチンに飛び込んでくるたびに「同じ名前の変数であっても違うアドレスが割り振られる」ことを保証する必要があります。通常、このためにはスタック上にフレームを生成し、フレームポインタからの相対アドレッシングを行なう必要があります。要するに「めんどくさい」のです。 ずいぶん昔の「計算機」には、このようなアドレッシングを簡単に行なえないものがあったため、リエントラントなルーチンを処理させることは「不必要に重い」処理であり、「できれば回避したい」というのが「プログラマにわざわざ指定させる」本当の意味だと思います。 もちろん、現在では C などをはじめとして「ローカル変数」という考え方を持つ言語が一般的であるため、フレームポインタを使った相対アドレッシングはほとんどの CPU でインプリメントされています。x86 でいえば、mov eax,[ebp+ebx+4*esi]というような命令もありますが、これはスタック上に作られたアドレス空間を指す EBP レジスタをフレームポインタとして使い、ローカル変数として定義された配列の先頭相対アドレスを EBX に格納しているときに、ESI 番目の配列要素をひとつの配列要素のサイズが 4バイトであるとしてアクセスし、EAX レジスタに格納するための命令です。 このへんの仕組みは、コンパイラを作成してみればいやでも理解できるようになると思います。 余談ですが、再帰可能なプログラムはリエントラントなプログラムですが、逆は必ずしも真ではありません。
- tatsu99
- ベストアンサー率52% (391/751)
>頂いた回答にはプログラムが面倒というだけで、 >それさえ我慢すれば、RENTで問題なしと言っているように >思えますが違うのでしょうか? その通りです。 但し、私が回答したのは、コンパイルオプションで指定するのではなく、実際に作り込むときに、どちらの仕様で作るかを想定した場合のケースです。最近、COBOL、アセンブラから遠ざかっているので、以下は私の想像ですが、コンパイルオプションで非リエントラントを指定するメリットは、メモリを余り消費しないですむ、ということにあると、思っています。というのは、リエントラントの場合、全てのスレッド用に作業領域を確保する必要がありますが、非リエントラントとであれば、その必要はありません。また、内部で確保したメモリを自由に使い回しが出来ますので、メモリの使用量が少なくてすみます。 しかしながら、メモリの使用量を気にするのは、古き良き時代の話であり、最近はそのことを気にする必要がなくなっていますので、リエントラントを指定しておけば、問題ないと思います。只、自分の作るプログラムがマルチスレッド(昔はマルチタスクと言ってましたが)であることを要求されないのなら、コンパイル時に非リエントラントを指定しても問題ありません。
お礼
ありがとうございます。非常にわかりました。 メモリの使用量は、おっしゃるとおりですね。 さて、もう1つだけ、より現実的な話なのですが 汎用機において通常のバッチモジュールは、jobスケジュールに 従いシリアルに流れることが多いです。 この場合、リエントラントは不要であると思います。 ただ、このバッチから呼ばれる共通ルーチン(アベンドルーチンなど)は どのモジュールから呼ばれることもあります。 この共通ルーチンが動的に呼ばれる場合、共通ルーチンがロード上に 展開されますが、リエントラント仕様でないと変数域の上書きが される可能性があると思ってよいのでしょうか? それとも、複数のモジュールから呼ばれてメモリ上に展開する場合は、 それぞれ別の領域に展開されるのでしょうか?(リエントラント不要)
絶対に複数から非同期で呼ばれることが無く再入可能にする意味が無いもの ・ブートローダーやOSのカーネルの初期化終了処理 非再入可能にせざるをえないもの ・組み込みなどでそのために割り当てるハードウェアリソースがない 歴史的事情 ・マルチスレッドなんてない時代に定義されて仕様上もう変えることが出来ないもの
お礼
とてもわかりました。 非再入可能にあえてする意味は、普通のプログラムにおいては あまりないと思ってよさそうですね。
- tatsu99
- ベストアンサー率52% (391/751)
まず、非再入可能の意味ですが、 「呼出したプログラムが、ちゃんと呼び出し元に戻らない」という意味ではありません。 非再入可能なプログラムも再入可能なプログラムのどちらも、きちんと、呼び出し元に戻ります。 では、再入可能とは何なのかと言うことですが、 これは、いまの言葉で言えば、スレッドセーフに該当します。つまり、複数のスレッドから、同時に呼ばれても、きちんと動作することを保証します。 非再入可能なプログラムのばあいは、複数のスレッドから同時に呼ばれた場合、正しく動作しません。 マルチスレッドプログラミングの場合は、再入可能かどうかが、非常に重要になりますが、一般的な、プログラミングの場合は、非再入可能なプログラムも再入可能なプログラムのどちらを使用しても、同じ事なので、あまり再入可能かどうかを気にしません。 非再入可能なプログラムの意義は、プログラムが簡単に作れるということです。再入可能なプログラムは、複数のスレッドから同時に呼び出された場合でも、正しい結果を保証しなければならないので、マルチスレッドに関する深い知識がないと、作れません。
お礼
回答ありがとうございます。 すみません。「元に戻らない」といった記載は間違いです。 中身が壊れることがあるということで記載したつもりでした。 さて、ちょっと古臭い言語で記載させて頂きますが、 COBOLやアセンブラのプログラムをコンパイル(アセンブル)する際に RENT(リエントラント)を指定するか,NORENTを指定するかを決めますが、 バッチであろうが、オンラインであろうが、 とりあえずRENTにしてればいいのではないか?と思うのですが、 どうなのでしょうか? アセンブラなら、GETMAINなどをソース上で指定しますが COBOLなどは勝手にコンパイルで保管してくれるなら RENT指定しておけば問題ないのではないかと思います。 頂いた回答にはプログラムが面倒というだけで、 それさえ我慢すれば、RENTで問題なしと言っているように 思えますが違うのでしょうか?
補足
また間違えました。 ≫複数スレッドからの処理の場合、中身が壊れる(上書きされる)ことがあるです。
- equinox2
- ベストアンサー率48% (321/660)
「再入可能」の意味を取り違えていませんか? http://ja.wikipedia.org/wiki/%E3%83%AA%E3%82%A8%E3%83%B3%E3%83%88%E3%83%A9%E3%83%B3%E3%83%88
お礼
すみません。 間違えですね。 それ以外は、No3の回答に記載します。
- Tacosan
- ベストアンサー率23% (3656/15482)
「再入可能」という言葉の意味を確認してください. すくなくとも「ちゃんと元に戻る」という意味ではありません.
お礼
すみません。 間違えですね。 それ以外は、No2の回答に記載します。
補足
ごめんなさい。 No3の回答に記載します。 ありがとうございました。
お礼
ありがとうございます。ずいぶんと詳しいですね。 x86のアセンブラではなく汎用機のアセンブラを利用していますが、 意味は十分理解しました。ただ、もう少し不安なので、より現実的な 質問をさせてください。 汎用機において通常のバッチモジュールは、jobスケジュールに 従いシリアルに流れることが多いです。 この場合、リエントラントは不要であると思います。 ただ、このバッチから呼ばれる共通ルーチン(アベンドルーチンなど)は どのモジュールから呼ばれることもあります。 この共通ルーチンが動的に呼ばれる場合、共通ルーチンがロード上に 展開されますが、リエントラント仕様でないと変数域の上書きが される可能性があると思ってよいのでしょうか? それとも、複数のモジュールから呼ばれてメモリ上に展開する場合は、 それぞれ別の領域に展開されるのでしょうか?(リエントラント不要)