Você menciona como se o código é específico para uma CPU, por que também deve ser específico para um sistema operacional. Esta é realmente uma pergunta mais interessante que muitas das respostas aqui assumiram.
Modelo de Segurança da CPU
O primeiro programa executado na maioria das arquiteturas de CPU é executado dentro do que é chamado de anel interno ou anel 0 . O modo como um arco específico da CPU implementa anéis varia, mas parece que quase toda CPU moderna possui pelo menos 2 modos de operação, um privilegiado e que executa código 'bare metal' que pode executar qualquer operação legal que a CPU possa executar e a outra é não confiável e executa código protegido, que só pode executar um conjunto seguro de recursos definido. No entanto, algumas CPUs têm granularidade muito maior e, para usar VMs com segurança, são necessários pelo menos 1 ou 2 toques extras (geralmente rotulados com números negativos), mas isso está além do escopo desta resposta.
Onde o SO entra
SOs iniciais de tarefas únicas
No DOS e em outros sistemas baseados em tarefas únicas, todo o código era executado no anel interno, todos os programas que você executava tinham poder total sobre todo o computador e podiam fazer literalmente qualquer coisa se se comportasse mal, incluindo apagar todos os dados ou danificar o hardware. em alguns casos extremos, como definir modos de exibição inválidos em telas muito antigas, pior ainda, isso pode ser causado por simplesmente código de buggy, sem qualquer malícia.
Na verdade, esse código era praticamente independente do sistema operacional, desde que você tivesse um carregador capaz de carregar o programa na memória (bastante simples para os formatos binários iniciais) e o código não dependesse de drivers, implementando todo o acesso de hardware em que deveria ser executado. qualquer sistema operacional, desde que seja executado no anel 0. Nota, um sistema operacional muito simples como esse geralmente é chamado de monitor se for simplesmente usado para executar outros programas e não oferece funcionalidade adicional.
Sistemas operacionais multitarefa modernos
Sistemas operacionais mais modernos, incluindo UNIX , versões do Windows começando com NT e vários outros sistemas operacionais obscuros decidiram melhorar essa situação, os usuários queriam recursos adicionais , como multitarefa, para que pudessem executar mais de um aplicativo ao mesmo tempo e proteção, um bug ( ou código malicioso) em um aplicativo não poderia mais causar danos ilimitados à máquina e aos dados.
Isso foi feito usando os anéis mencionados acima, o sistema operacional ocuparia o único lugar em execução no anel 0 e os aplicativos executariam nos anéis externos não confiáveis, capazes apenas de executar um conjunto restrito de operações permitidas pelo sistema operacional.
No entanto, esse aumento de utilidade e proteção custava um custo, os programas agora tinham que trabalhar com o sistema operacional para executar tarefas que não tinham permissão para realizar, não podiam mais, por exemplo, assumir o controle direto do disco rígido acessando sua memória e alterando arbitrariamente em vez disso, eles precisavam pedir ao sistema operacional para executar essas tarefas para que pudessem verificar se eles estavam autorizados a executar a operação, não alterando arquivos que não lhes pertenciam, mas também para verificar se a operação era realmente válida e não deixaria o hardware em um estado indefinido.
Cada sistema operacional decidiu uma implementação diferente para essas proteções, parcialmente baseada na arquitetura para a qual o sistema operacional foi projetado e parcialmente baseada no design e nos princípios do sistema operacional em questão; o UNIX, por exemplo, enfatizou que as máquinas são boas para uso multiusuário e focadas. os recursos disponíveis para isso, enquanto o Windows foi projetado para ser mais simples, para rodar em hardware mais lento com um único usuário. A maneira como os programas de espaço do usuário também conversam com o sistema operacional é completamente diferente no X86, como seria no ARM ou MIPS, por exemplo, forçando um sistema operacional de várias plataformas a tomar decisões com base na necessidade de trabalhar no hardware para o qual é direcionado.
Essas interações específicas do SO são geralmente chamadas de "chamadas de sistema" e abrangem como um programa de espaço do usuário interage completamente com o hardware através do SO; elas diferem fundamentalmente com base na função do SO e, portanto, um programa que faz seu trabalho através de chamadas do sistema precisa: seja específico do SO.
O Carregador de Programas
Além das chamadas do sistema, cada sistema operacional fornece um método diferente para carregar um programa da mídia de armazenamento secundário e na memória . Para ser carregado por um sistema operacional específico, o programa deve conter um cabeçalho especial que descreva para o sistema operacional como ele pode ser carregado e executado.
Esse cabeçalho costumava ser simples o suficiente para escrever um carregador para um formato diferente era quase trivial, no entanto, com formatos modernos, como o elf, que oferecem suporte a recursos avançados, como vínculo dinâmico e declarações fracas, agora é quase impossível para um sistema operacional tentar carregar binários que não foram projetados para isso, isso significa que, mesmo que não existissem incompatibilidades de chamada do sistema, é imensamente difícil colocar um programa no ram de maneira que ele possa ser executado.
Bibliotecas
Os programas raramente usam chamadas de sistema diretamente, no entanto, eles ganham quase exclusivamente sua funcionalidade, embora as bibliotecas que envolvem as chamadas de sistema em um formato um pouco mais amigável para a linguagem de programação, por exemplo, C tenha a Biblioteca Padrão C e glibc no Linux e libs similares e win32 em No Windows NT e acima, a maioria das outras linguagens de programação também possui bibliotecas semelhantes que agrupam a funcionalidade do sistema de maneira apropriada.
Essas bibliotecas podem, até certo ponto, superar os problemas de plataforma cruzada, conforme descrito acima, há uma variedade de bibliotecas projetadas para fornecer uma plataforma uniforme para aplicativos enquanto gerencia internamente chamadas para uma ampla variedade de sistemas operacionais , como SDL , isso significa que, embora programas não podem ser compatíveis com binários, programas que usam essas bibliotecas podem ter fontes comuns entre plataformas, tornando a portabilidade tão simples quanto recompilar.
Exceções ao acima
Apesar de tudo o que disse aqui, houve tentativas de superar as limitações de não poder executar programas em mais de um sistema operacional. Alguns bons exemplos são o projeto Wine, que emulou com êxito o carregador de programas win32, o formato binário e as bibliotecas do sistema, permitindo que os programas do Windows sejam executados em vários UNIXes. Há também uma camada de compatibilidade que permite que vários sistemas operacionais BSD UNIX executem o software Linux e, é claro, o próprio calço da Apple, permitindo executar um software MacOS antigo no MacOS X.
No entanto, esses projetos trabalham com níveis enormes de esforço de desenvolvimento manual. Dependendo da diferença entre os dois sistemas operacionais, a dificuldade varia de um calço razoavelmente pequeno até a emulação quase completa do outro sistema operacional, que geralmente é mais complexo do que escrever um sistema operacional inteiro em si e, portanto, essa é a exceção e não a regra.