今は本当に気になります。私はPythonプログラマーであり、この質問は私を驚かせました:あなたはOSを書いています。どのようにそれを実行しますか?それは何らかの方法で実行する必要があり、その方法は別のOS内にありますか?
アプリケーションをOSなしで実行するにはどうすればよいですか?実行するOSがない場合、Cを実行してこれらのコマンドを画面に実行するようにコンピューターに指示するにはどうすればよいですか?
UNIXカーネルと関係がありますか?もしそうなら、Unixカーネル、または一般的なカーネルとは何ですか?
OSはそれよりも複雑だと思いますが、どのように機能しますか?
起動プロセスを通過する多くのWebサイトがあります( How Computers Boot Up など)。一言で言えば、最終的にOSプロセスを開始できるまで、システムを少しずつ構築していく多段階プロセスです。
これは、CPUを起動して実行しようとするマザーボード上のファームウェアから始まります。次に、他のハードウェアを起動して実行するミニオペレーティングシステムのようなBIOSをロードします。それが完了すると、ブートデバイス(ディスク、CDなど)を探し、見つかったら、MBR(マスターブートレコード)を見つけてメモリにロードし、実行します。オペレーティングシステム(または状況が複雑になったため、他のブートローダー)を初期化して起動する方法を知っているのは、この小さなコードです。この時点で、カーネルなどがロードされ、実行を開始します。
それがまったく機能することはかなり素晴らしいです!
「ベアメタル」オペレーティングシステムは内では何も実行しません。物理マシンで完全な命令セットを実行し、すべての物理メモリ、すべてのデバイスレジスタ、および仮想メモリサポートハードウェアを制御するものを含むすべての特権命令にアクセスできます。
(オペレーティングシステムが仮想マシン上で実行されている場合、それはと考えるかもしれません上記と同じ状況にあります。違いは、特定のものがエミュレートされるか、または他の方法で処理されることですハイパーバイザー、つまり仮想マシンを実行するレベル)
とにかく、OSは(たとえば)Cで実装されている可能性がありますが、通常のCライブラリのすべてを利用できるわけではありません。特に、通常の「stdio」ライブラリはありません。むしろ、(たとえば)ディスクブロックの読み書きを可能にするディスクデバイスドライバーを実装します。これは、ディスクブロックレイヤーの上にファイルシステムを実装し、さらに、ユーザーアプリケーションのランタイムライブラリが(たとえば)ファイルの作成、読み取り、書き込みなどを行うためのシステムコールを実装します。
アプリケーションをOSなしで実行するにはどうすればよいですか?
これは、I/Oハードウェアなどと直接対話する方法を知っている特別な種類のアプリケーション(オペレーティングシステムなど)である必要があります。
OSが実行されていない場合、Cを実行してこれらのコマンドを画面に対して実行するようにコンピュータに指示するにはどうすればよいですか。
あなたはしません。
アプリケーション(Cで記述された引数のため)は、他のマシンでコンパイルおよびリンクされ、ネイティブコードイメージを提供します。次に、BIOSが検出できる場所のハードドライブにイメージが書き込まれます。 BIOSはイメージをメモリにロードし、アプリケーションのエントリポイントにジャンプする命令を実行します。
本格的なオペレーティングシステムでない限り、アプリケーションには(通常)「Cの実行とコマンドの実行」はありません。そしてその場合、それを実現するために必要なインフラストラクチャをすべて実装するのはオペレーティングシステムの責任です。魔法はありません。たくさんのコード。
Bill's answer カバーbootstrappingこれは、電源がオフのマシンから通常のオペレーティングシステムが稼働しているマシンに移行するプロセスです。ただし、BIOSがタスクを完了すると、(通常)ハードウェアの完全な制御がメインオペレーティングシステムに渡され、次のシステムが再起動するまでそれ以上の役割を果たすことはありません。メインOSは、従来の意味でBIOS内で実行されていません。
UNIXカーネルと関係がありますか?もしそうなら、UNIXカーネル、または一般的なカーネルとは何ですか?
はい、そうです。
UNIXカーネルは、UNIXオペレーティングシステムの中核です。上記の「ベアメタル」のすべてを行うのはUNIXの一部です。
「カーネル」の考え方は、システムソフトウェアをコアの要素(物理的なデバイスアクセス、すべてのメモリなどが必要)と非コアの要素に分離しようとすることです。カーネルはコアとなるもので構成されています。
実際には、カーネル/コアと非カーネル/非コアの違いはそれよりも複雑です。そして、カーネルに実際に属しているものと属していないものについては、多くの議論があります。 (たとえば、マイクロカーネルを検索します。)
最初はCPUに電力がありませんでした。
そして、男は「力を与えてください」と言い、CPUはメモリ内の特定のアドレスから読み取りを開始し、そこにあった命令を実行しました。それから次のもの、そして電源が切れるまで続きます。
これが起動でした。そのタスクは、別のソフトウェアをロードして、メインソフトウェアが存在する環境にアクセスし、それをロードすることでした。
最後に、わかりやすい画面でログオンするように招待されました。
遅くなってすみませんが、そのように説明します。
マザーボードに電力が供給されている。
タイミング回路は、電気的特性のみに基づいて、必要に応じて起動および安定化します。新しいデバイスの中には、非常に限られたマイクロプロセッサまたはシーケンサを実際に使用しているものがあります。
「タイミング回路が起動し、必要に応じて安定する」などの多くは、ハードウェアでは実際には発生しません。その膨大な量の作業は、実際には非常に限られたサブプロセッサー/シーケンサーで実行されている非常に特殊なソフトウェアです。
CPUとRAMに電力が供給されます。
CPUはBIOSから(内部配線に基づいて)データをロードします。一部のマシンでは、BIOSがRAM=にミラーリングされ、そこから実行されますが、IIRCはまれです。
オンにすると、x86互換CPUはアドレス空間のアドレス0xFFFFFFF0から始まります...
-Micheal Steil、 XboxのセキュリティシステムでMicrosoftが犯した17の間違い ( archive )
BIOSは、マザーボードがディスクおよびその他のハードウェアに使用しているハードウェアポートとアドレスを呼び出しますIOおよびディスクをスピンアップし、残りのRAMが機能します。他のもの。
BIOSコード(CMOS設定、ハードウェアに保存)により、低レベルのIDEまたはSATAコマンドを使用して、各ディスクのブートセクターを読み取り、 CMOSまたはユーザーによるメニューのオーバーライドで指定された順序で。
ブートセクターのある最初のディスクは、そのブートセクターが実行されます。このブートセクターは、ディスクからより多くのデータを読み込む、より大きなNTLDR
を読み込む、GRUB
の後の段階などを行うための命令を持つアセンブリです。
最後に、OSマシンコードは、ブートローダーによって、代替またはオフセットの場所からブートセクターをロードするチェーンロードを介して直接的または間接的に実行されます。
次に、フレンドリーなカーネルパニック、窒息したペンギン、またはヘッドクラッシュが原因でディスクが停止します。 =)代替シナリオでは、カーネルがプロセステーブル、メモリ内構造をセットアップし、ディスクをマウントし、ドライバー、モジュール、およびGUIまたはサービスのセット(サーバー上にある場合)をロードします。次に、ヘッダーが読み取られるとプログラムが実行され、アセンブリがメモリに読み込まれ、それに応じてマッピングされます。
良い答えはたくさんありますが、私はこれを追加したいと思います。あなたは、Python背景から来たと述べました。Pythonはninterpreted(または "interpiled"または何か、少なくとも典型的なCPythonの使用例では)言語です。つまり、他のソフトウェア(Pythonインタプリタ)がソースを見て何らかの方法で実行している)があることを意味します。これは優れたモデルであり、実際のハードウェアからうまく抽象化された非常に優れた高水準言語欠点は、常に最初にこのインタープリターソフトウェアが必要なことです。
このようなインタプリタソフトウェアは、通常、CやC++などのマシンコードにコンパイルされる言語で記述されています。マシンコードは、CPUが処理できるものです。 CPUが実行できることは、メモリからいくつかのバイトを読み取り、バイト値に応じて特定の操作を開始することです。したがって、1つのバイトシーケンスは、メモリからレジスタにデータをロードするコマンド、2つの値を追加するコマンド、レジスタからの値をメインメモリに格納するコマンドなどです(レジスタは、一部の特別なメモリ領域です) cpuが最適に機能する場合)、これらのコマンドのほとんどは、そのレベルではかなり低いです。これらのマシンコード命令の人間が読める形式はアセンブラコードです。このマシンコードは、基本的には、Windowsの.exeファイルまたは.comファイル、またはLinux/Unixバイナリ内に格納されているものです。
コンピュータが起動すると、それはばかげていますが、そのようなマシンコード命令を読み取るための何らかの配線があります。 PCでは、これは通常(現在)BIOSを含むメインボード上のEEPROMチップ(基本入力出力システム)であり、このシステムはあまり機能せず、一部のハードウェアなどへのアクセスを容易にし、キー操作を実行できます。ブートして最初の数バイト(マスターブートレコード、MBRとも呼ばれます)をメモリにコピーし、CPUに "ここにプログラムがあります"と伝え、CPUはそれらのバイトをマシンコードとして扱い、実行します。通常、これはいくつかのオペレーティングシステムローダーであり、いくつかのパラメーターでカーネルをロードしてから、そのカーネルに制御を渡し、すべてのドライバーをロードしてすべてのハードウェアにアクセスし、いくつかのデスクトップまたはシェルプログラムなどをロードして、ユーザーがログインし、システムを使用してください。
「OSを使用せずにアプリケーションを実行するにはどうすればよいですか?」簡単な答えは「OSはアプリケーションではない」です。 OSは、アプリケーションと同じツールで作成でき、同じ素材で作成できますが、同じものではありません。 OSはアプリケーションと同じルールでプレイする必要はありません。
OTOH、実際のハードウェアとファームウェアは、OS「アプリケーション」が実行される「OS」と考えることができます。ハードウェアは非常にシンプルなOSです。マシンコードで記述された命令を実行する方法を認識しており、起動時に最初の命令の非常に特定のメモリアドレスを調べる必要があることを認識しています。したがって、起動してすぐに最初の命令が実行され、次に2番目の命令が実行されます。
つまり、OSは既知の場所に存在する単純なマシンコードであり、ハードウェアと直接対話することができます。
質問への回答には、ネイティブ(CPU用)コードの外観と、CPUによる解釈の仕方に関する知識が必要です。
通常、コンパイルプロセス全体は、C、Pascal、またはPython(pypyを使用)およびC#で記述したものをCPUが理解できるものに変換することに基づいています。 "、"レジスタeaxとebxに保存された数値を追加 "、" call function foo "、" compare eax to 10 "これらの命令は1つずつ実行され、コードで実行したいことを行います。
これについて考えてみましょう。このネイティブコードを実行するためにOSは実際には必要ありません。必要なのは、このコードをメモリにロードして、そこにあることをCPUに伝え、実行することです。ただし、あまり気にしないでください。それはBIOSが心配すべき仕事です-CPUが起動した直後、物理アドレス0x7C00の下にコード(1セクターと1セクターのみ)をロードします。次に、CPUがコードのこの1セクター(512 B)の実行を開始します。そして、あなたはあなたが想像する何でもすることができます!もちろん、OSからのサポートはありません。それはあなたがオペレーティングシステムだからです。かっこいい?標準ライブラリ、ブースト、Python、プログラム、ドライバーはありません!すべてを自分で書く必要があります。
そして、どのようにハードウェアと通信しますか?さて、あなたは2つの選択肢があります:
今、あなたはカーネルが何であるかを尋ねています。簡単に言うと、カーネルは、ユーザーが直接目にしたり体験したりできないすべてのものです。キーボードからPC内のほとんどすべてのハードウェアに至るまで、ドライバーと共にすべてを管理します。グラフィカルシェルまたはターミナルで通信します。または、コード内の関数によって、幸いにも、OSのサポートにより実行されます。
よりよく理解するために、私はあなたに一つのアドバイスを与えることができます:あなた自身のOSを書いてみてください。画面に「Hello world」と書いても。
オペレーティングシステムがどのように機能するかを理解するには、それらを2つのカテゴリに分割すると役立つ場合があります。要求に応じてアプリケーションにサービスを提供するものと、CPUのハードウェア機能を使用して、アプリケーションがすべきでないことを実行しないようにするものです。 MS-DOSは以前のスタイルでした。 3.0以降のすべてのバージョンのWindowsは後者のスタイルです(少なくとも8086より強力なものを実行している場合)。
PC-DOSまたはMS-DOSを実行している元のIBM PCは、以前のスタイルの「OS」の例でした。アプリケーションが画面に文字を表示したい場合は、いくつかの方法がありました。 MS-DOSに「標準出力」に送るように要求するルーチンを呼び出すことができます。それを行った場合、MS-DOSは出力がリダイレクトされているかどうかを確認し、リダイレクトされていない場合は、ROMに保存されているルーチンを呼び出しますシステム)カーソルの位置に文字を表示し、カーソルを移動します(「テレタイプの書き込み」)。そのBIOSルーチンは、0xB800:0〜0xB800:3999の範囲のどこかにバイトのペアを格納します。カラーグラフィックアダプターのハードウェア各ペアの最初のバイトを使用して文字の形状を選択し、2番目のバイトを使用して前景色と背景色を選択して、その範囲内のバイトのペアを繰り返しフェッチします。バイトはフェッチされ、赤、緑、青の信号に順番に処理されます読みやすいテキスト表示になります。
IBM PC上のプログラムは、DOSの「標準出力」ルーチンを使用するか、BIOSの「テレタイプ書き込み」ルーチンを使用するか、直接メモリに表示して保存することで、テキストを表示できます。多くのテキストを表示する必要のある多くのプログラムは、DOSルーチンを使用するよりも文字通り数百倍も高速である可能性があるため、後者のアプローチをすぐに選択しました。これは、DOSおよびBIOSルーチンが非常に非効率的だったからではありません。ディスプレイがブランクになっていない限り、特定の時間にのみ書き込むことができます。文字を出力するBIOSルーチンは、いつでも呼び出せるように設計されています。したがって、各要求は、書き込み操作を実行する適切なタイミングを待って、新たに開始する必要がありました。対照的に、何をする必要があるかを知っているアプリケーションコードは、ディスプレイを書くために利用可能な機会を中心にしてそれ自体を編成することができます。
ここでの重要な点は、DOSとBIOSがテキストをディスプレイに出力する手段を提供する一方で、そのような機能について特に「魔法の」ものはなかったということです。ディスプレイにテキストを書き込みたいアプリケーションは、少なくともディスプレイハードウェアがアプリケーションの期待どおりに機能した場合(CGAに似ているが、その文字メモリを備えたモノクロディスプレイアダプターをインストールした場合)、同じくらい効果的にそれを行うことができます。 0xB000:0000-0xB000:3999)にある場合、BIOSは自動的にそこに文字を出力します。 MDAまたはCGAのいずれかで動作するようにプログラムされたアプリケーションも同様に機能しますが、CGA専用にプログラムされたアプリケーションはMDAではまったく役に立ちません)。
新しいシステムでは、状況が少し異なります。プロセッサにはさまざまな「特権」モードがあります。それらは、コードが必要なことを何でも実行できる、最も特権の高いモードから始まります。次に、制限されたモードに切り替えることができます。このモードでは、選択した範囲のメモリまたはI/O機能のみが使用可能です。コードは、制限モードから特権モードに直接切り替えることはできませんが、プロセッサは特権モードのエントリポイントを定義しており、制限モードコードは、プロセッサに特権モードのエントリポイントの1つでコードの実行を開始するように要求できます。さらに、制限付きモードでは禁止されるいくつかの操作に関連付けられた特権モードのエントリポイントがあります。たとえば、誰かが複数のMS-DOSアプリケーションを同時に実行し、それぞれに独自の画面を設定したいとします。アプリケーションが0xB800:0でディスプレイコントローラに直接書き込むことができる場合、あるアプリケーションが別のアプリケーションの画面を上書きするのを防ぐ方法はありません。一方、OSはアプリケーションを制限モードで実行し、ディスプレイメモリへのアクセスをトラップする可能性があります。 「バックグラウンド」にあるはずのアプリケーションが0xB800:160を書き込もうとしていることを発見した場合、バックグラウンドアプリケーションの画面バッファーとして確保していたメモリにデータを保存できます。そのアプリケーションが後でフォアグラウンドに切り替えられた場合、バッファーは実際の画面にコピーされます。
注意すべき重要な点は、(1)テキストの表示などのさまざまな標準サービスを実行する標準のルーチンのセットを持っていると便利なことがよくありますが、「特権モード」で実行されていたアプリケーションが実行できなかったことは何もしませんインストールされたハードウェアを処理するように適切にプログラムされている場合。 (2)今日実行されているほとんどのアプリケーションは、オペレーティングシステムによってそのようなI/Oを直接実行できないようになっていますが、特権モードで起動するプログラムは、必要なことをすべて実行し、制限モードに必要なルールを設定できますプログラム。
オペレーティングシステムの動作には、システムに大きく依存するいくつかの違いがあります。システムを有効にするには、「アドレスXで実行を開始する」など、起動時に予測可能な動作が必要です。フラッシュメモリなどの不揮発性ストレージがプログラムスペースにマッピングされているシステムの場合、起動コードをプロセッサのプログラムスペース内の適切な場所に配置するだけなので、これはかなり簡単です。これは、マイクロコントローラでは非常に一般的です。システムによっては、実行する前に他の場所から起動プログラムを取得する必要があります。これらのシステムには、いくつかの操作が組み込まれています(またはほとんど組み込まれています)。別のチップからi2cを介してスタートアップコードを取得するプロセッサがいくつかあるため、プロセッサはアセンブリ命令を実行せずに重要な操作を実行し、事前定義されたアドレスで命令の実行を開始します。
X86ファミリーのプロセッサーを使用するシステムは、通常、その進化と後方互換性の問題のためにかなり複雑なマルチステージブートプロセスを使用します。システムは、マザーボードの一部の不揮発性メモリにあるファームウェア(BIOS-基本入出力システムなどと呼ばれます)を実行します。場合によっては、このファームウェアの一部またはすべてがRAMにコピー(再配置)され、実行を高速化します。このコードは、どのハードウェアが存在し、起動に使用できるかについての知識で書かれています。
スタートアップファームウェアは通常、システム上に存在するハードウェアを想定して書かれています。 1年前の286マシンでは、I/OアドレスXにフロッピードライブコントローラがあり、特定のコマンドセット(およびセクター0のコード)が指定された場合、セクター0を特定のメモリ位置にロードすると想定されています。 BIOS独自の関数を使用してより多くのコードをロードする方法を知っており、最終的にはOSになるのに十分なコードがロードされます)。マイクロコントローラでは、特定の設定で動作するシリアルポートがあり、ブートプロセスを続行する前に、コマンドを(より複雑なファームウェアを更新するために)X時間待機する必要があると想定されている場合があります。
特定のシステムの正確な起動プロセスは、システムによって異なることを理解するほど重要ではありませんが、すべてのシステムに共通点があることも知っています。多くの場合、I/Oを実行する必要があるときに、スタートアップ(ブートストラップ)コード内で、割り込みに依存するのではなく、I/Oデバイスがポーリングされます。これは、割り込みが複雑であるため、スタックRAM(まだ完全に設定されていない可能性があります)を使用します。また、唯一の操作であるときに他の操作をブロックすることを心配する必要はありません。
最初に読み込まれると、OSカーネル(カーネルはほとんどのOSの主要部分です)は、最初はファームウェアのように動作します。存在するハードウェアの知識でプログラムするか、存在するハードウェアを発見し、いくつかのRAMをスタック空間として設定し、さまざまなテストを行い、さまざまなデータ構造を設定し、おそらくファイルシステムを発見してマウントします。そして、おそらく、作成に慣れているプログラムに似たプログラム(OSの存在に依存するプログラム)を起動します。
OSコードは通常、Cとアセンブリの混合で記述されています。 OSカーネルの最初のコードはおそらく常にアセンブリにあり、Cコードが依存するスタックのセットアップなどの処理を実行してから、C関数を呼び出します。 OSが実行する必要がある一部の操作はCでは表現できないことが多いため(コンテキストスイッチング/スタックの交換など)、他の手書きのアセンブリもそこに含まれます。多くの場合、ほとんどのCプログラムが使用する標準ライブラリに依存せず、プログラムにint main(int argc, char *argv[])
があることを期待しないように、特別なフラグをCコンパイラに渡す必要があります。さらに、ほとんどのアプリケーションプログラマが決して使用しない特別なリンカオプションを使用する必要があります。これらは、カーネルプログラムが特定のアドレスにロードされることを期待したり、特定の場所に外部変数があるように見えるように設定したりします(これらの変数はCコードで宣言されていない場合でも、これはメモリマップI/Oまたはその他の特別なメモリ位置)。
全体の操作は最初は魔法のように見えますが、それを調べてその一部を理解すると、魔法は単なるプログラムのセットになり、実装にはさらに多くの計画とシステムの知識が必要になります。ただし、デバッグには魔法が必要です。
Stephen C.が言ったように、それはオペレーティングシステムを起動することだけでなく、それがどのように実行され、ハードウェアおよびその上のソフトウェアと対話するかについても重要です。
"The Elements of Computing Systems" をご覧になることをお勧めします。それは本といくつかのツールであり、コンピュータ、オペレーティングシステム、コンパイラがどのように相互作用するかを説明しています。ユニークな点は、シミュレーション環境で独自のオペレーティングシステムを非常に迅速に開発するためのツールを提供し、無視概念を把握できるように、実際のもの。木ではなく森を見ることができます。
オペレーティングシステムがハードウェアとどのように相互作用するかについて詳しく知りたい場合は、 Minix を確認してください。
あなたはOSを書きます。それはどういうわけか実行する必要があり、その方法は別のOS内ですか?
アプリケーションはOS内で実行されています。このオペレーティングシステムは、ファイルを開いたり、バイトを書き込んだりするなど、アプリケーションにサービスを提供します。これらのサービスは通常、システムコールを介して提供されます。
オペレーティングシステムはハードウェア内で実行されています。ハードウェアは、シリアルポートのボーレートの設定やバイトの書き込みなど、オペレーティングシステムにサービスを提供します。これらのサービスは通常、メモリマップレジスタまたはI/Oポートを介して提供されます。
これがどのように機能するかを非常に単純化した例を挙げます。
アプリケーションは、オペレーティングシステムにファイルに何かを書き込むように指示します。アプリケーションに対して、オペレーティングシステムはファイルやディレクトリなどの概念を提供します。
ハードウェアでは、これらの概念は存在しません。ハードウェアは、512バイトの固定ブロックに分割されたディスクのような概念を提供します。オペレーティングシステムは、ファイルに使用するブロックと、ファイル名、サイズ、ディスク上の場所などのメタデータ用の他のブロックを決定します。次に、ハードウェアに通知します。これらの512バイトを、この番号のディスク上のこの番号のセクターに書き込みます。これらの他の512バイトを、同じ番号のディスク上のこの異なる番号のセクターに書き込みます。等々。
オペレーティングシステムがハードウェアに指示する方法は、さまざまです。オペレーティングシステムの機能の1つは、これらの違いからアプリケーションを保護することです。ディスクの例の場合、1種類のハードウェアでは、オペレーティングシステムはディスクとセクター番号をI/Oポートに書き込み、次にバイトを1つずつ別のI/Oポートに書き込む必要があります。別の種類のハードウェアでは、オペレーティングシステムはセクターの512バイト全体をメモリ領域にコピーし、そのメモリ領域の場所を特別なメモリ場所に書き込み、ディスクとセクター番号をさらに別の場所に書き込む必要があります。特別なメモリ位置。
今日のハイエンドハードウェアは非常に複雑です。プログラミングの詳細をすべて記載したマニュアルは、何千ページものドアストッパーです。たとえば、最新のIntel CPUマニュアルは7巻で、合計で4000ページを超えていますが、これはCPU専用です。他のほとんどのコンポーネントは、メモリまたはI/Oポートのブロックを公開します。オペレーティングシステムは、そのアドレススペース内のアドレスにマップするようにCPUに指示できます。これらのコンポーネントのいくつかは、いくつかのI/Oポートまたはメモリアドレスの背後にさらに多くのものを公開しています。例として、RTC(リアルタイムクロック、コンピューターの電源がオフになっている間、コンピューターの時間を維持するコンポーネント)は、I/Oポートのペアの背後に数百バイトのメモリを公開します。これは、元のPC/ATにさかのぼる非常に単純なコンポーネントです。ハードディスクのようなものには、オペレーティングシステムが標準化されたコマンドを介して通信する完全に独立したプロセッサがあります。GPUはさらに複雑です。
上記のコメントの数人がArduinoを提案しました。私はそれらに同意します。理解する方がはるかに簡単です。USBコネクタをシリアルポートとして公開することを除いてArduino Unoですべてを行うATmega328には、数百ページしかないマニュアルがあります。 Arduinoでは、ハードウェア上で直接実行しますが、その間にオペレーティングシステムはありません。ほんのいくつかの小さなライブラリルーチン。使いたくない場合に使用する必要はありません。
実行可能な例
技術的には、OSなしで実行されるプログラムはOSです。では、いくつかの非常に小さなHello World OSを作成して実行する方法を見てみましょう。
以下のすべての例のコードは this GitHub repo にあります。
ブートセクター
X86では、 ブートセクター の一種である マスターブートセクター(MBR) を作成し、それをディスクにインストールすることが、最も簡単で最低レベルのことです。 。
ここでは、単一のprintf
呼び出しで作成します。
_printf '\364%509s\125\252' > main.img
Sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img
_
結果:
Ubuntu 18.04、QEMU 2.11.1でテスト済み。
_main.img
_には以下が含まれます。
8進数の_\364
_ == 16進数の_0xf4
_:hlt
命令のエンコーディング。CPUに動作を停止するように指示します。
したがって、プログラムは何も実行せず、開始と停止のみを行います。
_\x
_ 16進数はPOSIXで指定されていないため、8進数を使用します。
このエンコーディングは、次のようにして簡単に取得できます。
_echo hlt > a.asm
nasm -f bin a.asm
hd a
_
ただし、_0xf4
_エンコーディングについては、Intelのマニュアルにも記載されています。
_%509s
_は509スペースを生成します。バイト510までファイルに入力する必要があります。
_\125\252
_ in 8進数== _0x55
_の後に_0xaa
_:ハードウェアが必要とするマジックバイト。それらはバイト511および512でなければなりません。
存在しない場合、ハードウェアはこれを起動可能なディスクとして扱いません。
何もしなくても、画面にはいくつかの文字がすでに印刷されていることに注意してください。それらはファームウェアによって印刷され、システムを識別するのに役立ちます。
実際のハードウェアで実行
エミュレーターは楽しいですが、ハードウェアは本物です。
ただし、これは危険であり、誤ってディスクを消去する可能性があることに注意してください。これは、重要なデータが含まれていない古いマシンでのみ実行してください。または、Raspberry Piなどのdevboardの場合は、以下のARMの例を参照してください。
一般的なラップトップの場合、次のようなことを行う必要があります。
画像をUSBスティックに書き込みます(データが破壊されます!):
_Sudo dd if=main.img of=/dev/sdX
_
uSBをコンピューターに接続する
それをオン
uSBから起動するように指示します。
これは、ファームウェアがハードディスクより先にUSBを選択することを意味します。
それがマシンのデフォルトの動作でない場合は、電源投入後、USBからの起動を選択できる起動メニューが表示されるまで、Enter、F12、ESCなどの奇妙なキーを押し続けます。
多くの場合、これらのメニューで検索順序を構成できます。
たとえば、古いLenovo Thinkpad T430、UEFI BIOS 1.16では、次のように表示されます。
Hello world
最小限のプログラムを作成したので、Hello Worldに移りましょう。
明白な質問は次のとおりです:IOを行う方法?いくつかのオプション:
シリアルポート 。これは、ホスト端末との間で文字を送受信する非常にシンプルな標準プロトコルです。
ソース 。
残念ながら、ほとんどの最新のラップトップでは公開されていませんが、開発ボードにアクセスするための一般的な方法です。以下のARMの例を参照してください。
これは本当に残念です。そのようなインターフェースは本当に便利です たとえば、Linuxカーネルをデバッグするために 。
チップのデバッグ機能を使用します。 ARM彼らの セミホスティング などを呼び出します。実際のハードウェアでは、追加のハードウェアとソフトウェアのサポートが必要ですが、エミュレータでは無料で便利なオプションになります。 例 。
ここでは、x86の方が簡単なBIOSの例を示します。ただし、これが最も堅牢な方法ではないことに注意してください。
main.S
_.code16
mov $msg, %si
mov $0x0e, %ah
loop:
lodsb
or %al, %al
jz halt
int $0x10
jmp loop
halt:
hlt
msg:
.asciz "hello world"
_
link.ld
_SECTIONS
{
. = 0x7c00;
.text :
{
__start = .;
*(.text)
. = 0x1FE;
SHORT(0xAA55)
}
}
_
組み立ててリンクする:
_gcc -c -g -o main.o main.S
ld --oformat binary -o main.img -T linker.ld main.o
_
結果:
テスト済み:Lenovo Thinkpad T430、UEFI BIOS 1.16。 Ubuntu 18.04ホストで生成されたディスク。
標準のユーザーランドアセンブリの指示の他に、次のものが用意されています。
_.code16
_:GASに16ビットコードを出力するように指示します
cli
:ソフトウェア割り込みを無効にします。これらは、hlt
の後にプロセッサを再び実行させる可能性があります
_int $0x10
_:BIOS呼び出しを行います。これは、文字を1つずつ印刷するものです。
重要なリンクフラグは次のとおりです。
--oformat binary
_:生のバイナリアセンブリコードを出力します。通常のユーザーランド実行可能ファイルの場合のように、ELFファイル内でワープしないでください。アセンブリの代わりにCを使用します
CはAssemblyにコンパイルされるため、標準ライブラリなしでCを使用するのは非常に簡単なので、基本的には次のものが必要です。
main
に必要なC状態を設定する小さなアセンブリエントリポイント、特に:TODO:GitHubでx86の例をリンクします。 ARM私が作成したもの です。
ただし、標準ライブラリを使用する場合は、Cの標準ライブラリ機能の多くを実装するLinuxカーネル through POSIX がないため、物事はもっと楽しくなります。
Linuxのような本格的なOSに行かなくても、いくつかの可能性があります。
詳細な例: https://electronics.stackexchange.com/questions/223929/c-standard-libraries-on-bare-metal/223931
Newlibでは、syscallを自分で実装する必要がありますが、システムは非常に最小限であり、簡単に実装できます。
たとえば、printf
をUARTまたはARMシステムにリダイレクトするか、exit()
を セミホスティング 。
FreeRTOS および Zephyr などの組み込みオペレーティングシステム。
このようなオペレーティングシステムでは、通常、プリエンプティブスケジューリングをオフにできるため、プログラムの実行時間を完全に制御できます。
これらは、事前に実装された一種のNewlibと見なすことができます。
[〜#〜]アーム[〜#〜]
ARMでは、一般的な考え方は同じです。アップロードしました:
いくつかの簡単なQEMUベアメタルの例 GitHubのこちら 。 Prompt.cの例 は、ホスト端末から入力を受け取り、シミュレートされたUARTを通じて出力を返します。
_enter a character
got: a
new alloc of 1 bytes at address 0x0x4000a1c0
enter a character
got: b
new alloc of 2 bytes at address 0x0x4000a1c0
enter a character
_
完全に自動化されたRaspberry Piブリンカーのセットアップ: https://github.com/cirosantilli/raspberry-pi-bare-metal-blinker
Raspberry Piの場合、 https://github.com/dwelch67/raspberrypi は、今日利用できる最も人気のあるチュートリアルのように見えます。
X86とのいくつかの違いは次のとおりです。
IOはマジックアドレスに直接書き込むことによって行われ、in
およびout
命令はありません。
これは メモリマップIO と呼ばれます。
raspberry Piなどの一部の実際のハードウェアでは、自分でファームウェア(BIOS)をディスクイメージに追加できます。
これは、ファームウェアの更新をより透過的にするので、良いことです。
ファームウェア
実際、ブートセクターは、システムのCPUで実行される最初のソフトウェアではありません。
実際に最初に実行されるのは、ソフトウェアである、いわゆるfirmwareです。
よく知られているファームウェアは次のとおりです。
ファームウェアは次のようなことを行います:
起動可能なものが見つかるまで、各ハードディスク、USB、ネットワークなどをループします。
QEMUを実行すると、_-hda
_は、_main.img
_がハードウェアに接続されたハードディスクであることを示します。
hda
は最初に試行されるものであり、使用されます。
最初の512バイトをRAMメモリアドレス_0x7c00
_にロードし、そこにCPUのRIPを配置して実行します
ブートメニューやBIOSプリントコールなどをディスプレイに表示する
ファームウェアは、ほとんどのOSが依存するOSのような機能を提供します。例えば。 a PythonサブセットがBIOS/UEFIで実行するように移植されました: https://www.youtube.com/watch?v=bYQ_lq5dcvM
ファームウェアはOSと区別がつかず、そのファームウェアは、実行できる唯一の「真の」ベアメタルプログラミングであると主張できます。
このように CoreOS devはそれを置く :
難しい部分
PCの電源を投入しても、チップセットを構成するチップ(ノースブリッジ、サウスブリッジ、SuperIO)はまだ適切に初期化されていません。 BIOS ROMは、可能な限りCPUから遠く離れていますが、CPUからアクセス可能でなければなりません。そうでない場合、CPUには実行する命令がありません。これはBIOS ROMが完全にマップされていることを意味するわけではありませんが、通常はそうではありません。しかし、ブートプロセスを実行するのに十分なだけがマップされています。他のデバイスはすべて忘れてください。
QEMUの下でCorebootを実行する場合、Corebootの上位層とペイロードを試すことができますが、QEMUは低レベルの起動コードを試す機会がほとんどありません。 1つには、RAMは最初から正しく機能します。
ポストBIOS初期状態
ハードウェアの多くのことと同様に、標準化は弱く、依存してはいけないことは、コードがBIOSの後に実行を開始するときのレジスターの初期状態です。
だから自分自身を支持して、次のようないくつかの初期化コードを使用してください: https://stackoverflow.com/a/32509555/895245
_%ds
_や_%es
_などのレジスタには重要な副作用があるため、明示的に使用していなくてもゼロにする必要があります。
一部のエミュレーターは実際のハードウェアよりも優れており、初期状態が良い場合があります。次に、実際のハードウェアで実行すると、すべてが壊れます。
GNU GRUB Multiboot
ブートセクターは単純ですが、あまり便利ではありません。
これらの理由により、 GNU GRUB がマルチブートと呼ばれるより便利なファイル形式を作成したのです。
私は GitHub examples repo でも使用して、USBを100万回書き込むことなく、すべてのサンプルを実際のハードウェアで簡単に実行できるようにしています。 QEMUでは、次のようになります。
OSをマルチブートファイルとして準備すると、GRUBは通常のファイルシステム内でそれを見つけることができます。
これは、ほとんどのディストリビューションが行うことで、OSイメージを_/boot
_の下に置きます。
マルチブートファイルは基本的に、特別なヘッダーを持つELFファイルです。これらは、GRUB at: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html で指定されます。
_grub-mkrescue
_を使用して、マルチブートファイルを起動可能なディスクに変換できます。
El Torito
CDに書き込むことができる形式: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
ISOまたはUSBで動作するハイブリッドイメージを作成することもできます。これは_grub-mkrescue
_( example )で実行でき、isohybrid
を使用して_make isoimage
_のLinuxカーネルでも実行できます。
リソース