Por que o SO do software é específico?

77

Estou tentando determinar os detalhes técnicos de por que o software produzido usando linguagens de programação para determinados sistemas operacionais só funciona com eles.

Entendo que os binários são específicos para determinados processadores devido à linguagem de máquina específica do processador que eles compreendem e aos diferentes conjuntos de instruções entre diferentes processadores. Mas de onde vem a especificidade do sistema operacional? Eu costumava assumir que eram APIs fornecidas pelo sistema operacional, mas depois vi esse diagrama em um livro: Diagrama

Sistemas operacionais - princípios internos e de design 7ª ed - W. Stallings (Pearson, 2012)

Como você pode ver, as APIs não são indicadas como parte do sistema operacional.

Se, por exemplo, eu criar um programa simples em C usando o seguinte código:

#include<stdio.h>

main()
{
    printf("Hello World");

}

O compilador está fazendo algo específico do SO ao compilar isso?

user139929
fonte
15
Você imprime em uma janela? ou um console? ou para memória gráfica? Como você coloca os dados lá? Observando printf para a Apple] [+ seria bem diferente do Mac OS 7 e, novamente, bem diferente do Mac OS X (apenas com uma 'linha' de computadores).
3
Porque se você escrevesse esse código para o Mac OS 7, ele apareceria em texto em uma nova janela. Se você fizesse isso na Apple] [+, estaria gravando diretamente em algum segmento da memória. No Mac OS X, ele é gravado em um console. Portanto, há três maneiras diferentes de escrever manipulando o código com base no hardware de execução tratado pela camada da biblioteca.
2
@StevenBurnap yep - pt.wikipedia.org/wiki/Aztec_C
10
Sua função FFT será executada com facilidade no Windows ou Linux (na mesma CPU), sem sequer recompilar. Mas então, como você vai exibir o resultado? Usando uma API do sistema operacional, é claro. ( printffrom msvcr90.dll não é o mesmo que printflibc.so.6)
user253751
9
Mesmo que as APIs "não façam parte do sistema operacional", elas ainda serão diferentes se você passar de um SO para outro. (O que, claro, levanta a questão de que a frase "não faz parte do sistema operacional" realmente significa, de acordo com o diagrama.)
Theodoros Chatzigiannakis

Respostas:

78

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.

Validade
fonte
6
+1 "Por que o SO do software é específico?" Porque história.
Paul Draper
2
o modelo de segurança da CPU é x86? por que e quando o modelo foi inventado?
N611x007
8
@naxa Não, por muito tempo é anterior ao x86, foi parcialmente implementado para o Multics em 1969, que é o primeiro sistema operacional com recursos úteis de compartilhamento de tempo para vários usuários que exigem esse modelo no computador GE-645 , mas essa implementação foi incompleta e contou com a No suporte a software, a primeira implementação completa e segura de hardware ocorreu em seu sucessor, o Honeywell 6180 . Isso foi totalmente baseado em hardware e permitiu ao Multics executar código de vários usuários sem a oportunidade de interferir.
Vality 9/07/2014
@Vality Além disso, o IBM LPAR é ~ 1972.
Elliott Frisch
@ ElliottFrisch uau, isso é impressionante. Eu não tinha percebido que era tão cedo. Obrigado por essa informação.
Vality 15/08/14
48

Como você pode ver, as APIs não são indicadas como parte do sistema operacional.

Eu acho que você está lendo muito no diagrama. Sim, um sistema operacional especificará uma interface binária de como as funções do sistema operacional são chamadas e também definirá um formato de arquivo para executáveis, mas também fornecerá uma API, no sentido de fornecer um catálogo de funções que podem ser chamadas por um aplicativo para chamar serviços do SO.

Eu acho que o diagrama está apenas tentando enfatizar que as funções do sistema operacional geralmente são chamadas por meio de um mecanismo diferente do que uma simples chamada de biblioteca. A maioria do sistema operacional comum usa interrupções no processador para acessar as funções do sistema operacional. Os sistemas operacionais modernos típicos não permitem que um programa do usuário acesse diretamente nenhum hardware. Se você quiser escrever um personagem no console, precisará solicitar ao sistema operacional que faça isso por você. A chamada do sistema usada para gravar no console variará de sistema operacional para sistema operacional; portanto, há um exemplo de por que o software é específico do sistema operacional.

printf é uma função da biblioteca de tempo de execução C e em uma implementação típica é uma função bastante complexa. Se você pesquisar no Google, poderá encontrar a fonte de várias versões online. Consulte esta página para uma visita guiada a um . Na grama, apesar de acabar fazendo uma ou mais chamadas de sistema, e cada uma dessas chamadas de sistema é específica para o sistema operacional host.

Charles E. Grant
fonte
4
E se tudo o que o programa fizesse fosse adicionar dois números, sem entrada ou saída. Esse programa ainda seria específico do sistema operacional?
Paul
2
Os sistemas operacionais destinam-se a colocar a maioria das coisas específicas de hardware atrás / em uma camada de abstração. No entanto, o próprio sistema operacional (a abstração) pode diferir de implementação para implementação. Há o POSIX que alguns SOs (mais ou menos) aderem e talvez outros, mas os SOs gerais simplesmente diferem demais em sua parte "visível" da abstração. Como dito anteriormente: você não pode abrir / home / usuário no Windows e não pode acessar HKEY_LOCAL_MACHINE \ ... em um sistema * N * X. Você pode escrever um software virtual ("emulação") para ajudar a aproximar esses sistemas, mas isso sempre será "de terceiros" (do OS POV).
RobIII
16
@Paul Yes. Em particular, a maneira como é empacotada em um executável seria específica do sistema operacional.
1811 OrangeDog
4
@ TimSeguine Não concordo com o seu exemplo do XP vs 7. Muito trabalho é feito pela Microsoft para garantir que a mesma API exista no 7 e no XP. Claramente, o que aconteceu aqui é que o programa foi projetado para executar contra uma determinada API ou contrato. O novo sistema operacional acabou de aderir à mesma API / contrato. No entanto, no caso do Windows, a API é muito proprietária, e é por isso que nenhum outro fornecedor de SO suporta. Mesmo assim, há um pedaço de um monte de exemplos de programas que não são executados no 7.
Artes
3
@Paul: Um programa que não faz Entrada / Saída é o programa vazio , que deve ser compilado para um no-op.
Bergi 9/07/2014
14

O compilador está fazendo algo específico do SO ao compilar isso?

Provavelmente. Em algum momento durante o processo de compilação e vinculação, seu código é transformado em um binário específico do SO e vinculado a todas as bibliotecas necessárias. Seu programa deve ser salvo em um formato que o sistema operacional espera, para que o SO possa carregar o programa e começar a executá-lo. Além disso, você está chamando a função de biblioteca padrão printf(), que em algum nível é implementada em termos dos serviços que o sistema operacional fornece.

As bibliotecas fornecem uma interface - uma camada de abstração do sistema operacional e do hardware - e isso possibilita recompilar seu programa para um sistema operacional ou hardware diferente. Mas essa abstração existe no nível da fonte - uma vez que o programa é compilado e vinculado, ele é conectado a uma implementação específica dessa interface, específica para um determinado sistema operacional.

Caleb
fonte
12

Há várias razões, mas uma razão muito importante é que o sistema operacional precisa saber como ler as séries de bytes que compõem seu programa na memória, encontrar as bibliotecas que acompanham esse programa e carregá-las na memória, e então comece a executar o código do seu programa. Para fazer isso, os criadores do sistema operacional criam um formato específico para essa série de bytes, para que o código do sistema operacional saiba onde procurar as várias partes da estrutura do seu programa. Como os principais sistemas operacionais têm autores diferentes, esses formatos geralmente têm pouco a ver um com o outro. Em particular, o formato executável do Windows tem pouco em comum com o formato ELF usado pela maioria das variantes do Unix. Portanto, todo esse carregamento, ligação dinâmica e código de execução devem ser específicos do SO.

Em seguida, cada sistema operacional fornece um conjunto diferente de bibliotecas para conversar com a camada de hardware. Essas são as APIs que você mencionou e geralmente são bibliotecas que apresentam uma interface mais simples para o desenvolvedor, convertendo-a em chamadas mais complexas e mais específicas para as profundezas do próprio sistema operacional, essas chamadas geralmente não são documentadas ou protegidas. Essa camada geralmente é bastante cinza, com as APIs "OS" mais recentes criadas parcialmente ou inteiramente em APIs antigas. Por exemplo, no Windows, muitas das APIs mais recentes que a Microsoft criou ao longo dos anos são essencialmente camadas sobre as APIs Win32 originais.

Um problema que não surge no seu exemplo, mas que é um dos maiores que os desenvolvedores enfrentam é a interface com o gerenciador de janelas, para apresentar uma GUI. Se o gerenciador de janelas faz parte do "SO" às vezes depende do seu ponto de vista, assim como do próprio SO, com a GUI no Windows sendo integrada ao SO em um nível mais profundo, enquanto as GUIs no Linux e OS X estão sendo mais diretamente separados. Isso é muito importante, porque hoje o que as pessoas costumam chamar de "O sistema operacional" é um animal muito maior do que o que os livros didáticos tendem a descrever, pois inclui muitos componentes no nível de aplicativos.

Finalmente, não é um problema estritamente do sistema operacional, mas um problema importante na geração de arquivos executáveis ​​é que máquinas diferentes têm destinos de linguagem de montagem diferentes e, portanto, o código do objeto gerado real deve ser diferente. Isso não é estritamente um problema de "SO", mas um problema de hardware, mas significa que você precisará de compilações diferentes para diferentes plataformas de hardware.

Gort the Robot
fonte
2
Pode valer a pena notar que formatos executáveis ​​mais simples podem ser carregados usando apenas uma pequena quantidade de RAM (se houver) além da necessária para armazenar o código carregado, enquanto formatos mais complexos podem exigir uma área de armazenamento de RAM muito maior durante e, em alguns casos, mesmo depois, carregando. O MS-DOS carregava arquivos COM de até 63,75K, simplesmente lendo bytes seqüenciais na RAM, começando no deslocamento 0x100 de um segmento arbitrário, carregava o CX com o endereço final e passava para ele. A compilação de passagem única pode ser realizada sem back-patching (útil com disquetes) por ... #
226
1
... ter o compilador a incluir em cada rotina uma lista de todos os pontos de correção, cada um dos quais incluiria o endereço da lista anterior e colocar o endereço da última lista no final do código. O sistema operacional carregaria apenas o código como bytes não processados, mas uma pequena rotina dentro do código poderia aplicar todos os patches de endereço necessários antes de executar a parte principal do código.
supercat
9

De outra resposta minha:

Considere as primeiras máquinas DOS e qual foi a real contribuição da Microsoft para o mundo:

O Autocad precisava escrever drivers para cada impressora na qual eles pudessem imprimir. O mesmo fez o lótus 1-2-3. De fato, se você quisesse imprimir seu software, teria que escrever seus próprios drivers. Se houvesse 10 impressoras e 10 programas, 100 partes diferentes do mesmo código teriam que ser escritas separadamente e independentemente.

O que o Windows 3.1 tentou realizar (junto com o GEM e tantas outras camadas de abstração) é fazer com que o fabricante da impressora escrevesse um driver para a impressora e o programador escrevesse um driver para a classe de impressoras do Windows.

Agora, com 10 programas e 10 impressoras, apenas 20 partes do código precisam ser escritas e, como o lado da Microsoft era o mesmo para todos, exemplos da MS significavam que você tinha muito pouco trabalho a fazer.

Agora, um programa não estava restrito apenas às 10 impressoras que eles escolheram para oferecer suporte, mas a todas as impressoras cujos fabricantes forneciam drivers no Windows.

Portanto, o sistema operacional fornece serviços aos aplicativos para que eles não precisem executar trabalhos redundantes.

Seu programa C de exemplo usa printf, que envia caracteres para stdout - um recurso específico do SO que exibirá os caracteres em uma interface do usuário. O programa não precisa saber onde está a interface do usuário - pode estar no DOS, pode estar em uma janela gráfica, pode ser canalizada para outro programa e usada como entrada para outro processo.

Como o sistema operacional fornece esses recursos, os programadores podem realizar muito mais com pouco trabalho.

No entanto, mesmo iniciar um programa é complicado. O sistema operacional espera que um arquivo executável tenha certas informações no início que digam ao sistema operacional como deve ser iniciado e, em alguns casos (ambientes mais avançados, como Android ou iOS), quais recursos serão necessários e que precisam de aprovação, pois tocam em recursos fora do "sandbox" - uma medida de segurança para ajudar a proteger usuários e outros aplicativos contra programas que se comportam mal.

Portanto, mesmo que o código da máquina executável seja o mesmo e não haja recursos do sistema operacional necessários, um programa compilado para o Windows não será executado em um sistema operacional OS X sem uma camada adicional de emulação ou conversão, mesmo no mesmo hardware exato.

Os sistemas operacionais antigos do DOS geralmente podiam compartilhar programas, porque implementavam a mesma API no hardware (BIOS) e o sistema operacional conectado ao hardware para fornecer serviços. Portanto, se você escreveu e compilou um programa COM - que é apenas uma imagem de memória de uma série de instruções do processador - você pode executá-lo no CP / M, no MS-DOS e em vários outros sistemas operacionais. Na verdade, você ainda pode executar programas COM em máquinas Windows modernas. Outros sistemas operacionais não usam os mesmos ganchos da API do BIOS, portanto, os programas COM não serão executados neles sem, novamente, uma camada de emulação ou conversão. Os programas EXE seguem uma estrutura que inclui muito mais do que meras instruções do processador e, portanto, juntamente com os problemas da API, não é executado em uma máquina que não entende como carregá-lo na memória e executá-lo.

Adam Davis
fonte
7

Na verdade, a resposta real é que, se todo sistema operacional compreendesse o mesmo layout de arquivo binário executável, e você se limitasse apenas a funções padronizadas (como na biblioteca padrão C) fornecidas pelo sistema operacional (que sistemas operacionais fornecem), então o software seria de fato, execute em qualquer sistema operacional.

Claro, a realidade é que não é esse o caso. Um EXEarquivo não tem o mesmo formato que um ELFarquivo, mesmo que ambos contenham código binário para a mesma CPU. * Portanto, cada sistema operacional precisaria ser capaz de interpretar todos os formatos de arquivo, e eles simplesmente não fizeram isso no diretório começando, e não havia razão para eles começarem a fazê-lo mais tarde (quase certamente por razões comerciais e não técnicas).

Além disso, seu programa provavelmente precisa fazer coisas que a biblioteca C não define como fazer (mesmo para coisas simples, como listar o conteúdo de um diretório) e, nesses casos, todo sistema operacional fornece suas próprias funções para alcançar seu objetivo. tarefa, naturalmente significando que não haverá um denominador comum mais baixo para você usar (a menos que você mesmo faça esse denominador).

Então, em princípio, é perfeitamente possível. De fato, o WINE executa executáveis ​​do Windows diretamente no Linux.
Mas é uma tonelada de trabalho e (geralmente) comercialmente injustificada.

* Nota: Há muito mais em um arquivo executável do que apenas código binário. Há uma tonelada de informações que informa o sistema operacional quais bibliotecas o arquivo depende, quanto pilha de memória que precisa, as funções que exporta para outras bibliotecas que pode depender dela, onde o sistema operacional pode encontrar informações de depuração relevante, como " re-localize "o arquivo na memória, se necessário, como fazer com que o tratamento de exceções funcione corretamente, etc. etc .... novamente, pode haver um formato único para o qual todos concordam, mas simplesmente não existe.

Mehrdad
fonte
Curiosidade: existe um formato binário posiz padronizado, que pode ser executado em sistemas operacionais. Não é apenas comumente usado.
Marcin
@ Marcin: Parece que você não considera o Windows um SO. (Ou você está dizendo que o Windows pode executar binários POSIX ?!) Para os fins da minha resposta, POSIX não é o tipo de padrão ao qual estou me referindo. O X no POSIX significa Unix. Ele nunca foi projetado para ser usado, por exemplo, no Windows, mesmo que o Windows possua um subsistema POSIX.
Mehrdad
1. Algo pode ser executado em vários sistemas operacionais sem executar em todos os sistemas operacionais; 2. Windows desde o NT foi capaz de executar binários posix.
Marcin
1
@ Marcin: (1) Como eu disse, o X no POSIX significa UNIX . Não é um padrão que deveria ser seguido por outros sistemas operacionais, mas apenas uma tentativa de alcançar um denominador comum entre os vários Unixes, o que é ótimo, mas não tão surpreendente. O fato de existirem vários tipos de sistemas operacionais Unix por aí é completamente irrelevante ao ponto que tenho tentado fazer em relação à compatibilidade com outros sistemas operacionais que não o Unix. (2) Você pode fornecer uma referência para o número 2?
Mehrdad
1
@ Mehrdad: Marcin está certo; Janelas SUA (Subsistema para Aplicativos UNIX) é compatível com POSIX
MSalters
5

O diagrama tem a camada "aplicativo" (principalmente) separada da camada "sistema operacional" pelas "bibliotecas" e isso implica que "aplicativo" e "SO" não precisam saber um do outro. Isso é uma simplificação no diagrama, mas não é bem verdade.

O problema é que a "biblioteca" tem realmente três partes: a implementação, a interface para o aplicativo e a interface para o sistema operacional. Em princípio, os dois primeiros podem ser "universais" no que diz respeito ao sistema operacional (depende de onde você o divide), mas a terceira parte - a interface para o sistema operacional - geralmente não pode. A interface do sistema operacional dependerá necessariamente do sistema operacional, das APIs fornecidas, do mecanismo de empacotamento (por exemplo, o formato do arquivo usado pela DLL do Windows) etc.

Como a "biblioteca" geralmente é disponibilizada como um único pacote, significa que, uma vez que o programa escolhe uma "biblioteca" para uso, ele se compromete com um sistema operacional específico. Isso acontece de duas maneiras: a) o programador escolhe completamente antecipadamente e, em seguida, a ligação entre a biblioteca e o aplicativo pode ser universal, mas a própria biblioteca está vinculada ao sistema operacional; ou b) o programador configura as coisas para que a biblioteca seja selecionada quando você executa o programa, mas o mecanismo de ligação em si, entre o programa e a biblioteca, depende do sistema operacional (por exemplo, o mecanismo DLL no Windows). Cada um tem suas vantagens e desvantagens, mas de qualquer forma você deve fazer uma escolha com antecedência.

Agora, isso não significa que é impossível fazer isso, mas você precisa ser muito inteligente. Para superar o problema, seria necessário escolher a biblioteca em tempo de execução e criar um mecanismo de ligação universal que não dependa do sistema operacional (portanto, você é responsável por mantê-lo, muito mais trabalho). Algumas vezes vale a pena.

Você não precisa, mas se você se esforçar para fazer isso, há uma boa chance de você não querer estar vinculado a um processador específico também, então você escreverá uma máquina virtual e compilará seu programa para um formato de código neutro do processador.

Até agora você já deve ter notado para onde estou indo. Plataformas de linguagem como Java fazem exatamente isso. O Java runtime (biblioteca) define a ligação neutra ao SO entre seu programa Java e a biblioteca (como o Java runtime abre e executa seu programa) e fornece uma implementação específica para o SO atual. O .NET faz a mesma coisa até certo ponto, exceto que a Microsoft não fornece uma "biblioteca" (tempo de execução) para nada além do Windows (mas outros fornecem - consulte Mono). E, na verdade, o Flash também faz a mesma coisa, embora seu escopo seja mais limitado ao Navegador.

Por fim, existem maneiras de fazer a mesma coisa sem um mecanismo de ligação personalizado. Você pode usar ferramentas convencionais, mas adie a etapa de ligação à biblioteca até que o usuário escolha o sistema operacional. É exatamente o que acontece quando você distribui o código fonte. O usuário pega o seu programa e o vincula ao processador (compile) e SO (vincule) quando o usuário estiver pronto para executá-lo.

Tudo depende de como você divide as camadas. No final do dia, você sempre tem um dispositivo de computação fabricado com hardware específico executando código de máquina específico. As camadas existem em grande parte como uma estrutura conceitual.

Euro Micelli
fonte
3

O software nem sempre é específico do SO. O Java e o sistema de código p anterior (e até o ScummVM) permitem software portátil nos sistemas operacionais. A Infocom (fabricantes do Zork e da Z-machine ) também tinha um banco de dados relacional baseado em outra máquina virtual. No entanto, em algum nível, algo precisa traduzir essas abstrações em instruções reais a serem executadas em um computador.

Elliott Frisch
fonte
3
No entanto, o Java é executado em uma máquina virtual, que não é entre sistemas operacionais. Você tem que usar um binário JVM diferente para cada OS
Izkata
3
@ Izkata True, mas você não recompila o software (apenas a JVM). Além disso, veja minha última frase. Mas vou salientar que a Sun tinha um microprocessador que poderia executar diretamente o código de bytes.
Elliott Frisch
3
Java é um sistema operacional, embora geralmente não seja considerado um. O software Java é específico para o Java OS e existem emuladores de Java OS para a maioria dos sistemas operacionais "reais". Mas você pode fazer o mesmo com qualquer host e sistema operacional de destino - como executar o software Windows no Linux usando o WINE.
precisa saber é o seguinte
@immibis eu seria mais específico. O Java Foundation Classes (JFC, a biblioteca padrão do Java) é uma estrutura. O próprio Java é uma linguagem. A JVM é semelhante a um sistema operacional: possui "Máquina Virtual" em seu nome e executa funções semelhantes a um sistema operacional da perspectiva do código em execução nele.
1

Você diz

o software produzido usando linguagens de programação para certos sistemas operacionais só funciona com eles

Mas o programa que você fornece como exemplo funcionará em muitos sistemas operacionais e até em alguns ambientes bare-metal.

O importante aqui é a distinção entre o código fonte e o binário compilado. A linguagem de programação C foi projetada especificamente para ser independente do SO na forma de origem. Isso é feito deixando a interpretação de coisas como "imprimir no console" até o implementador. Mas C pode estar em conformidade com algo que é específico do SO (consulte outras respostas por razões). Por exemplo, os formatos executáveis ​​PE ou ELF.

Dan
fonte
6
Parece bastante claro que o OP está perguntando sobre binários, não sobre o código fonte.
Caleb
0

Outras pessoas abordaram bem os detalhes técnicos, gostaria de mencionar uma razão menos técnica, o lado UX / UI:

Escreva uma vez, sinta-se estranho em todos os lugares

Todo sistema operacional possui suas próprias APIs de interface com o usuário e padrões de design. É possível escrever uma interface de usuário para um programa e executá-la em vários sistemas operacionais, mas isso garante que o programa pareça deslocado em qualquer lugar. Para criar uma boa interface do usuário, é necessário ajustar os detalhes de cada plataforma suportada.

Muitos desses são pequenos detalhes, mas eles os enganam e você frustrará seus usuários:

  • As caixas de diálogo de confirmação têm seus botões em ordem diferente no Windows e no OSX; entenda isso errado e os usuários clicarão no botão errado pela memória muscular. O Windows tem "Ok", "Cancelar" nessa ordem. O OSX trocou a ordem e o texto do botão do-it é uma breve descrição da ação a ser executada: "Cancelar", "Mover para a Lixeira".
  • O comportamento "Voltar" é diferente para iOS e Android. Os aplicativos iOS desenham seu próprio botão Voltar, conforme necessário, geralmente no canto superior esquerdo. O Android possui um botão dedicado no canto inferior esquerdo ou no canto inferior direito, dependendo da rotação da tela. As portas rápidas para o Android se comportarão incorretamente se o botão Voltar do sistema operacional for ignorado.
  • A rolagem dinâmica é diferente entre iOS, OSX e Android. Infelizmente, se você não estiver escrevendo código de interface do usuário nativo, provavelmente precisará escrever seu próprio comportamento de rolagem.

Mesmo quando é tecnicamente possível escrever uma base de código da interface do usuário que seja executada em qualquer lugar, é melhor fazer ajustes para cada sistema operacional suportado.

Nick Pinney
fonte
-2

Uma distinção importante neste momento é separar o compilador do vinculador. O compilador provavelmente produz mais ou menos a mesma saída (as diferenças são devidas principalmente a vários #if WINDOWSs). O vinculador, por outro lado, precisa lidar com todo o material específico da plataforma - vinculando as bibliotecas, criando o arquivo executável etc.

Em outras palavras, o compilador se preocupa principalmente com a arquitetura da CPU, porque está produzindo o código executável real e precisa usar as instruções e os recursos da CPU (observe que o IL do .NET ou o bytecode da JVM do .NET seriam considerados conjuntos de instruções de uma CPU virtual Nesta visão). É por isso que você deve compilar o código separadamente para x86e ARM, por exemplo.

O vinculador, por outro lado, precisa pegar todos esses dados e instruções brutos e colocá-lo em um formato que o carregador (atualmente, esse quase sempre seja o sistema operacional) possa entender, além de vincular bibliotecas vinculadas estaticamente (que também inclui o código necessário para vinculação dinâmica, alocação de memória etc.).

Em outras palavras, você pode compilar o código apenas uma vez e executá-lo no Linux e no Windows - mas é necessário vinculá- lo duas vezes, produzindo dois executáveis ​​diferentes. Agora, na prática, muitas vezes você também precisa fazer concessões no código (é aí que as diretivas do (pré-) compilador entram), então até compilar um link uma vez duas vezes não é muito usado. Sem mencionar que as pessoas estão tratando a compilação e o link como uma única etapa durante a compilação (assim como você não se importa mais com as partes do próprio compilador).

O software da era DOS costumava ser mais binário-portátil, mas você deve entender que ele também foi compilado não no DOS ou no Unix, mas em um contrato que era comum à maioria dos PCs no estilo IBM - descarregando o que hoje são chamadas de API para software interrompe. Isso não precisava de vinculação estática, pois você só precisava definir os registros necessários, por exemplo, chamar int 13hfunções gráficas, e a CPU saltou para um ponteiro de memória declarado na tabela de interrupção. É claro que, novamente, a prática foi muito mais complicada, porque para obter um desempenho do pedal do metal, era necessário escrever todos esses métodos, mas isso basicamente equivalia a percorrer o sistema operacional por completo. E, claro, há algo que invariavelmente precisa de interação com a API do SO - encerramento do programa. Mas ainda assim, se você usou os formatos mais simples disponíveis (por exemplo,COMno DOS, que não tem cabeçalho, apenas instruções) e não queria sair, boa sorte! E, é claro, você também pode lidar com a terminação adequada no tempo de execução, para poder ter código para terminação Unix e DOS no mesmo executável e detectar no tempo de execução qual usar :)

Luaan
fonte
este parece apenas repetir pontos explicados no presente e este respostas anteriores que foram postadas ontem
mosquito