web-dev-qa-db-ja.com

なぜソフトウェアOS固有なのですか?

特定のオペレーティングシステム用のプログラミング言語を使用して作成されたソフトウェアがそれらでしか機能しない理由の技術的な詳細を特定しようとしています。

私が理解しているのは、バイナリは特定のプロセッサに固有のものであり、それらは異なるプロセッサ間で命令セットが異なるためです。しかし、オペレーティングシステムの特異性はどこから来るのでしょうか。以前はOSによって提供されたAPIであると思っていましたが、次にこの図を本で見ました。 Diagram

オペレーティングシステム-内部および設計原則第7版-W. Stallings(Pearson、2012)

ご覧のとおり、APIはオペレーティングシステムの一部として示されていません。

たとえば、次のコードを使用してCで簡単なプログラムを作成したとします。

#include<stdio.h>

main()
{
    printf("Hello World");

}

これをコンパイルするとき、コンパイラーはOS固有の何かをしていますか?

81
user139929

コードがCPUに固有である場合、OSにも固有である必要がある理由について言及します。これは実際には、ここでの回答の多くが想定しているより興味深い質問です。

CPUセキュリティモデル

ほとんどのCPUアーキテクチャで実行される最初のプログラムは、 内部リングまたはリング と呼ばれる内部で実行されます。特定のCPU Archがリングを実装する方法はさまざまですが、ほとんどすべての最近のCPUには少なくとも2つの動作モードがあります。1つは特権モードであり、CPUが実行できるすべての正当な操作を実行できる「ベアメタル」コードを実行し、もう1つは信頼されておらず、定義された安全な機能セットのみを実行できる保護されたコードを実行します。一部のCPUははるかに高い粒度を持っていますが、VMを安全に使用するには、少なくとも1つまたは2つの追加のリングが必要です(多くの場合、負の数でラベルが付けられます)。ただし、これはこの回答の範囲を超えています。

OSの出所

初期のシングルタスクOS

非常に初期のDOSおよび他の初期のシングルタスクベースのシステムでは、すべてのコードが内部リングで実行されていました。これまでに実行したすべてのプログラムは、コンピューター全体にフルパワーを発揮し、すべてのデータの消去やハードウェアの損傷さえも含めて、誤動作した場合は文字通り何でもできました。非常に古い表示画面で無効な表示モードを設定するなど、いくつかの極端なケースでは、さらに悪いことに、悪意のないバグのあるコードが原因である可能性があります。

このコードは、プログラムをメモリにロードできるローダー(初期のバイナリ形式ではかなりシンプル)があり、コードがドライバーに依存せず、実行するハードウェアアクセス自体をすべて実装している限り、実際にはほとんどOSに依存しません。リング0で実行される限り、どのOSでもかまいません。このような非常に単純なOSは、他のプログラムを実行するためだけに使用され、追加機能を提供しない場合、通常 モニター です。

最新のマルチタスクOS

より近代的なオペレーティングシステム NIXを含むWindowsで始まるNT 、およびその他のさまざまな不明瞭なOSがこの状況を改善することを決定したため、ユーザーは追加機能 マルチタスクなど を望んだ_複数のアプリケーションを同時に実行して保護できるため、アプリケーションのバグ(または悪意のあるコード)によってマシンとデータに無制限の損傷が発生することはなくなりました。

これは上記のリングを使用して行われ、OSはリング0で実行される唯一の場所を占め、アプリケーションは外部の信頼されていないリングで実行され、OSが許可する制限された一連の操作のみを実行できます。

ただし、このユーティリティと保護の増加は犠牲になりました。プログラムはOSと連携して、許可されていないタスクを実行する必要がありました。たとえば、メモリにアクセスして任意のハードディスクを直接制御することはできなくなりました。データの代わりに、OSにこれらのタスクを実行するように依頼して、自分に属していないファイルを変更せずに、操作の実行が許可されていることを確認できるようにしました。また、操作が実際に有効であることを確認しました。ハードウェアが未定義の状態のままになることはありません。

各OSは、これらの保護の異なる実装を決定しました。一部はOSが設計されたアーキテクチャに基づいており、一部は問題のOSの設計と原則に基づいています。たとえば、UNIXは、マルチユーザーの使用に適したマシンに重点を置いており、これに対して利用可能な機能は、Windowsがよりシンプルに設計されており、シングルユーザーが低速のハードウェアで実行できるようになっています。 ARMまたはMIPSなど)のように、ユーザー空間プログラムがOSと通信する方法は、X86では完全に異なります。マルチプラットフォームOSは、対象となるハードウェアで作業します。

これらのOS固有の対話は通常「システムコール」と呼ばれ、ユーザー空間プログラムがOSを介してハードウェアと完全に対話する方法を含みます。これらはOSの機能に基づいて根本的に異なり、システムコールを介して機能するプログラムは、 OS固有である。

プログラムローダー

システムコールに加えて、各OSはプログラムが セカンダリストレージメディア および メモリ からプログラムをロードするための異なる方法を提供します。 OSにロードして実行する方法を説明する特別なヘッダー。

このヘッダーは、以前は単純で、別の形式の ローダーの作成 は簡単なものでしたが、動的リンクや弱い宣言などの高度な機能をサポートするelfなどの最新の形式では、 OSは、そのために設計されていないバイナリをロードしようとします。これは、システムコールの非互換性がなくても、プログラムを実行可能な方法でramに配置することさえ非常に難しいことを意味します。

図書館

プログラムがシステムコールを直接使用することはほとんどありませんが、システムコールをプログラミング言語用のやや親しみやすい形式でラップするライブラリーがありますが、たとえばCにはLinuxのC標準ライブラリーとglibcと同様のwin32ライブラリーがあります。 Windows NT以降では、他のほとんどのプログラミング言語にも同様のライブラリがあり、システムの機能を適切にラップしています。

これらのライブラリは、上記のクロスプラットフォームの問題をある程度克服することもできます。幅広いOSへの呼び出しを内部的に管理しながら、アプリケーションに均一なプラットフォームを提供するように設計されたさまざまなライブラリがあります SDLなど 、これは、プログラムをバイナリ互換にすることはできませんが、これらのライブラリを使用するプログラムは、プラットフォーム間で共通のソースを持つことができるため、再コンパイルと同じくらい簡単に移植できることを意味します。

上記の例外

ここで私が述べたことすべてにかかわらず、複数のオペレーティングシステムでプログラムを実行できないという制限を克服する試みがありました。いくつかの良い例は Wineプロジェクト で、win32プログラムローダー、バイナリフォーマット、システムライブラリの両方を正常にエミュレートして、WindowsプログラムをさまざまなUNIXで実行できるようにしました。いくつかのBSD UNIXオペレーティングシステムでLinuxソフトウェアを実行できる互換性レイヤーもあり、もちろんApple独自のシムを使用すると、MacOS Xで古いMacOSソフトウェアを実行できます。

ただし、これらのプロジェクトは、膨大なレベルの手動開発作業を通じて機能します。 2つのOSの違いに応じて、難易度はかなり小さいシムから他のOSのほぼ完全なエミュレーションまであり、オペレーティングシステム全体を記述するよりも複雑になることが多いため、これは例外であり、規則ではありません。

78
Vality

ご覧のとおり、APIはオペレーティングシステムの一部として示されていません。

図を読みすぎていると思います。はい、OSはオペレーティングシステム関数の呼び出し方法のバイナリインターフェイスを指定し、実行可能ファイルのファイル形式も定義しますが、APIも提供します。これは、 OSサービスを呼び出すアプリケーション。

この図は、オペレーティングシステムの関数は通常、単純なライブラリ呼び出しとは異なるメカニズムを介して呼び出されることを強調しようとしているだけだと思います。一般的なOSのほとんどは、プロセッサの割り込みを使用してOSの機能にアクセスします。典型的な最新のオペレーティングシステムでは、ユーザープログラムがanyハードウェアに直接アクセスすることはできません。文字をコンソールに書き込む場合は、OSに文字を書き込むように依頼する必要があります。コンソールへの書き込みに使用されるシステムコールはOSによって異なるため、ソフトウェアがOS固有である理由の1つの例があります。

printfはCランタイムライブラリの関数であり、一般的な実装ではかなり複雑な関数です。グーグルなら、オンラインでいくつかのバージョンのソースを見つけることができます。 1つのガイド付きツアーについては、このページを参照してください 。最終的には1つ以上のシステムコールを作成することになりますが、それらのシステムコールはそれぞれホストオペレーティングシステムに固有のものです。

48

これをコンパイルするとき、コンパイラーはOS固有の何かをしていますか?

多分。コンパイルおよびリンクプロセス中のある時点で、コードはOS固有のバイナリに変換され、必要なライブラリにリンクされます。 OSがプログラムをロードして実行を開始できるように、プログラムはオペレーティングシステムが想定する形式で保存する必要があります。さらに、標準ライブラリ関数printf()を呼び出しています。これは、オペレーティングシステムが提供するサービスの観点から実装されています。

ライブラリは、オペレーティングシステムとハードウェアからの抽象層であるインターフェイスを提供します。これにより、プログラムを別のオペレーティングシステムまたは別のハードウェア用に再コンパイルすることができます。しかし、その抽象化はソースレベルに存在します。プログラムがコンパイルされてリンクされると、特定のOSに固有のそのインターフェイスの特定の実装に接続されます。

16
Caleb

いくつかの理由がありますが、1つの非常に重要な理由は、オペレーティングシステムが、プログラムを構成する一連のバイトをメモリに読み込み、そのプログラムに付属するライブラリを見つけてメモリにロードする方法を知っている必要があることです。次に、プログラムコードの実行を開始します。これを行うために、OSの作成者はその一連のバイトの特定のフォーマットを作成し、OSコードがプログラムの構造のさまざまな部分を探す場所を認識できるようにします。主要なオペレーティングシステムは作成者が異なるため、これらの形式は互いにほとんど関係がありません。特に、 Windows実行可能形式 は、ほとんどのUnixバリアントが使用する ELF形式 とほとんど共通点がありません。したがって、このすべての読み込み、動的リンク、および実行コードは、OS固有である必要があります。

次に、各OSは、ハードウェア層と通信するための異なるライブラリセットを提供します。これらはあなたが言及するAPIであり、それらは一般に、開発者にシンプルなインターフェースを提供すると同時に、OS自体の深さへのより複雑でより具体的な呼び出しに変換するライブラリです。多くの場合、このレイヤーはかなり灰色で、新しい「OS」APIは部分的または全体的に古いAPIに基づいて構築されています。たとえば、Windowsでは、Microsoftが長年にわたって作成してきた新しいAPIの多くは、基本的に元のWin32 APIの上に重ねられています。

この例では発生しない問題ですが、開発者が直面する大きな問題の1つは、GUIを表示するためのウィンドウマネージャーとのインターフェースです。ウィンドウマネージャーが「OS」の一部であるかどうかは、WindowsのGUIがより深いレベルでOSと統合され、LinuxとOS XのGUIが統合されているため、OS自体だけでなく、視点にも依存します。より直接分離されます。これは非常に重要です。今日、一般的に「オペレーティングシステム」と呼ばれるものは、多くのアプリケーションレベルのコンポーネントが含まれているため、教科書が説明する傾向よりもはるかに大きな獣です。

最後に、厳密にはOSの問題ではありませんが、実行可能ファイルの生成で重要なのは、マシンごとに異なるアセンブリ言語ターゲットがあるため、実際に生成されるオブジェクトコードは異なる必要があるということです。これは厳密には「OS」の問題ではなく、ハードウェアの問題ですが、ハードウェアプラットフォームごとに異なるビルドが必要になることを意味します。

12
Gort the Robot

から 別の答え 私の:

初期のDOSマシンと、世界に対するMicrosoftの実際の貢献は次のとおりです。

Autocadは、印刷できるプリンタごとにドライバを作成する必要がありました。 Lotus 1-2-3も同様です。実際、ソフトウェアで印刷したい場合は、独自のドライバーを作成する必要がありました。 10台のプリンターと10個のプログラムがある場合、本質的に同じコードの100個の異なる部分を個別に独立して記述する必要がありました。

Windows 3.1が(GEMや他の多くの抽象化レイヤーと共に)達成しようとしたことは、プリンターの製造元がプリンター用に1つのドライバーを作成し、プログラマーがWindowsプリンタークラス用に1つのドライバーを作成したことです。

10個のプログラムと10個のプリンターがあれば、20個のコードを書くだけで済みます。Microsoft側のコードは誰にとっても同じであるため、MSの例では、ほとんど作業を行う必要がありませんでした。

現在、プログラムは、サポートすることを選択した10台のプリンターだけでなく、製造元がWindowsでドライバーを提供しているすべてのプリンターに限定されていません。

したがって、OSはアプリケーションにサービスを提供するため、アプリケーションは冗長な作業を行う必要がありません。

サンプルCプログラムでは、文字をstdout(ユーザーインターフェイスに文字を表示するOS固有のリソース)に送信するprintfを使用します。プログラムはユーザーインターフェイスがどこにあるかを知る必要はありません。DOSの場合もあれば、グラフィカルウィンドウの場合もあり、別のプログラムにパイプして別のプロセスへの入力として使用することもできます。

OSはこれらのリソースを提供するため、プログラマーはほとんど作業をせずに、はるかに多くのことを達成できます。

ただし、プログラムの起動も複雑です。 OSは 実行可能ファイル の最初に特定の情報があり、OSがどのように起動するかを指示することを想定しています。場合によっては(AndroidまたはiOS)「サンドボックス」外のリソースにアクセスするために承認が必要なリソース-ユーザーや他のアプリをプログラムの誤動作から保護するためのセキュリティ対策。

したがって、実行可能なマシンコードが同じで、OSリソースが必要ない場合でも、Windows向けにコンパイルされたプログラムは、まったく同じハードウェア上であっても、追加のエミュレーションまたは変換層がないOS Xオペレーティングシステムでは実行できません。

初期のDOSスタイルのオペレーティングシステムは、ハードウェア(BIOS)に同じAPIを実装し、OSがハードウェアに接続されてサービスを提供するため、プログラムを共有することがよくありました。したがって、 COMプログラム を記述してコンパイルした場合、これは一連のプロセッサ命令の単なるメモリイメージです。CP/ M、MS-DOS、およびその他のいくつかのオペレーティングシステムで実行できます。実際、最新のWindowsマシンでCOMプログラムを実行できます。他のオペレーティングシステムは同じBIOS APIフックを使用しないため、エミュレーション層または変換層がなければ、COMプログラムはそれらで実行されません。 EXEプログラムは、単なるプロセッサ命令以上のものを含む構造に従っているため、APIの問題とともに、プログラムをメモリにロードして実行する方法を理解していないマシンでは実行できません。

9
Adam Davis

実際、本当の答えはifすべてのOSが同じ実行可能バイナリファイルレイアウトを理解していたことですandあなたは(C標準ライブラリのような)標準化された関数のみに制限されました提供されるOS(OSが提供するもの)、次にソフトウェアwould、実際、任意のOSで実行されます。

もちろん、現実はそうではありません。 EXEファイルの形式はELFファイルと同じではありませんが、どちらにも同じCPUのバイナリコードが含まれています。*各オペレーティングシステムがすべてのファイルを解釈できる必要があります。フォーマット、そして彼らは単にこれをしなかったであり、彼らが後でそうする理由はなかった(ほぼ間違いなく、技術的な理由ではなく商業上の理由から)。

さらに、プログラムはおそらく、Cライブラリが方法を定義していないことを行う必要があります(ディレクトリの内容を一覧表示するなどの単純な場合でも)。その場合、すべてのOSが独自の機能を提供して、タスク、当然のことながら、あなたが使用するための最も低い共通の分母がないことを意味します(あなたがmakeを除いて)。

したがって、原則として、それは完全に可能です。実際、WINEはWindows実行可能ファイル直接をLinux上で実行します。
しかし、それは膨大な作業であり、(通常)商業的に正当化されません。

*注意:実行可能ファイルには、バイナリコード以外にもlotがあります。 tonには、ファイルが依存するライブラリ、必要なスタックメモリの量、ファイルにエクスポートする関数をオペレーティングシステムに通知する情報がありますother依存する可能性のあるライブラリ、オペレーティングシステムが関連するデバッグ情報、必要に応じてメモリ内のファイルを「再配置」する方法、例外処理を正しく機能させる方法など...もう一度、そこにcould =誰もが同意するこのための単一の形式であるが、それは単にありません。

7
user541686

この図では、「アプリケーション」層が(ほとんど)「ライブラリ」によって「オペレーティングシステム」層から分離されています。つまり、「アプリケーション」と「OS」は互いについて知る必要がないことを意味します。これは図では簡略化されていますが、実際にはそうではありません。

問題は、「ライブラリ」には実際には3つの部分があることです。それは、実装、アプリケーションへのインターフェース、OSへのインターフェースです。原則として、OSに関する限り、最初の2つは「ユニバーサル」にすることができます(スライスする場所によって異なります)が、3番目の部分(OSへのインターフェース)は一般にできません。 OSへのインターフェースは、OS、APIが提供するAPI、パッケージメカニズム(Windows DLLで使用されるファイル形式など)などに依存します。

「ライブラリ」は一般に単一のパッケージとして提供されるため、プログラムが使用する「ライブラリ」を選択すると、特定のOSにコミットします。これは、次の2つの方法のいずれかで発生します。a)プログラマーが事前に完全に選択することで、ライブラリとアプリケーション間のバインディングを汎用的にすることができますが、ライブラリ自体はOSにバインドされます。またはb)プログラマがプログラムを実行するときにライブラリが選択されるように設定しますが、プログラムとライブラリの間のバインディングメカニズム自体はOSに依存します(たとえば、DLLメカニズム)それぞれに長所と短所がありますが、どちらの方法でも事前に選択する必要があります。

だからといって、それが不可能であることを意味するわけではありませんが、あなたは非常に賢い必要があります。この問題を克服するには、実行時にライブラリを選択するルートをたどる必要があり、OSに依存しないユニバーサルバインディングメカニズムを考え出す必要があります(そのため、維持する責任があります)。より多くの作業)。時にはそれはそれの価値があります。

必ずしもそうする必要はありませんが、そうする努力をするつもりであれば、特定のプロセッサに縛られたくない可能性が高いので、仮想マシンを作成してコンパイルします。プログラムをプロセッサニュートラルコード形式に変換します。

今までにあなたは私がどこへ行くのか気づいているはずです。 Javaのような言語プラットフォームは、まさにそれを行います。Javaランタイム(ライブラリ)は、Java間のOS中立バインディングを定義します=プログラムとライブラリ(Javaランタイムがプログラムを開いて実行する方法)。現在のOSに固有の実装を提供します。NETは、MicrosoftがはWindows以外の「ライブラリ」(ランタイム)を提供していません(ただし、他のものは提供しています-Monoを参照してください)。実際には、Flashも同じことを行いますが、ブラウザの範囲はより限定されています。

最後に、カスタムバインディングメカニズムなしで同じことを行う方法があります。従来のツールを使用することもできますが、ユーザーがOSを選択するまでライブラリへのバインド手順を延期します。ソースコードを配布すると、まさにそれが起こります。ユーザーはプログラムを取得し、実行する準備ができたら、それをプロセッサ(コンパイル)およびOS(リンク)にバインドします。

それはすべて、レイヤーのスライス方法によって異なります。結局のところ、特定のマシンコードを実行する特定のハードウェアで作成されたコンピューティングデバイスが常にあります。レイヤーは主に概念的なフレームワークとして存在します。

5
Euro Micelli

ソフトウェアは常にOS固有ではありません。 Java と以前のpコードシステム(さらにはScummVM)の両方で、オペレーティングシステム間で移植可能なソフトウェアが可能です。 Infocom( Zork および Z-machine のメーカー)は、別の仮想マシンに基づいた リレーショナルデータベース も持っていました。ただし、あるレベルでは、これらの抽象化でさえも、コンピューターで実行される実際の命令に変換する必要があります。

3
Elliott Frisch

あなたは言う

特定のオペレーティングシステム用のプログラミング言語を使用して作成されたソフトウェアは、それらでのみ動作します

ただし、例として提供するプログラムは、多くのオペレーティングシステムで動作し、一部のベアメタル環境でも動作します。

ここで重要なのは、ソースコードとコンパイル済みバイナリの違いです。 Cプログラミング言語は、ソース形式でOSに依存しないように特別に設計されています。これは、「コンソールに出力する」などの解釈を実装者に任せることによって行われます。しかし、CはOS固有の何かに準拠している場合があります(理由については他の回答を参照してください)。たとえば、PEまたはELF実行可能形式。

1
Dan

他の人々は技術的な詳細を十分にカバーしています、私はより技術的な理由、物事のUX/UI側に言及したいと思います:

一度書くとどこでも気まずい

すべてのオペレーティングシステムには、独自のユーザーインターフェイスAPIと設計標準があります。プログラムの1つのユーザーインターフェイスを記述して、それを複数のオペレーティングシステムで実行させることは可能ですが、そうすることで、プログラムがどこでも場違いに感じられることが保証されます。優れたユーザーインターフェイスを作成するには、サポートされている各プラットフォームの詳細を調整する必要があります。

これらの多くは詳細ではありませんが、間違えるとユーザーを苛立たせます。

  • 確認ダイアログでは、WindowsとOSXでボタンの順序が異なります。これを間違えると、ユーザーは筋肉の記憶によって間違ったボタンをクリックします。 Windowsでは「OK」、「キャンセル」の順です。 OSXの順序が入れ替わり、実行ボタンのテキストは、実行するアクションの簡単な説明です:「キャンセル」、「ゴミ箱に移動」。
  • 「戻る」の動作は、iOSとAndroidでは異なります。 iOSアプリケーションは、必要に応じて、通常は左上に独自の戻るボタンを描画します。 Androidには、画面の回転に応じて左下または右下に専用ボタンがあります。OSの戻るボタンがオンの場合、Androidへのクイックポートは正しく動作しません無視されました。
  • 運動量のスクロールは、iOS、OSX、Androidで異なります。悲しいことに、ネイティブUIコードを作成していない場合は、独自のスクロール動作を作成する必要があります。

どこでも実行できる1つのUIコードベースを作成することが技術的に可能である場合でも、サポートされているオペレーティングシステムごとに調整を行うのが最善です。

0
Nick Pinney