プログラムを開くと、オペレーティングシステムは独自のマシンコードを挿入しますか?


32

私はCPUを研究していますが、メモリからプログラムを読み取り、その命令を実行する方法を知っています。また、OSはプログラムをプロセス内で分離し、各プログラムを非常に高速に切り替えて、同時に実行していると考えますが、実際には各プログラムはCPUで単独で実行されることも理解しています。しかし、OSがCPUで実行される大量のコードである場合、どのようにプロセスを管理できますか?

私は考えてきましたが、考えられる唯一の説明は、OSが外部メモリからRAMにプログラムをロードすると、元のプログラム命令の途中に独自の命令を追加するため、プログラムが実行され、プログラムがOSを呼び出して、いくつかのことを実行できます。OSがプログラムに追加する命令があり、CPUがOSコードにいつか戻ることができると信じています。また、OSがプログラムを読み込むと、禁止されている命令(メモリ内の禁止されたアドレスにジャンプする)があるかどうかをチェックし、それを排除すると信じています。

私はきちんと考えていますか?私はCSの学生ではありませんが、実際には数学の学生です。可能であれば、OSがCPUで実行される大量のコードであり、同時に実行できない場合、OSがプロセスを管理する方法を説明する人を見つけられなかったため、これに関する良い本が欲しいでしょう。プログラムの時間。これらの本は、OSが物事を管理できることだけを伝えていますが、今ではその方法を伝えています。


7
参照:コンテキストスイッチ OSはアプリへのコンテキストスイッチを行います。アプリは、OSにコンテキストを戻すOSサービスを要求できます。アプリが終了すると、コンテキストはOSに戻ります。
ガイコーダー14

4
「syscall」も参照してください。
ラファエル


1
コメントと回答があなたの質問にあなたの理解や満足に答えない場合は、コメントとしてより多くの情報を求め、あなたが考えていることや失われていること、具体的にあなたがより詳細に必要なものを説明してください。
ガイコーダー14

2
私が思う割り込みフック、(割り込みの)ハードウェアタイマ(とスケジューリング -handlingフック)とページング(禁じられたメモリ上のあなたの発言への部分的な答えは)あなたが必要とする主なキーワードです。OSは、必要な場合にのみコードを実行するために、プロセッサと本当に密接に協力する必要があります。したがって、CPUパワーのほとんどは、その管理ではなく、実際の計算に使用できます。
パレック14

回答:


35

いいえ。オペレーティングシステムは、プログラムのコードが新しいコードを挿入することに干渉しません。それには多くの欠点があります。

  1. OSは実行可能ファイル全体をスキャンして変更を加える必要があるため、時間がかかります。通常、実行可能ファイルの一部は必要な場合にのみロードされます。また、挿入することは、大量のものを邪魔にならないように移動する必要があるため、費用がかかります。

  2. 停止する問題は決定不能であるため、「OSにジャンプ」命令を挿入する場所を知ることは不可能です。たとえば、コードにのようなものが含まれている場合、while (true) {i++;}そのループ内にフックを挿入する必要がありますが、ループの条件(trueここでは)は任意に複雑になる可能性があるため、ループの長さを決定できません。一方、すべてのループにフックを挿入することは非常に非効率的です。たとえば、中にOSに戻るとfor (i=0; i<3; i++) {j=j+i;}、プロセスの速度が大幅に低下します。また、同じ理由で、短いループを検出してそれらをそのままにすることはできません。

  3. 停止する問題は決定不能であるため、コードインジェクションがプログラムの意味を変更したかどうかを知ることはできません。たとえば、Cプログラムで関数ポインターを使用するとします。新しいコードを挿入すると、関数の場所が移動するため、ポインタを使用して関数を呼び出すと、間違った場所にジャンプしてしまいます。プログラマーが計算されたジャンプを使用するのに十分な病気であれば、それらも失敗します。

  4. ウイルスコードも変更され、すべてのチェックサムが破壊されるため、どのウイルス対策システムでも陽気な結果になります。

コードをシミュレートし、特定の固定回数以上実行するループにフックを挿入することで、停止の問題を回避できます。ただし、実行するには、プログラム全体の非常に高価なシミュレーションが必要になります。

実際、コードを挿入したい場合は、コンパイラがそれを行うのに自然な場所になります。そうすれば、それを一度行うだけで済みますが、それでも上記の2番目と3番目の理由で機能しません。(そして、誰かが一緒に動作しないコンパイラを書くことができました。)

OSがプロセスから制御を取り戻すには、主に3つの方法があります。

  1. 協力的な(またはプリエンプティブでない)システムyieldには、OSに制御を戻すためにプロセスが呼び出すことができる機能があります。もちろん、それがあなたの唯一のメカニズムであるなら、あなたはプロセスがうまく振る舞うことに依存しており、降伏しないプロセスは終了するまでCPUを独占します。

  2. その問題を回避するために、タイマー割り込みが使用されます。CPUを使用すると、OSはCPUが実装するさまざまな種類の割り込みすべてに対してコールバックを登録できます。OSはこのメカニズムを使用して、定期的に起動されるタイマー割り込みのコールバックを登録し、独自のコードを実行できるようにします。

  3. プロセスがファイルからの読み取りまたは他の方法でハードウェアとのやり取りを試みるたびに、OSにその処理を実行するように要求します。OSがプロセスによって何かを行うように求められた場合、OSはそのプロセスを保留にして、別のプロセスの実行を開始することを決定できます。これは少しマキャベリアンに聞こえるかもしれませんが、それは正しいことです:ディスクI / Oが遅いので、プロセスAが回転する金属塊が適切な場所に移動するのを待っている間にプロセスBを実行させることもできます。ネットワークI / Oはさらに遅くなります。キーボードI / Oは氷河期です。なぜなら人々はギガヘルツの人間ではないからです。


5
あなたは最後の2.ポイントをさらに開発できますか?私はこの質問に興味があり、説明はここでは省略されていると感じています。質問は「OSがプロセスからCPUをどのように取り戻すか」であり、あなたの答えは「OSがそれを処理する」と言うように思えます。でもどうやって?最初の例の無限ループを考えてみましょう。コンピューターをフリーズさせない方法は?
BiAiB 14

3
OSによっては、ほとんどのOSが少なくとも「リンク」を行うコードを台無しにしているため、プログラムは任意のアドレスにロードできます
イアンリングローズ14

1
ここでのキーワードは「割り込み」です。CPUは、命令の特定のストリームを処理するだけのものではなく、別のソースから非同期的に割り込むこともできます。最も重要なのは、I / Oおよびクロック割り込みです。カーネル空間コードのみが割り込みを処理できるため、Windowsは実行中のプロセスからいつでも作業を「盗む」ことができます。割り込みハンドラーは、「CPUのレジスタをどこかに保存し、ここ(別のスレッド)から復元する」など、任意のコードを実行できます。非常に単純化されていますが、それがコンテキストスイッチです。
ルアーン

1
この回答に追加; ポイント2および3で参照されるマルチタスクのスタイルは「プリエンプティブマルチタスク」と呼ばれ、名前は実行中のプロセスをプリエンプトするOSの機能を指します。古いオペレーティングシステムでは、協調マルチタスクが頻繁に使用されていました。Windowsでは、少なくともプリエンプティブマルチタスクはWindows 95まで導入されていませんでした。今日使用されている産業用制御システムの少なくとも1つを読んでいます。
ジェイソンC 14

3
@BiAiB実際、あなたは間違っています。i486以来、デスクトップCPUはコードを連続して同期的に実行しません。ただし、古いCPUでさえ非同期入力(割り込み)が残っていました。CPU自体のピンのようなハードウェア割り込み要求(IRQ)を想像してください-取得する1と、CPUは実行中の処理をすべて停止し、割り込みの処理を開始します(基本的に「状態を保持してメモリ内のアドレスにジャンプする」ことを意味します)。割り込み処理自体はx86コードではなく、文字通りハードコードされています。ジャンプした後、再び(任意の)x86コードを実行します。スレッドは、より高度な抽象化です。
ルアン14

12

David Richerbyの答えは良いものですが、最新のオペレーティングシステムが既存のプログラムを停止する方法については、ちょっとした見方をしています。私の答えは、デスクトップおよびラップトップで一般的に使用されている唯一のアーキテクチャであるx86またはx86_64アーキテクチャに対して正確でなければなりません。他のアーキテクチャには、これを実現する同様の方法が必要です。

オペレーティングシステムの起動時に、割り込みテーブルが設定されます。テーブルの各エントリは、オペレーティングシステム内の少しのコードを指します。CPUによって制御される割り込みが発生すると、このテーブルを参照してコードを呼び出します。ゼロ除算、無効なコード、オペレーティングシステムで定義されたものなど、さまざまな割り込みがあります。

これは、ユーザープロセスがカーネルと対話する方法です。たとえば、ディスクまたはオペレーティングシステムカーネルが制御する何かを読み書きする場合です。オペレーティングシステムは、終了時に割り込みを呼び出すタイマーも設定するため、実行中のコードはユーザープログラムからオペレーティングシステムカーネルに強制的に変更され、カーネルは実行する他のプログラムをキューに入れるなど、他のことを行うことができます。

メモリから、これが発生すると、オペレーティングシステムカーネルはコードの場所を保存する必要があり、カーネルが必要な処理を完了すると、プログラムの以前の状態が復元されます。したがって、プログラムは、それが中断されたことを知りません。

プロセスは2つの理由で割り込みテーブルを変更できません。1つは、保護された環境で実行されているため、特定の保護されたアセンブリコードを呼び出そうとすると、CPUが別の割り込みをトリガーします。2番目の理由は仮想メモリです。割り込みテーブルの場所は実メモリの0x0〜0x3FFにありますが、ユーザープロセスでは通常その場所はマップされず、マップされていないメモリを読み取ろうとすると別の割り込みがトリガーされます。 、ユーザープロセスは変更できません。


4
割り込みはオペレーティングシステムによって定義されるのではなく、ハードウェアによって与えられます。また、現在のほとんどのアーキテクチャには、オペレーティングシステムを呼び出すための特別な指示があります。i386はこのために(ソフトウェアで生成された)割り込みを使用しましたが、後継機ではもうそのようには行われません。
フォンブランド14

2
割り込みはCPUによって定義されますが、カーネルはポインターを設定します。私はおそらくそれをひどく説明した。Linuxはint 9を使用してカーネルと通信することも考えましたが、今はもっと良い方法があるかもしれません。
Programmdude 14

これはかなり誤解を招く答えです。ただし、プリエンプティブスケジューラはタイマー割り込みによって駆動されるという考え方は正しいものです。まず、タイマーがハードウェアにあることに注意する価値があります。また、 "save ... restore"プロセスはコンテキストスイッチと呼ばれ、主にすべてのCPUレジスタ(命令ポインターを含む)の保存などを含むことを明確にするために。また、プロセス割り込みテーブル効果的に変更できます。これは「保護モード」と呼ばれ、仮想メモリも定義します。286以降、割り込みテーブルへのポインタは書き込み可能なレジスタに格納されます。
ジェイソンC 14

(リアルモード割り込みテーブルも8086以降、メモリの最初のページにロックされていないため、再配置可能です。)
ジェイソンC 14

1
この答えは、重要な詳細を見落としています。割り込みが発生すると、CPUはカーネルに直接切り替わりません。代わりに、最初に既存のレジスタを保存し、次に別のスタックに切り替えてから、カーネルが呼び出されます。ランダムプログラムからランダムスタックでカーネルを呼び出すのは、かなり悪い考えです。また、最後の部分は誤解を招くものです。マップされていないメモリを読み取るための割り込み「試行」はありません。単に不可能です。仮想アドレスから読み取り、マップされていないメモリには仮想アドレスがありません。
MSalters

5

OSカーネルは、プロセスにコードを挿入するのではなく、CPUクロック割り込みハンドラーにより、実行中のプロセスから制御を取り戻します。

割り込みの仕組みとOSカーネルが割り込みを処理し、さまざまな機能を実装する方法についてより明確にするために、割り込みについて読む必要があります。


クロック割り込みだけでなく、あらゆる割り込み。また、モード変更の指示もあります。
ジル「SO-悪であるのをやめる」14

3

そこです:あなたが記述するものと同様の方法協同マルチタスキングが。OSは命令を挿入しませんが、各プログラムは、別の協調プロセスを実行することを選択できるOS機能を呼び出すように作成する必要があります。これには、あなたが説明する欠点があります。1つのプログラムがクラッシュすると、システム全体が削除されます。3.0までのWindowsはこのように機能しました。3.0は「保護モード」以上ではありませんでした。

プリエンプティブマルチタスク(最近の通常の種類)は、外部の割り込みソースに依存しています。割り込みは通常の制御フローを無効にし、通常はレジスタをどこかに保存します。そのため、CPUは何か他のことをして、透過的にプログラムを再開できます。もちろん、オペレーティングシステムは「割り込みをここから再開する」レジスタを変更できるため、別のプロセス内で再開します。

(一部のシステムで、プログラムのロード時に「サンキング」と呼ばれる制限された方法で命令を書き換え、Transmetaプロセッサは独自の命令セットに動的に再コンパイルします)


AFAICR 3.1も協力的でした。Win95は、プリエンプティブマルチタスクが登場した場所でした。保護モードは、主にアドレス空間の分離をもたらしました(これにより、安定性は向上しますが、ほとんど無関係な理由によります)。
cHao 14

サンクは、アプリケーションにコードを書き直したり注入したりしません。変更されるローダーはOSベースであり、アプリケーションの製品ではありません。JITコンパイラーなどを使用してコンパイルされた解釈言語は、コードを変更したり、コードに何かを挿入したりしません。ソースコードを実行可能ファイルに変換します。繰り返しますが、これはアプリケーションにコードを注入することと同じではありません。
デイブゴードン14

Transmetaは、解釈言語ではなく、x86実行可能コードをソースとして使用しました。そして、私はコード注入される1つのケースを考えました:デバッガーの下で実行します。X86システムは通常、ブレークポイントで命令を「INT 03」で上書きします。これはデバッガーにトラップされます。再開すると、元のオペコードが復元されます。
pjc50 14

デバッグは、だれでもアプリケーションを実行する方法ではありません。アプリケーションの開発者のそれを超えています。だから、それが本当にOPに役立つとは思わない。
デイブゴードン14

3

マルチタスクでは、コードインジェクションなどは必要ありません。Windowsなどのオペレーティングシステムには、ハードウェアタイマーによってトリガーされるハードウェア割り込みに依存するスケジューラと呼ばれるオペレーティングシステムコードのコンポーネントがあります。これは、異なるプログラムとそれ自体を切り替えるためにオペレーティングシステムによって使用され、人間の知覚ではすべて同時に発生しているように見えます。

基本的に、オペレーティングシステムはハードウェアタイマーを頻繁にオフになるようにプログラムします。おそらく1秒に100回です。タイマーがオフになると、ハードウェア割り込みが生成されます-実行中の処理を停止し、スタックに状態を保存し、モードをより特権のあるものに変更し、特別に指定されたコードで実行するコードを実行するようCPUに指示する信号メモリに配置します。そのコードは、スケジューラの一部であり、次に何を行うべきかを決定します。他のプロセスを再開する場合があります。その場合、「コンテキストスイッチ」と呼ばれる処理を実行する必要があります。現在の状態全体(仮想メモリテーブルを含む)を他のプロセスの状態に置き換えます。プロセスに戻る際には、そのプロセスのすべてのコンテキストを復元する必要があります。

メモリ内の「特別に指定された」場所は、オペレーティングシステム以外に知られている必要はありません。実装はさまざまですが、その要点は、CPUがテーブルルックアップを実行することでさまざまな割り込みに応答することです。テーブルの場所はメモリ内の特定の場所にあり(CPUのハードウェア設計によって決定されます)、テーブルの内容はオペレーティングシステムによって設定され(通常ブート時に)、割り込みの「タイプ」がどのエントリを決定します表の「割り込みサービスルーチン」として使用されます。

これには「コードインジェクション」は含まれません。これは、CPUおよびそのサポート回路のハードウェア機能と連携してオペレーティングシステムに含まれるコードに基づいています。


2

あなたが説明するものに最も近い実世界の例は、VMwareが使用する技術の1つ、バイナリ変換を使用した完全仮想化だと思います。

VMwareは、同じハードウェア上で同時に実行される1つ以上のオペレーティングシステムの下のレイヤーとして機能します。

実行中の命令(通常のアプリケーションなど)のほとんどはハードウェアを使用して仮想化できますが、OSカーネル自体は仮想化できない命令を使用します。 VMwareホストの制御。たとえば、ゲストOSは、最も特権のある保護リングで実行し、割り込みテーブルを設定する必要があります。それが許可されていれば、VMwareはハードウェアの制御を失っていたでしょう。

VMwareは、実行する前にOSコード内のこれらの命令を書き換え、目的の効果をシミュレートするVMwareコードへのジャンプに置き換えます。

したがって、この手法は、説明した内容に多少似ています。


2

オペレーティングシステムがプログラムに「コードを挿入」するさまざまなケースがあります。Apple Macintoshシステムの68000ベースのバージョンは、すべてのセグメントエントリポイントのテーブルを構築します(静的グローバル変数IIRCの直前にあります)。プログラムが開始されると、テーブル内の各エントリは、トラップ命令とそれに続くセグメント番号およびセグメントへのオフセットで構成されます。トラップが実行されると、システムはトラップ命令の後の単語を見て、必要なセグメントとオフセットを確認し、セグメントをロードし(まだない場合)、セグメントの開始アドレスをオフセットに追加し、次に、トラップを、新しく計算されたアドレスへのジャンプに置き換えます。

古いPCソフトウェアでは、これは「OS」によって技術的には行われませんでしたが、コプロセッサーの数学命令ではなくトラップ命令でコードが構築されるのが一般的でした。数値演算コプロセッサがインストールされていない場合、トラップハンドラはそれをエミュレートします。コプロセッサーがインストールされている場合、トラップが初めて取得されると、ハンドラーはトラップ命令をコプロセッサー命令に置き換えます。同じコードの今後の実行では、コプロセッサー命令が直接使用されます。


FP方式は、ARMプロセッサでまだ使用されています。x86CPUの場合とは異なり、FPなしのバリアントがまだあります。しかし、ほとんどのARMは専用デバイスで使用されるため、まれです。これらの環境では、通常、CPUにFP機能があるかどうかが知られています。
MSalters

これらのいずれの場合も、OSはアプリケーションにコードを挿入しませんでした。OSがコードを挿入するには、取得できないアプリケーションを「変更」するためのソフトウェアベンダーによるライセンスが必要です。OSはコードを挿入しません。
デイブゴードン14

@DaveGordonトラップされた命令は、アプリケーションにコードを挿入するOSであると合理的に言うことができます。
ジル「SO-悪であるのをやめる」14

@MSaltersトラップされた命令は、仮想マシンで一般的に発生します。
ジル 'SO-悪であるのをやめる' 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.