Почему разные операционные системы используют разные форматы программ

Скачанная программа для Windows не запустится на Linux «двойным щелчком», а приложение, собранное для Linux, macOS откажется открывать вовсе. Причина не в капризах разработчиков: каждая операционная система исторически и технически опирается на свой формат исполняемого файла — жёстко описанную структуру, по которой ядро системы понимает, как загрузить программу в память и передать ей управление. В этой статье объясняем, что именно описывает такой формат, чем различаются EXE, ELF и Mach-O, почему унификация не состоялась и в каких случаях программа всё-таки может работать на нескольких системах.

Что происходит, когда вы запускаете программу

Исполняемый файл — это не просто «набор команд процессора». Это контейнер со строгой внутренней разметкой, который операционная система читает перед запуском. Когда вы запускаете приложение, происходит примерно следующая последовательность:

  1. Система распознаёт тип файла — по расширению, метаданным или по сигнатуре в первых байтах.
  2. Загрузчик (часть ядра или системная утилита) проверяет, что формат ему знаком и структура файла корректна.
  3. Загрузчик выделяет программе виртуальную память и размещает в ней секции файла: машинный код, данные, таблицы.
  4. Подключаются библиотеки, от которых зависит программа, — динамически, если они не встроены в файл.
  5. Управление передаётся на точку входа — конкретный адрес, с которого начинается выполнение кода.

Если на втором шаге загрузчик не узнаёт структуру файла, запуск прекращается. Именно поэтому Windows сообщает, что «приложение не может быть запущено», а Linux в терминале отвечает «cannot execute binary file». Файл физически присутствует на диске, но система не понимает, как с ним работать.

Что описывает формат исполняемого файла

Формат — это, по сути, договор между компилятором, который собирает программу, и загрузчиком, который её запускает. В этом договоре зафиксировано несколько принципиальных вещей:

  • Архитектура процессора. Формат указывает, под какой набор инструкций собран код: x86-64, ARM, RISC-V и так далее. Код для одной архитектуры другой процессор просто не поймёт.
  • Разметка памяти. Какие секции есть в файле (код, данные, ресурсы), где они должны располагаться в адресном пространстве процесса и с какими правами — исполнение, чтение, запись.
  • Точка входа. Адрес, с которого начинается выполнение.
  • Зависимости. Список динамических библиотек, которые нужно подключить до старта, и правила поиска этих библиотек.
  • Таблицы символов и релокаций. Информация, позволяющая связать обращения программы к функциям библиотек с их реальными адресами в памяти.
  • Метаданные. Версия формата, требования к версии системы, информация для отладчика, подписи и сертификаты — в тех форматах, где это предусмотрено.

Поскольку каждая ОС разрабатывала свой загрузчик под свои задачи, «договоры» получились разными — и по структуре, и по возможностям.

Основные форматы и где они используются

Три формата покрывают подавляющее большинство настольных и серверных систем.

Формат Основные системы Типичное расширение Ключевая особенность
PE (Portable Executable) Windows, ReactOS .exe, .dll, .sys Развитая система метаданных, поддержка подписи кода и версионирования
ELF (Executable and Linkable Format) Linux, большинство BSD, Android (для приложений на нативном коде) обычно без расширения, .so для библиотек Гибкость: один формат для исполняемых файлов, библиотек и объектов
Mach-O macOS, iOS без расширения, .dylib для библиотек Проектировался под ядро Mach, поддерживает «универсальные» файлы с кодом для нескольких архитектур

За пределами этой тройки остаются форматы с долгой историей: a.out — ранний формат Unix-систем, COFF — предок PE, использовавшийся в старых версиях Windows и некоторых Unix, а также проприетарные форматы встраиваемых систем и мейнфреймов. Их объединяет то же свойство: они понятны конкретному загрузчику конкретной системы.

PE: формат Windows

Portable Executable появился в Windows NT как развитие формата COFF. Название «переносимый» относится не к переносимости между операционными системами, а к переносимости между архитектурами процессоров: формат позволяет описывать код и для x86, и для ARM. PE подробно описывает секции файла, импортируемые и экспортируемые функции, ресурсы (иконки, строки, диалоги), информацию о версии и цифровую подпись. Последнее важно для экосистемы Windows: подпись кода — часть механизмов безопасности, и её отсутствие или повреждение влияет на то, как система и антивирусы относятся к файлу.

ELF: формат Unix-мира

ELF пришёл на смену a.out в начале 1990-х и стал стандартом де-факто для свободных Unix-подобных систем. Его сильная сторона — универсальность: один и тот же формат описывает и готовые программы, и промежуточные объектные файлы, и разделяемые библиотеки. ELF хорошо приспособлен к динамической компоновке: программа может объявлять зависимости от библиотек, а загрузчик разрешает их при запуске. Именно поэтому в Linux исполняемые файлы обычно не имеют расширения — система ориентируется на внутреннюю структуру, а не на имя.

Mach-O: формат Apple

Mach-O вырос из ядра Mach, на котором построены операционные системы Apple. Формат исторически тесно связан с инструментами разработки NeXTSTEP, наследницей которой стала macOS. Отличительная черта — поддержка универсальных бинарных файлов (fat binaries): один файл может содержать код сразу для нескольких архитектур, например для Intel и Apple Silicon. Это позволило относительно безболезненно пережить переходы между платформами: система сама выбирает подходящую часть файла.

Почему форматы не унифицировали

Технически создать общий формат возможно — и такие попытки были. Но у унификации есть фундаментальные препятствия.

Формат привязан к загрузчику, а загрузчик — к архитектуре системы. Формат исполняемого файла не существует сам по себе: он имеет смысл только вместе с кодом, который умеет его читать. Переделать загрузчик ядра — значит затронуть самый чувствительный слой операционной системы, где цена ошибки максимальна.

Разные модели безопасности. Windows встраивает в PE подписи и механизмы контроля целостности, macOS связывает запуск с проверкой нотаризации и карантинными атрибутами, Linux традиционно оставляет больше свободы на уровне дистрибутива. Общий формат потребовал бы либо общего механизма безопасности, либо его отсутствия — оба варианта кого-то не устраивают.

Разные модели библиотек и ABI. Даже при одинаковом формате файла программа зависит от системного интерфейса: как она обращается к ядру, какие библиотеки ожидает найти, как устроены системные вызовы. Этот слой называется ABI (Application Binary Interface), и он у систем различается не меньше, чем форматы файлов. Совместимый формат без совместимого ABI ничего бы не дал.

История и обратная совместимость. Миллионы работающих программ опираются на существующие форматы. Любая смена формата — это годы поддержки двух режимов параллельно. Windows, например, до сих пор способна запускать программы, написанные десятилетия назад, именно потому, что не ломает старые механизмы без крайней необходимости.

Формат файла — не единственная причина несовместимости

Распространённое заблуждение: «если бы форматы совпадали, программы запускались бы везде». На практике формат — лишь первый барьер. Полный список препятствий выглядит так:

  • Архитектура процессора. Код для ARM не выполнится на x86-64 без эмуляции или трансляции инструкций.
  • Системные вызовы. Программа общается с ядром через строго определённый интерфейс, и он у Windows, Linux и macOS разный.
  • Системные библиотеки. Программа для Linux ожидает найти libc и другие библиотеки в определённых местах; на macOS их нет или они устроены иначе.
  • Разметка экрана, файловой системы, реестра. Пути вроде «C:\Program Files» или «/usr/lib» не существуют в чужой системе.
  • Графические и оконные подсистемы. Приложения с интерфейсом зависят от оконного API, который у каждой ОС свой.

Поэтому даже «правильный» файл без поддержки окружения бесполезен. Совместимость достигается не совпадением форматов, а эмуляцией целого слоя системы.

Как программы всё-таки работают на нескольких ОС

Кроссплатформенность существует, но достигается обходными путями, каждый из которых имеет цену.

Компиляция под каждую платформу

Самый надёжный способ: исходный код один, а компилятор собирает отдельную версию для каждой системы. Так выпускаются большинство кроссплатформенных программ — браузеры, редакторы, игры. Цена — необходимость поддерживать сборку и тестирование для каждой платформы, а также учитывать различия в поведении систем.

Виртуальные машины и эмуляция

Виртуальная машина запускает внутри себя полноценную гостевую операционную систему: программа для Windows работает внутри Windows, установленной на виртуальной машине поверх macOS или Linux. Эмуляция процессора (например, трансляция инструкций одной архитектуры в другую) позволяет запускать программы даже для чужой архитектуры, но с заметной потерей производительности — объём работы зависит от того, насколько различаются системы команд.

Слои совместимости

Слой совместимости — это программа, которая «притворяется» другой ОС: принимает системные вызовы чужой программы и переводит их в вызовы родной системы. Классический пример — Wine, позволяющий запускать Windows-приложения на Linux и macOS без самой Windows. Такой подход легче виртуальной машины, но требует огромной работы по воспроизведению поведения чужой системы, и часть программ всё равно работает с ошибками или не работает вовсе.

Переносимые среды выполнения

Программа пишется не под операционную систему, а под промежуточную среду: виртуальную машину Java, среду .NET, интерпретатор Python или браузер как платформу для веб-приложений. Среда выполнения устанавливается на любую ОС и обеспечивает программе единое окружение. Ограничение — доступ только к тем возможностям, которые среда предоставляет, и зависимость от её производительности.

Статическая сборка

Можно собрать программу так, чтобы все библиотеки были встроены в сам файл и не требовались от системы. Это снимает проблему зависимостей, но не проблему формата, системных вызовов и архитектуры: статически собранный Linux-бинарник всё равно не запустится на Windows. Зато он часто работает на разных дистрибутивах Linux, где версии библиотек различаются, — поэтому такой подход популярен для утилит командной строки.

Что это значит на практике

Понимание форматов помогает в повседневных ситуациях:

  • Выбор загрузок. Если на сайте разработчика есть несколько версий файла, выбирайте ту, что соответствует вашей системе и архитектуре процессора. Файл «для Windows» на Mac не заработает, даже если переименовать расширение.
  • Диагностика ошибок запуска. Сообщение «cannot execute binary file» в Linux чаще всего означает, что файл собран для другой архитектуры или это вообще исполняемый файл чужой системы, а не повреждение самого файла.
  • Оценка предложений. Если разработчик обещает «один файл для всех систем» без упоминания среды выполнения или эмуляции, это повод уточнить, о чём именно идёт речь.
  • Понимание размеров файлов. Универсальные бинарные файлы macOS и программы, собранные статически, весят больше, потому что содержат несколько вариантов кода или встроенные библиотеки, — это нормально.

Частые вопросы

Можно ли изменить расширение файла, чтобы он запустился в другой системе?

Нет. Расширение — лишь подсказка для пользователя и проводника. Загрузчик операционной системы читает внутреннюю структуру файла, и чужой формат он не распознает независимо от имени. Переименование в лучшем случае приведёт к другой ошибке, в худшем — к попытке выполнить бессмысленные данные.

Почему Android-приложения устанавливаются как APK, а программы Linux — без расширения?

Это разные уровни упаковки. APK — установочный пакет, внутри которого лежат ресурсы и, среди прочего, нативные библиотеки в формате ELF. Сам исполняемый код при этом по-прежнему описывается ELF. Формат пакета отвечает за установку и обновление, а формат исполняемого файла — за запуск.

Правда ли, что Linux-программы можно запускать на Windows?

Напрямую — нет, но есть несколько путей: подсистема Windows для Linux (WSL) запускает настоящую Linux-среду внутри Windows, виртуальная машина даёт полную Linux-систему, а отдельные утилиты пересобираются под Windows изначально. Выбор зависит от задачи: для работы в терминале обычно достаточно WSL, для графических приложений чаще нужна виртуальная машина.

Появится ли когда-нибудь единый формат для всех ОС?

Полная унификация маловероятна: форматы слишком глубоко связаны с загрузчиками, моделями безопасности и системными интерфейсами. Практическая альтернатива уже сложилась — переносимые среды выполнения и веб-платформа, где «совместимость» обеспечивается не общим бинарным форматом, а общим слоем поверх операционных систем.

Главное

Разные операционные системы используют разные форматы программ потому, что формат — это не ярлык, а часть механизма запуска: он описывает, как загрузчик размещает код в памяти, подключает библиотеки и передаёт управление. Windows опирается на PE, Linux и большинство Unix-систем — на ELF, Apple — на Mach-O, и каждый формат вырос из архитектуры своей системы, её модели безопасности и истории развития.

Если перед вами стоит задача запустить программу на «чужой» системе, действуйте в таком порядке:

  1. Убедитесь, что версия программы соответствует вашей ОС и архитектуре процессора.
  2. Если версии для вашей системы нет, проверьте, существует ли сборка под переносимую среду (Java, .NET, Python, браузер).
  3. Для Windows-приложений на Linux или macOS рассмотрите слой совместимости или виртуальную машину, понимая, что результат не гарантирован для каждой программы.
  4. Для серверных задач оцените контейнеризацию: она решает проблему зависимостей внутри одной системы, но не совместимость между разными ОС.

Формат файла — первый фильтр совместимости, но не единственный. Программа работает там, где для неё воссоздано всё окружение: архитектура, системные вызовы и библиотеки. Поэтому универсальность достигается не общим форматом, а общим слоем поверх систем — и выбор этого слоя определяет и удобство, и производительность, и надёжность результата.

PEFile.ru