ソースコード、たとえばC++がコンパイルされるとき、コンパイラからの出力は、私が直接CPUへの命令であると思ったマシンコード(実行可能ファイル)であることを私は知っています。最近私はカーネルについて調べていましたが、プログラムはハードウェアに直接アクセスすることはできませんがカーネルを通過する必要があることがわかりました。
単純なソースコード、例えばprintf()
関数でコンパイルして実行可能なマシンコードを生成すると、このマシンコード内の各命令はメモリから直接実行されます(コードがOSによってロードされると)、またはマシンコードの各コマンドはまだ実行されるためにOS(カーネル)を通過する必要がありますか?
私は 同じような質問を読んだ 。コンパイル後に生成されたマシンコードがCPUへの直接の命令であるのか、それともCPUを介して正しい命令を作成するために再度カーネルを通過する必要があるのかは説明されていません。つまり、マシンコードがメモリにロードされた後はどうなりますか。それはカーネルを通過するのでしょうか、それとも直接プロセッサと話すでしょうか?
OSなしで実行するプログラムを書いたことがある人として、私は決定的な答えを提供します。
実行ファイルを実行するにはOSカーネルが必要ですか?
それはそのプログラムがどのように書かれそして構築されたかに依存する。
OSをまったく必要としないプログラムを書くことができます(あなたが知識を持っていると仮定して)。
このようなプログラムは、スタンドアロンとして記述されます。
ブートローダと診断プログラムはスタンドアロンプログラムの典型的な用途です。
しかしながら、あるホストOS環境で書かれ構築された典型的なプログラムは、デフォルトでその同じホストOS環境で実行することになるでしょう。
スタンドアロンプログラムを書いて構築するには、非常に明確な決定と行動が必要です。
...コンパイラからの出力は、私が直接CPUへの命令だと思ったマシンコード(実行可能ファイル)です。
正しい。
最近私はカーネルについて調べていましたが、プログラムはハードウェアに直接アクセスすることはできませんがカーネルを通過する必要があることがわかりました。
これは、OSがプログラムの実行に使用するCPUモードによって課される制限であり、コンパイラやライブラリなどの特定のビルドツールによって促進されます。
これまでに書かれたすべてのプログラムに対する本質的な制限ではありません。
したがって、たとえばprintf()関数を使用して単純なソースコードをコンパイルし、そのコンパイルによって実行可能なマシンコードが生成されると、このマシンコード内の各命令はメモリから直接実行されます。またはマシンコード内の各コマンドを実行するには、まだOS(カーネル)を通過する必要がありますか。
すべての命令はCPUによって実行されます。
サポートされていない命令または不正な命令(たとえば、プロセスが十分な特権を持っていない)は即時例外を引き起こし、代わりにCPUはこの異常な状態を処理するためのルーチンを実行するであろう。
printf()関数は、「単純なソースコード」の例としては使用しないでください。
オブジェクト指向の高水準プログラミング言語からマシンコードへの変換は、あなたが暗示するほど簡単ではないかもしれません。
次に、データ変換およびI/Oを実行するランタイムライブラリから最も複雑な関数の1つを選択します。
あなたの質問は、OS(およびランタイムライブラリ)のある環境を規定していることに注意してください。
システムが起動され、OSにコンピュータの制御権が与えられると、プログラムが実行できることに制限が課される(例えば、I / OはOSによって実行されなければならない)。
スタンドアロンのプログラムを実行することを予定している場合(すなわちOSなしで)、OSを実行するためにコンピュータを起動してはいけません。
...マシンコードがメモリにロードされた後はどうなりますか?
それは環境によります。
独立型プログラムの場合は、それを実行することができる。すなわち、制御はプログラムの開始アドレスにジャンプすることによって引き渡される。
OSによってロードされたプログラムの場合、プログラムはそれが依存している共有ライブラリと動的にリンクされる必要があります。 OSはプログラムを実行するプロセスの実行スペースを作成する必要があります。
それはカーネルを通過するのでしょうか、それとも直接プロセッサと話すでしょうか?
マシンコードはCPUによって実行されます。
それらは「カーネルを通過する」ことはしませんが、「「プロセッサーと対話する」こともしません。
マシンコード(オペコードとオペランドからなる)は、デコードされて演算が実行されるCPUへの命令です。
次に調べるべきトピックは、おそらく CPUモード です。
カーネルは「ただ」もっと多くのコードです。そのコードが、システムの最下部と実際のハードウェアとの間に存在する層であるということだけです。
それはすべてCPU上で直接実行されます、あなたはただ何かをするためにそれの層を通って遷移するだけです。
あなたのプログラムは、最初にprintf
name__コマンドを使用するために標準Cライブラリを必要とするのと全く同じ方法でカーネルを「必要とします」。
プログラムの実際のコードはCPU上で実行されますが、コードが画面に何かを印刷するための分岐は、他のさまざまなシステムやインタープリターを介してC printf
name__関数のコードを通過します。 howhello world!
は実際に画面に表示されます。
デスクトップウィンドウマネージャ上でターミナルプログラムが実行されていて、そのカーネルがハードウェア上で実行されているとします。
まだまだたくさんありますが、シンプルにしておくことができます...
hello world!
を印刷するためにあなたのプログラムを実行しますhello world!
を書き込んだことを認識します。hello world!
を書いてもらった。x
name__、y
name__の位置に置いてもらえますか?」と言ってきます。これは説明のみを目的とした非常に単純化しすぎです。ここにドラゴンがいる。
ハードウェアアクセスを必要とする実質的にあなたがするすべてのこと、それが表示すること、メモリのブロック、ファイルのビットまたはそれのようなものは正確にうまくいくためにカーネルのデバイスドライバを通過する関連デバイスとの会話方法。それ自体がPCIeブリッジデバイスの上に座っているSATAハードディスクコントローラドライバの上のファイルシステムドライバであること。
カーネルは、これらすべてのデバイスをどのように結び付けるかを知っていて、これらすべてのことを自分自身で行う方法を知らなくても、プログラムが物事を行うための比較的単純なインターフェースを提示します。
デスクトップウィンドウマネージャはレイヤを提供します。つまり、プログラムはウィンドウを描画する方法を知らなくても、同時に物事を表示しようとする他のプログラムとうまく遊ぶ必要がありません。
最後に端末プログラムは、あなたのプログラムがウィンドウを描画する方法、カーネルグラフィックスカードドライバと通信する方法、スクリーンバッファと表示タイミングを処理すること、そして実際にウィグルすることに関する複雑さのすべてを知る必要がないことを意味します。ディスプレイへのデータ行。
それはすべてコードの層の上の層によって処理されます。
それは環境によります。 IBM 1401のような多くの古い(そしてより単純な!)コンピュータでは、答えは "いいえ"になるでしょう。コンパイラとリンカは、オペレーティングシステムなしで実行されるスタンドアロンの「バイナリ」を発行しました。プログラムの実行が停止したときに、別のプログラムをロードしました。これもOSなしで実行されました。
あなたが一度に一つのプログラムだけを走らせているわけではないので、現代の環境ではオペレーティングシステムが必要です。 CPUコア、RAM、大容量記憶装置、キーボード、マウス、ディスプレイを一度に複数のプログラムで共有するには調整が必要です。 OSがそれを提供します。したがって、現代の環境では、プログラムは単にディスクやSSDを読み書きできないため、OSに代わってそれを実行するように要求する必要があります。 OSは、ストレージデバイスにアクセスしたいすべてのプログラムからそのような要求を受け取り、アクセス制御のようなことを実装し(普通のユーザがOSのファイルに書き込めないように)、それらをデバイスにキューし、返された情報を整理します。正しいプログラム(プロセス)に。
さらに、現代のコンピューター(例えば1401とは異なり)は、IBMが昔に販売していたものだけでなく、非常に多種多様な入出力装置の接続をサポートします。あなたのコンパイラとリンカはすべての可能性についておそらく知ることができません。たとえば、キーボードがPS/2、またはUSB経由で接続されているとします。 OSを使用すると、それらのデバイスとの通信方法を知っているがデバイスクラス用の共通インタフェースをOSに提示する、デバイス固有の「デバイスドライバ」をインストールできます。したがって、あなたのプログラム、そしてOSさえも、USBとPS/2キーボードからキーストロークを取得するために、あるいはローカルのSATAディスクとUSBストレージデバイスとどこか離れた場所にあるストレージにアクセスするために何もする必要はありません。 NASまたはSAN上。これらの詳細は、さまざまなデバイスコントローラ用のデバイスドライバによって処理されます。
大容量記憶装置の場合、OSは、記憶域がどこでどのように実装されているかに関係なく、ディレクトリとファイルへの同じインタフェースを提供するファイルシステムドライバをすべて提供します。また、OSはアクセス制御とシリアル化について心配しています。一般的に、例えば、同じファイルをいくつかのフープを飛び越えずに一度に複数のプログラムで書き込み用に開くことはできません(ただし、同時読み取りは一般的に問題ありません)。
つまり、現代の汎用環境では、そうです - あなたは本当にOSが必要です。しかし今日でも、リアルタイムコントローラのように複雑ではないコンピュータが必要です。
例えば、Arduino環境では、本当にOSはありません。確かに、ビルド環境がビルドするすべての「バイナリ」に組み込まれているたくさんのライブラリコードがあります。しかし、あるプログラムから次のプログラムへのそのコードの永続性はないので、それはOSではありません。
多くの回答がこの問題を誤解していると思います。
コンパイラはマシンコードを出力します。このマシンコードはCPUによって直接実行されますか、それともカーネルによって「解釈」されますか?
基本的に、CPUは直接マシンコードを実行します。カーネルにすべてのアプリケーションを実行させるのはかなり遅いでしょう。ただし、注意点がいくつかあります。
OSが存在するとき、アプリケーションプログラムは通常、特定の命令を実行することまたは特定のリソースにアクセスすることを制限されている。たとえば、アプリケーションがシステム割り込みテーブルを変更する命令を実行すると、CPUは代わりにOS例外ハンドラにジャンプし、問題のあるアプリケーションは終了します。また、アプリケーションは通常、デバイスメモリへの読み書きを許可されていません。これらの特別なメモリ領域にアクセスすることは、OSがグラフィックカード、ネットワークインタフェース、システムクロックなどのような装置とどのように通信するかということである。
OSがアプリケーションに課す制限は、特権モード、メモリ保護、割り込みなど、CPUの特別な機能によって達成されます。スマートフォンやPCにあるCPUにはこれらの機能がありますが、特定のCPUにはありません。これらのCPUには、必要な機能を実現するためにアプリケーションコードを「解釈する」特別なカーネルが必要です。非常に興味深い例は Gigatron です。これは34命令のコンピュータをエミュレートする8命令のコンピュータです。
Javaのようないくつかの言語はBytecodeと呼ばれるものに「コンパイル」しますが、これは実際にはマシンコードではありません。以前はプログラムを実行すると解釈されていましたが、最近は Just-in-Timeコンパイル と呼ばれるものが通常使用されるため、直接実行されます。マシンコードとしてCPU上で。
仮想マシンでソフトウェアを実行すると、以前はそのマシンコードが ハイパーバイザー と呼ばれるプログラムによって「解釈」される必要がありました。 VMに対する業界の大きな需要のため、CPUメーカーは、ゲストシステムのほとんどの命令をCPUで直接実行できるように、VTxのような機能をCPUに追加しました。ただし、仮想マシンで互換性のないCPU用に設計されたソフトウェアを実行する場合(NESのエミュレーションなど)、マシンコードを解釈する必要があります。
コードをコンパイルするとき、(ほとんどの場合)システムライブラリに依存する、いわゆる "オブジェクト"コード(例えばprintf
)を作成します。そして、あなたのコードはリンカによってラップされます。を認識することができます(そのため、たとえば、Linux上でWindows用にコンパイルされたプログラムを実行することはできません)。そして、コードのラップを解除して実行する方法を知っています。ですから、あなたのプログラムはサンドイッチの中の肉のようなもので、全体としては束としてしか食べられません。
最近私はカーネルについて調べていましたが、プログラムは直接ハードウェアにアクセスすることはできませんが、カーネルを通過する必要があることがわかりました。
それは真実です。あなたのプログラムがカーネルモードドライバならば、実際にハードウェアに「話す」方法を知っていれば直接ハードウェアにアクセスすることができますが、通常(特に文書化されていないか複雑なハードウェアのために)人々はカーネルライブラリであるドライバを使います。これにより、アドレス、レジスタ、タイミング、その他のことを知らなくても、ほとんど人間が読める形式でハードウェアと通信する方法を知っているAPI関数を見つけることができます。
このマシンコードの各命令は(コードがOSによってメモリにロードされると)メモリから直接実行されますか、それともマシンコードの各コマンドは実行されるためにOS(カーネル)を通過する必要があります。
さて、カーネルはウェイトレスのようなもので、その責任はあなたをテーブルに連れて行ってあなたに仕えることです。それができない唯一のこと - それはあなたのために食べることです、あなたはあなた自身がそれをするべきです。あなたのコードと同じように、カーネルはあなたのプログラムをメモリにアンパックして、CPUによって直接実行されるマシンコードであるあなたのコードを開始します。カーネルはただあなたを監督する必要があります - あなたが許されることとあなたがすることを許されないこと。
コンパイル後に生成されたマシンコードがCPUへの直接の命令なのか、それともカーネルを経由してCPUの正しい命令を作成する必要があるのかは説明できません。
コンパイル後に生成されるマシンコードは、CPUへの直接の命令です。それは間違いありません。あなたが心に留めておく必要がある唯一の事、コンパイルされたファイルのすべてのコードが実際のマシンの/ CPUコードであるというわけではありません。手掛かりとして、リンカはあなたのプログラムを、カーネルだけが解釈することができるいくつかのメタデータでラップしました - あなたのプログラムをどうするか。
マシンコードがメモリにロードされた後はどうなりますか?それはカーネルを通過するのか、それとも直接プロセッサと通信するのか。
あなたのコードが2つのレジスタの追加のような単純なオペコードであればそれはカーネルの援助なしで直接CPUによって実行されます、しかしもしあなたのコードがライブラリからの関数を使用するならレストランで食べるために彼らはあなたに道具を与えるだろう - フォーク、スプーン(そしてそれはまだ彼らの資産)しかしあなたがそれを使って何をするか - あなたの「コード」次第で。
まさしく、コメントの炎を防ぐために - 私はOPが基本的な事柄を理解するのを手助けすることを望んでいるのは本当に過度に単純化されたモデルですが、この答えを改善するための良い提案は大歓迎です。
したがって、たとえばprintf()関数だけを使って単純なソースコードをコンパイルし、そのコンパイルによって実行可能なマシンコードが生成されると、このマシンコード内の各命令はメモリから直接実行されます。 OSによって)、またはマシンコード内の各コマンドはまだ実行されるためにOS(カーネル)を通過する必要がありますか?
基本的に、システムコールだけがカーネルに行きます。 I/Oやメモリの割り当て/割り当て解除に関連する処理は、通常、システムコールを引き起こします。一部の命令はカーネルモードでしか実行できず、CPUに例外を発生させます。例外が発生すると、カーネルモードに切り替わり、カーネルコードにジャンプします。
カーネルはプログラム内のすべての命令を処理するわけではありません。システムコールを実行し、実行中のプログラムを切り替えてCPUを共有するだけです。
ユーザーモード(カーネルなし)でメモリ割り当てを行うことはできません。あなたがアクセスする権限を持っていないメモリにアクセスすると、以前にカーネルによってプログラムされたMMUが気付いてCPUレベルの "segmentation fault"例外を引き起こします。これはカーネルを起動し、カーネルはプログラムを終了させます。
ユーザーモード(カーネルなし)でI/Oを実行することはできません。デバイスのI/Oポートやレジスタ、あるいはデバイスに接続されたアドレスにアクセスすると(いずれか一方または両方がI/Oを実行するために必要)同じように例外。
実行可能ファイルを実行するにはOSカーネルが必要ですか?
実行可能ファイルの種類によって異なります。
カーネルは、RAMおよびハードウェアへの共有アクセスを仲介するだけでなく、ローダ機能も実行します。
ELFやPEのような多くの「実行可能フォーマット」は、コードに加えて実行可能ファイルにメタデータを持ち、それを処理するローダーの仕事です。詳しくは、MicrosoftのPEフォーマットについての 詳細な説明をお読みください 。
これらの実行ファイルはライブラリ(Windows .dll
またはLinux共有オブジェクト.so
ファイル)も参照します - それらのコードはインクルードされなければなりません。
コンパイラがオペレーティングシステムローダーによって処理されることを意図したファイルを生成し、そのローダーがそこにない場合、それは機能しません。
もちろんです。メタデータを処理せずに生のコードを何らかの形で実行するようにOSに納得させる必要があります。あなたのコードがカーネルAPIを呼び出したとしても、それはまだ機能しません。
あなたがどういうわけかオペレーティングシステムからこの実行可能ファイルをロードするならば(すなわちそれが生のコードがロードされ実行されるのを可能にするなら)、それはまだユーザモードになるでしょう。未割り当てのメモリやI/Oデバイスのアドレス/レジスタなど、カーネルモードではなくユーザーモードで禁止されているものにコードがアクセスすると、特権またはセグメント違反でクラッシュします(これも例外はカーネルモードになり、処理されます)。まだありません)。
それでうまくいきます。
TL、DR番号.
Arduinoの開発はOSが存在しない現在の環境として頭に浮かぶ。私を信頼してください、 これらの赤ちゃん のうちの1つで、オペレーティングシステムのためのスペースがありません。
同様に、Sega Genesis用のゲームにはSegaが提供するOSがありませんでした。あなたは68Kアセンブラであなたのゲームを作り上げ、ベアメタルに直接書き込みました。
あるいは、私が歯を切るところで、Intel 8051の組み込み作業をしています。もう一度、あなたが持っているのが2k * 8のフットプリントを持つ2716 epromであるとき、あなたはオペレーティングシステムのための部屋を持っていません。
もちろん、これはWordアプリケーションの非常に広い用途を想定しています。修辞的な質問として、Arduinoのスケッチが実際にはアプリケーションであるかどうかを自分で尋ねる価値があります。
他の答えが自分自身では正しくないことを暗示したくはありませんが、あまりにも多くの詳細を提供していますが、私は怖いので、まだあなたにはあいまいです。
基本的な答えは、コードはプロセッサ上で直接実行されるということです。いいえ、マシンコードはだれにも「話す」ことはありません、それは逆です。プロセッサはアクティブコンポーネントであり、あなたがあなたのコンピュータですることはすべてそのプロセッサによって行われるでしょう(私は物事をここで少し単純化しますが、それは今のところ大丈夫です)。プロセッサはコードを読んでそれを実行し、結果を吐き出します。マシンコードはプロセッサのためだけのものです。
あなたの混乱はWordハードウェアの使用から生じています。分割は以前ほど明確ではありませんでしたが、単にすべてのハードウェアを呼び出すのではなく、周辺機器の観点から考える方が得策です。あなたのマシンにオペレーティングシステムなどがある場合、あなたのプログラムは周辺機器にアクセスするためにそのサービスを使用しなければなりませんが、プロセッサ自体は周辺機器ではなく、それはあなたのプログラムが直接実行されるメインプロセッシングユニットです。
カーネル、オペレーティングシステム、および類似の介在層は通常、いくつかのプログラムが実行されることが予想され、これらのプログラムがコンピュータの周辺機器をどのように使用できるかを管理するシステムが必要な大規模システムでのみ使用されます。同時)。このような場合、実行中のプログラムはシステムを使用してこれらの周辺機器にのみアクセスでき、それらを共有する方法を決定し、競合がないことを確認します。競合するプログラム間で管理を行う必要がない小規模なシステムで、基盤となるシステムがまったくない場合が多く、これらのシステムで通常実行されている単一のプログラムでは、周辺機器を自由に使用できます。
起動時にコンピュータで実行されるBIOSは、ROMに格納されている実行可能コードです。機械命令とデータで構成されています。ソースコードからこのBIOSを組み立てるコンパイラ(またはアセンブラ)があります。これは特別な場合です。
その他の特別な場合としては、カーネルをロードするブートストラッププログラムとカーネル自体があります。これらの特別な場合は、通常C++以外の言語でコーディングされています。
一般的には、カーネルまたはライブラリルーチンによって提供されるシステムサービスを呼び出す命令をコンパイラに生成させる方がはるかに実用的です。それはコンパイラをずっと軽量にする。また、コンパイル済みコードが軽量になります。
もう一方の端にはJavaがあります。 Javaでは、この用語は通常理解されているように、コンパイラはソースコードを機械語命令に変換しません。その代わりに、ソースコードは、Java仮想マシンと呼ばれる架空のマシンの「マシン命令」に変換されます。 Javaプログラムを実行する前に、Java仮想マシン用のインタプリタを含むJavaランタイムと組み合わせる必要があります。
昔はあなたのプログラムはあなたがそれを自分でやることによって、あるいは他の人があなたのプログラムに書いたライブラリコードを追加することによってあなたのプログラムの実行中にされる必要があるすべてをする責任がありました。幸運ならば - コンピュータのそれのそばで走っている唯一のものはあなたのコンパイルされたプログラムで読むコードでした。より多くのことができるようになる前に(元の "ブートストラップ"プロセス)、あるいはあなたのプログラム全体がこの方法で入ってくるようなコードをスイッチを通して入力しなければならなかったコンピュータもありました。
コードを実行してプログラムをロードし実行することができるのはいいことだということがすぐにわかりました。後になって、特にハードウェアが助けになるが、プログラムの複雑さが増して互いにつま先がぶつからないようにすると、コンピュータは複数のプログラムをそれらの間で切り替えることによって同時に実行するのをサポートするのに十分強力だった。 、一度にデータをプリンタに送ろうとしている複数のプログラムをどう扱うか?).
これらすべての結果、ユーザープログラムからヘルパーコードを呼び出す標準化された方法で、大量のヘルパーコードが個々のプログラムから「オペレーティングシステム」に移動されました。
そしてそれが私たちが今日いるところです。あなたのプログラムはフルスピードで動作しますが、それらがオペレーティングシステムによって管理される何かを必要とするときはいつでも、それらはオペレーティングシステムによって提供されるヘルパールーチンを呼び出します、そしてそのコードは必要とされず、ユーザープログラム自身には存在しません。これには、ディスプレイへの書き込み、ファイルの保存、ネットワークへのアクセスなどが含まれます。
特定のプログラムを完全なオペレーティングシステムなしで実行するために必要なものだけを提供するマイクロカーネルが書かれています。これは経験豊富なユーザーにとっては利点がありますが、他のユーザーにはほとんど利点があります。それについてのWikipediaのページを読みたいと思うかもしれません - https://en.wikipedia.org/wiki/Microkernel - もっと知りたいのであれば。
私は、Java仮想マシンを実行できるMicrokernelを使って実験しましたが、後になってその最適な場所はDockerであることがわかりました。
典型的なデスクトップOSでは、カーネル自体が実行可能ファイルです。 (Windowsにはntoskrnl.exe
、Linuxにはvmlinux
などがあります。)実行可能ファイルを実行するためにカーネルが必要な場合は、それらのOSは存在しません。
カーネルが必要なのは、カーネルがすることをすることです。複数の実行可能ファイルを一度に実行し、それらの間でレフェリーを実行し、ハードウェアを抽象化することなどを許可します。ほとんどのプログラムでは、それ自体はうまくできません。ほとんどオペレーティングシステムとは言えないDOSの時代には、ゲームはOSをローダー以外のものとして使用せず、カーネルのように直接ハードウェアにアクセスしていました。しかし、ゲームを購入する前に、ハードウェアのブランドやモデルがマシンに何であるかを知っている必要がありました。多くのゲームはビデオとサウンドカードの特定のファミリーをサポートするだけでした、そしてそれらが全くうまくいったとしても競合するブランドで非常に貧弱に走りました。これは、プログラムが通常はカーネル経由で提供される抽象化を通してではなく、直接ハードウェアを制御するときに得られる種類のものです。)