なぜWindowsアプリはMacで動かない?OS互換性の深淵を覗く旅
- 2025-02-16

なぜWindowsアプリはMacで動かない?OS互換性の深淵を覗く旅
はじめに:CPUだけでは説明できない謎
WindowsマシンからMacに実行ファイル(.exe)をコピーしても、Macで動かないのはなぜでしょうか?多くの人は「MacはARM CPU、WindowsはIntel x86 CPUだから」と答えるかもしれません。確かにそれは一つの理由ですが、全てではありません。2019年以前、MacもIntel CPUを使用していましたが、同じ問題が発生していました。
同じハードウェア上で複数のOS(例えば、LinuxとWindows)を同時に実行できますが、WindowsアプリがLinux上で動作するわけではありません。これはCPUの違いだけでは説明がつきません。では、なぜなのでしょうか?
謎解き:システムコールの役割
コンピューターサイエンスの卒業生にこの質問をすれば、「システムコール」と答えるでしょう。
システムコールとは何か、その前に、一般的なOSが複数のプロセスを同時に実行するために、ハードウェア資源(特にCPU)を共有する必要があることを理解しましょう。CPUはコンピューターの中枢であり、あらゆることを制御します。しかし、この共有はセキュリティリスクを生み出します。任意のプロセスがCPUを悪用してシステム全体を乗っ取ってしまう可能性があるからです。
これを防ぐために、CPUはユーザーモードとカーネルモードの2つの基本モードで動作します。
- カーネルモード(特権モード): CPUは命令セット内の任意の命令を実行でき、システム全体を完全に制御できます。I/Oデバイスやハードウェアコンポーネント(CPUタイマー、メモリ管理ユニットなど)を操作できます。
- ユーザーモード: CPUは命令セットの限定されたサブセットに制限されます。データの移動やコピー、数学演算、条件の評価、ループの実行など、特定のタスクのみを実行できます。ハードウェアを直接制御する特権命令は実行できません。
ユーザープログラムはユーザーモードで、OSはカーネルモードで動作します。OSは特権命令を使用して、ユーザープログラムがカーネルモードに不正アクセスすることを防ぎます。これはセキュリティに不可欠ですが、問題も引き起こします。
システムコール:OSへの橋渡し
ファイルの読み込みやネットワークメッセージの送信といった些細なタスクであっても、ユーザープログラムはハードウェアにアクセスする必要があります。しかし、ユーザーモードでは直接アクセスできません。そこで登場するのがシステムコールです。
ユーザープログラムは、ハードウェア操作をOSに依頼します。例えば、ファイルへの書き込みであれば、ファイルの場所の特定や書き込み処理といったタスクをOSに依頼します。
システムコールは、OSが提供するサービスへのインターフェースです。 CやC++で記述された関数として提供されることが多いです。各OSは独自のシステムコールセットを持っています。これが互換性の問題の始まりです。
ほとんどのプログラムはシステムコールに大きく依存しています。この記事を読んでいるブラウザも、毎分数千回ものシステムコールを介してOSにサービスを要求しているのです。Linuxを使用している場合は、cat /proc/<プロセスID>/status
コマンド(<プロセスID>
を対象のプロセスIDに置き換える)を実行することで、プロセスがシステムコールを介してOSを呼び出した回数を表示できます。 「voluntary context switches」という項目を確認しましょう。この値が非常に大きいことに気付くはずです。
互換性の壁:システムコールの複雑さ
ユーザープログラムはシステムコールに依存しているため、別のOSがシステムコールを全く同じ方法で実装していない場合、プログラムは動作しません。これは単に名前の違いだけではありません。
システムコールの識別:番号とレジスタ
システムコールは通常、一意の番号で識別されます。プログラムはハードウェアレベルでシステムコールを呼び出す前に、対応する番号をレジスタに書き込みます。OSはレジスタの値を読み取り、システムコールテーブルを参照して、要求されたシステムコールを特定します。Linuxカーネルのソースコードでは、各システムコールに対応する番号を確認できます(アーキテクチャ固有のファイルをチェック)。
しかし、2つのOSが同じシステムコールテーブルを使用しているとしても、互換性が保証されるわけではありません。例えば、システムコール番号を指定するレジスタが異なると、プログラムは誤ったレジスタを読み取り、誤ったシステムコールを識別し、予期せぬ動作を引き起こします。
パラメータの受け渡し:レジスタ vs メモリ
多くのシステムコールはパラメータを必要とします。パラメータの受け渡し方法もOSによって異なります。レジスタを使用するもの、特定のメモリ領域を使用するもの、またはその両方を使用するものがあります。
例えば、Linuxでは、6個以下のパラメータはレジスタに格納され、それ以上のパラメータはメモリに書き込まれます。このような小さな違いが大きな問題を引き起こします。あるOSではメモリにパラメータを期待しているのに、プログラムがレジスタに格納していた場合、プログラムは予期せぬ動作をします。
ABI:アプリケーションバイナリインターフェース
これらの低レベルの詳細の整合性を図るための規約の集合が、**アプリケーションバイナリインターフェース(ABI)**です。APIがアプリケーションレベルの関数を定義するのと同様に、ABIは特定のOSとアーキテクチャ上で、バイナリコードの異なるコンポーネントがどのように相互作用するかを定義します。これはシステムコールだけでなく、実行ファイルフォーマットなども含みます。
実行ファイルフォーマットの違い
コンパイラはソースコードを翻訳して実行ファイルを作成します。実行ファイルには命令だけでなく、データやメタデータも含まれており、OSがプログラムをロードして実行するために必要です。
各OSは実行ファイルのフォーマットに独自のルールを持っています。この構造は、OSが実行ファイルを正しくロードして実行できるようにするだけでなく、リンキング、デバッグ、動的ライブラリの使用などの機能もサポートします。LinuxのELF(Executable and Linkable Format)やWindowsのPE(Portable Executable)フォーマットなどが例として挙げられます。
ランタイム環境の考慮
一部のプログラミング言語は、マシンコードに直接コンパイルされません。代わりに、仮想マシンやインタプリタ上で実行されます。仮想マシンがインストールされていれば、同じコードを複数のプラットフォームで実行できます。
しかし、現代のアプリケーションはモジュール式に構築されることが多いです。モジュールがマシンコードにコンパイルされていても、一つのモジュールがランタイムに依存し、そのランタイムが対象マシンにインストールされていない場合、アプリケーション全体が機能しなくなる可能性があります。
まとめ:互換性の複雑さ
WindowsアプリがMacで動作しない理由は、CPUアーキテクチャだけでなく、システムコール、ABI、実行ファイルフォーマット、ランタイム環境など、多くの低レベルの詳細が関わっている複雑な問題です。これらの要素がOSによって異なるため、単純に実行ファイルをコピーするだけでは動作しないのです。
今後の展望:より深い探求へ
この記事では、OS互換性の問題について、その表面をほんの少しだけ触れました。CPUスケジューリングなど、さらに興味深いトピックもたくさんあります。今後の記事もご期待ください。 もし、特定のトピックについて深く知りたいことがあれば、コメント欄でお知らせください。
Brilliantでスキルアップ!
この記事で説明したように、問題解決能力は開発者にとって非常に重要です。Brilliantは、専門家によって設計された数十ものインタラクティブコースを提供しており、読書だけでなく実践を通して学習できます。第一原理に基づいたアプローチで、基礎的な理解を構築できます。各レッスンには、実践的な問題解決活動が満載で、概念を理解しやすくなっています。講義ビデオを見るよりも6倍効果的です。モバイルアプリにも対応しているので、毎日数分間学習できます。
Brilliantの最新のコース(Applied Python、Creative Coding、Thinking in Codeなど)は、基礎的なスキルを構築し、現実世界のアプリケーションを学ぶのに最適です。プログラマのように考え、複雑なプログラムを作成してゲームやアプリを開発する能力を身につけることができます。
30日間の無料トライアルを試してみて、年間購読を20%割引で利用しましょう!画面上のコードをスキャンするか、brilliant.org/coredumped を訪問してください(説明欄にもリンクがあります)。
ご視聴ありがとうございました!次の動画もお楽しみに!