Вы упоминаете о том, что если код специфичен для процессора, почему он должен быть специфичным и для ОС. Это на самом деле более интересный вопрос, который многие из ответов здесь приняли.
Модель безопасности процессора
Первая программа, запускаемая на большинстве архитектур ЦП, выполняется внутри так называемого внутреннего кольца или кольца 0 . То, как конкретная арка ЦП реализует кольца, варьируется, но это означает, что почти каждый современный ЦП имеет по крайней мере 2 режима работы, один из которых является привилегированным и выполняет код «с нуля», который может выполнять любую законную операцию, которую может выполнять ЦП, а другой - ненадежный и выполняет защищенный код, который может выполнять только определенный безопасный набор возможностей. Однако некоторые процессоры имеют гораздо более высокую степень детализации, и для безопасного использования виртуальных машин необходимы как минимум 1 или 2 дополнительных кольца (часто помеченных отрицательными числами), однако это выходит за рамки этого ответа.
Где операционная система входит
Ранние однозадачные ОС
В очень ранних версиях DOS и других ранних системах, основанных на однозадачности, весь код выполнялся во внутреннем кольце, каждая программа, которую вы когда-либо запускали, имела полную мощность на всем компьютере и могла делать буквально все, что угодно, включая удаление всех ваших данных или даже повреждение оборудования. в некоторых крайних случаях, таких как установка недопустимых режимов отображения на очень старых экранах, что еще хуже, это может быть вызвано просто ошибочным кодом без какой-либо злобы.
Этот код был фактически независим от ОС, если у вас был загрузчик, способный загружать программу в память (довольно просто для ранних двоичных форматов), и код не полагался на какие-либо драйверы, реализуя весь аппаратный доступ, под которым он должен работать любая ОС, если она работает в кольце 0. Обратите внимание, что очень простую ОС, подобную этой, обычно называют монитором, если она просто используется для запуска других программ и не предлагает никаких дополнительных функций.
Современные многозадачные ОС
Более современные операционные системы, в том числе UNIX , версии Windows, начинающиеся с NT, и различные другие, ныне непонятные, ОС решили улучшить эту ситуацию, пользователям потребовались дополнительные функции, такие как многозадачность, чтобы они могли запускать более одного приложения одновременно и защиту, поэтому ошибка ( или вредоносный код) в приложении больше не может наносить неограниченный ущерб машине и данным.
Это было сделано с использованием упомянутых выше колец, ОС будет занимать единственное место, работающее в кольце 0, а приложения будут работать во внешних ненадежных кольцах, способных выполнять только ограниченный набор операций, разрешенных ОС.
Однако эта повышенная утилита и защита обходились дорого, теперь программам приходилось работать с ОС для выполнения задач, которые им не разрешалось выполнять, например, они больше не могли, например, напрямую контролировать жесткий диск, получая доступ к его памяти и изменяя произвольно данные, вместо этого они должны были попросить ОС выполнить эти задачи для них, чтобы она могла проверить, что им разрешено выполнять операцию, не изменяя файлы, которые им не принадлежат, она также проверит, что операция действительно действительна и не оставит оборудование в неопределенном состоянии.
Каждая ОС выбрала свою реализацию для этих средств защиты, частично основанную на архитектуре, для которой была разработана ОС, и частично основанную на дизайне и принципах рассматриваемой ОС. Например, UNIX фокусировалась на машинах, пригодных для многопользовательского использования, и была ориентирована на доступные функции для этого, в то время как окна были разработаны для упрощения работы на медленном оборудовании с одним пользователем. В X86 программы пользовательского пространства также взаимодействуют с ОС совершенно по-другому, как, например, в ARM или MIPS, заставляя многоплатформенную ОС принимать решения, основанные на необходимости работать на оборудовании, для которого она предназначена.
Эти специфичные для ОС взаимодействия обычно называются «системными вызовами» и охватывают то, как программа пользовательского пространства полностью взаимодействует с аппаратным обеспечением через ОС, они принципиально различаются в зависимости от функции ОС, и, следовательно, программе, которая выполняет свою работу через системные вызовы, необходимо быть конкретной ОС.
Загрузчик программы
В дополнение к системным вызовам каждая ОС предоставляет свой метод загрузки программы со вторичного носителя данных и в память , чтобы ее можно было загружать из конкретной ОС, программа должна содержать специальный заголовок, который описывает ОС, как она может быть загрузить и запустить.
Этот заголовок был достаточно простым, чтобы написание загрузчика для другого формата было почти тривиальным, однако в современных форматах, таких как elf, которые поддерживают расширенные функции, такие как динамическое связывание и слабые объявления, теперь ОС практически не может пытаться загрузить двоичные файлы которые не были предназначены для этого, это означает, что, даже если бы не было несовместимости системных вызовов, очень трудно даже поместить программу в оперативную память таким образом, чтобы ее можно было запустить.
Библиотеки
Программы редко используют системные вызовы напрямую, однако они почти исключительно приобретают свою функциональность, хотя библиотеки, которые оборачивают системные вызовы в немного более дружественный формат для языка программирования, например, C имеют C Standard Library и glibc под Linux и аналогичные библиотеки win32 под Windows NT и выше, большинство других языков программирования также имеют схожие библиотеки, которые оборачивают функциональность системы соответствующим образом.
Эти библиотеки могут в некоторой степени даже преодолеть кросс-платформенные проблемы, как описано выше, существует целый ряд библиотек, которые предназначены для обеспечения единой платформы приложениям при внутреннем управлении вызовами для широкого спектра ОС, таких как SDL , это означает, что хотя программы не могут быть двоично-совместимыми, программы, использующие эти библиотеки, могут иметь общий исходный код между платформами, что делает перенос так же простым, как перекомпиляция.
Исключения из вышесказанного
Несмотря на все, что я здесь сказал, были попытки преодолеть ограничения, связанные с невозможностью запуска программ более чем в одной операционной системе. Несколько хороших примеров - проект Wine, который успешно эмулировал загрузчик программ win32, двоичный формат и системные библиотеки, позволяющие программам Windows работать в различных UNIX. Существует также уровень совместимости, позволяющий нескольким операционным системам BSD UNIX запускать программное обеспечение Linux, и, конечно, собственная оболочка Apple, позволяющая запускать старое программное обеспечение MacOS под MacOS X.
Однако эти проекты работают через огромные уровни ручной разработки. В зависимости от того, насколько эти две ОС отличаются друг от друга, сложность варьируется от довольно маленькой прокладки до почти полной эмуляции другой ОС, которая зачастую сложнее, чем написание всей операционной системы самой по себе, и поэтому это исключение, а не правило.