Usted menciona cómo si el código es específico para una CPU, ¿por qué debe ser específico también para un sistema operativo? Esta es realmente una pregunta más interesante que muchas de las respuestas aquí han asumido.
Modelo de seguridad de la CPU
El primer programa que se ejecuta en la mayoría de las arquitecturas de CPU se ejecuta dentro de lo que se denomina anillo interno o anillo 0 . La forma en que un arco de CPU específico implementa los anillos varía, pero es evidente que casi todas las CPU modernas tienen al menos 2 modos de operación, uno que es privilegiado y ejecuta código 'bare metal' que puede realizar cualquier operación legal que la CPU pueda realizar y el otro es no confiable y ejecuta código protegido que solo puede realizar un conjunto seguro de capacidades definido. Sin embargo, algunas CPU tienen una granularidad mucho mayor y, para poder usar las VM de forma segura, se necesitan al menos 1 o 2 anillos adicionales (a menudo etiquetados con números negativos), sin embargo, esto está fuera del alcance de esta respuesta.
Donde entra el sistema operativo
Primeros sistemas operativos de tareas individuales
En los primeros sistemas basados en DOS y otros sistemas basados en tareas individuales, todo el código se ejecutaba en el anillo interno, cada programa que ejecutaba tenía plena potencia sobre toda la computadora y podía hacer literalmente cualquier cosa si se comportaba mal, incluso borrar todos sus datos o incluso dañar el hardware. En algunos casos extremos, como la configuración de modos de visualización no válidos en pantallas de visualización muy antiguas, peor aún, esto podría deberse a un código con errores sin malicia alguna.
De hecho, este código era en gran medida independiente del sistema operativo, siempre que tuviera un cargador capaz de cargar el programa en la memoria (bastante simple para los primeros formatos binarios) y el código no dependía de ningún controlador, implementando todo el acceso de hardware en sí mismo bajo el cual debería ejecutarse cualquier sistema operativo siempre que se ejecute en el anillo 0. Tenga en cuenta que un sistema operativo muy simple como este generalmente se denomina monitor si simplemente se utiliza para ejecutar otros programas y no ofrece funcionalidad adicional.
Sistemas operativos multitarea modernos
Los sistemas operativos más modernos, como UNIX , las versiones de Windows que comienzan con NT y otros sistemas operativos ahora oscuros decidieron mejorar esta situación, los usuarios querían características adicionales como la multitarea para poder ejecutar más de una aplicación a la vez y protección, por lo que un error ( o código malicioso) en una aplicación ya no podría causar daños ilimitados a la máquina y los datos.
Esto se realizó utilizando los anillos mencionados anteriormente, el sistema operativo ocuparía el único lugar ejecutándose en el anillo 0 y las aplicaciones se ejecutarían en los anillos externos no confiables, solo capaces de realizar un conjunto restringido de operaciones que el sistema operativo permitía.
Sin embargo, esta mayor utilidad y protección tuvo un costo, los programas ahora tenían que trabajar con el sistema operativo para realizar tareas que no se les permitía hacer, ya no podían, por ejemplo, tomar el control directo sobre el disco duro accediendo a su memoria y cambiar arbitrariamente datos, en su lugar, tuvieron que pedirle al sistema operativo que realizara estas tareas por ellos para que pudiera verificar que se les permitiera realizar la operación, sin cambiar los archivos que no les pertenecían, también verificaría que la operación fuera realmente válida y no dejaría el hardware en un estado indefinido.
Cada sistema operativo decidió una implementación diferente para estas protecciones, parcialmente basada en la arquitectura para la cual fue diseñado el sistema operativo y parcialmente basada en el diseño y los principios del sistema operativo en cuestión, UNIX, por ejemplo, se enfocó en que las máquinas sean buenas para el uso multiusuario y enfocadas Las características disponibles para esto mientras Windows fue diseñado para ser más simple, para ejecutarse en hardware más lento con un solo usuario. La forma en que los programas de espacio de usuario también se comunican con el sistema operativo es completamente diferente en X86 como lo sería en ARM o MIPS, por ejemplo, lo que obliga a un sistema operativo multiplataforma a tomar decisiones basadas en la necesidad de trabajar en el hardware al que está dirigido.
Estas interacciones específicas del sistema operativo generalmente se denominan "llamadas al sistema" y abarcan cómo un programa espacial de usuario interactúa completamente con el hardware a través del sistema operativo, fundamentalmente difieren según la función del sistema operativo y, por lo tanto, un programa que hace su trabajo a través de las llamadas del sistema necesita ser específico del sistema operativo.
El cargador de programas
Además de las llamadas al sistema, cada sistema operativo proporciona un método diferente para cargar un programa desde el medio de almacenamiento secundario y en la memoria , para que un sistema operativo específico pueda cargarlo, el programa debe contener un encabezado especial que describa al sistema operativo cómo puede ser cargado y corrido.
Este encabezado solía ser lo suficientemente simple como para que escribir un cargador para un formato diferente fuera casi trivial, sin embargo, con formatos modernos como elf que admiten funciones avanzadas como el enlace dinámico y declaraciones débiles, ahora es casi imposible que un sistema operativo intente cargar binarios. que no fueron diseñados para ello, esto significa que, incluso si no existieran las incompatibilidades de llamadas del sistema, es inmensamente difícil incluso colocar un programa en ram de una manera que pueda ejecutarse.
Bibliotecas
Los programas rara vez usan las llamadas del sistema directamente, sin embargo, obtienen casi exclusivamente su funcionalidad a través de bibliotecas que envuelven las llamadas del sistema en un formato un poco más amigable para el lenguaje de programación, por ejemplo, C tiene la Biblioteca estándar C y glibc en Linux y libs similares y win32 en Windows NT y superior, la mayoría de los otros lenguajes de programación también tienen bibliotecas similares que ajustan la funcionalidad del sistema de manera adecuada.
Hasta cierto punto, estas bibliotecas pueden superar los problemas de plataforma cruzada como se describió anteriormente, hay una gama de bibliotecas que están diseñadas para proporcionar una plataforma uniforme a las aplicaciones mientras se administran internamente las llamadas a una amplia gama de sistemas operativos como SDL , esto significa que aunque los programas no pueden ser compatibles con los binarios, los programas que usan estas bibliotecas pueden tener una fuente común entre plataformas, lo que hace que la transferencia sea tan simple como la recompilación.
Excepciones a lo anterior
A pesar de todo lo que he dicho aquí, ha habido intentos de superar las limitaciones de no poder ejecutar programas en más de un sistema operativo. Algunos buenos ejemplos son el proyecto Wine que ha emulado con éxito el cargador de programas win32, el formato binario y las bibliotecas del sistema, lo que permite que los programas de Windows se ejecuten en varios UNIX. También hay una capa de compatibilidad que permite que varios sistemas operativos BSD UNIX ejecuten software de Linux y, por supuesto, la propia cuña de Apple que permite ejecutar software antiguo de MacOS en MacOS X.
Sin embargo, estos proyectos funcionan a través de enormes niveles de esfuerzo de desarrollo manual. Dependiendo de cuán diferentes sean los dos sistemas operativos, la dificultad varía desde una cuña bastante pequeña hasta una emulación casi completa del otro sistema operativo, que a menudo es más complejo que escribir un sistema operativo completo en sí mismo, por lo que esta es la excepción y no la regla.