Estou realmente curioso agora. Eu sou um programador Python, e essa pergunta me surpreendeu: você escreve um sistema operacional. Como você executa isso? Tem que ser executado de alguma forma, e dessa forma está dentro de outro sistema operacional?
Como um aplicativo pode ser executado sem estar em um sistema operacional? Como você instrui o computador a executar, digamos, C, e executar esses comandos na tela, se ele não possui um sistema operacional para executar?
Isso tem a ver com um kernel UNIX? Se sim, o que é um kernel Unix ou um kernel em geral?
Tenho certeza que os sistemas operacionais são mais complicados do que isso, mas como isso funciona?
kernel
operating-systems
Thor Correia
fonte
fonte
Respostas:
Existem muitos sites que passam pelo processo de inicialização (como Como os computadores são inicializados ). Em poucas palavras, é um processo de vários estágios que continua construindo o sistema um pouco de cada vez, até que possa finalmente iniciar os processos do SO.
Começa com o firmware na placa-mãe que tenta colocar a CPU em funcionamento. Em seguida, ele carrega o BIOS, que é como um mini sistema operacional que coloca o outro hardware em funcionamento. Feito isso, ele procura um dispositivo de inicialização (disco, CD, etc) e, uma vez encontrado, localiza o MBR (registro mestre de inicialização) e o carrega na memória e o executa. É esse pequeno pedaço de código que sabe como inicializar e iniciar o sistema operacional (ou outros gerenciadores de inicialização, à medida que as coisas ficam mais complicadas). É nesse ponto que coisas como o kernel serão carregadas e começarão a ser executadas.
É incrível que funcione!
fonte
Um sistema operacional "bare metal" não roda em nada. Ele executa o conjunto completo de instruções na máquina física e tem acesso a toda a memória física, a todos os registros de dispositivos e a todas as instruções privilegiadas, incluindo aquelas que controlam o hardware de suporte à memória virtual.
(Se o sistema operacional estiver sendo executado em uma máquina virtual, é possível que esteja na mesma situação acima. A diferença é que certas coisas são emuladas ou de alguma outra forma manipuladas pelo hipervisor; ou seja, o nível que executa as máquinas virtuais .)
De qualquer forma, embora o sistema operacional possa ser implementado em (por exemplo) C, ele não terá todas as bibliotecas C normais disponíveis. Em particular, ele não terá as bibliotecas 'stdio' normais. Em vez disso, implementará (por exemplo) um driver de dispositivo de disco que permita ler e gravar blocos de disco. Ele implementará um sistema de arquivos na parte superior da camada de bloco de disco e, além disso, implementará as chamadas do sistema que as bibliotecas de tempo de execução de um aplicativo de usuário fazem para (por exemplo) criar, ler e gravar arquivos ... e assim por diante.
Ele precisa ser um tipo especial de aplicativo (por exemplo, um sistema operacional) que saiba interagir diretamente com o hardware de E / S, etc.
Você não
O aplicativo (que era para fins de argumento escrito em C) é compilado e vinculado em alguma outra máquina para fornecer uma imagem de código nativa. Em seguida, a imagem é gravada no disco rígido em um local que o BIOS possa encontrá-la. O BIOS carrega a imagem na memória e executa uma instrução para pular para o ponto de entrada do aplicativo.
Geralmente, não existe "C e execução de comandos" no aplicativo, a menos que seja um sistema operacional completo. E, nesse caso, é responsabilidade do sistema operacional implementar toda a infraestrutura necessária para que isso aconteça. Nenhuma mágica. Apenas muito código.
A resposta de Bill cobre a inicialização, que é o processo no qual você passa de uma máquina desligada para uma máquina na qual o sistema operacional normal está em funcionamento. No entanto, é importante notar que, quando o BIOS conclui suas tarefas, ele (normalmente) cede o controle completo do hardware ao sistema operacional principal e não desempenha outra parte - até a próxima reinicialização do sistema. O sistema operacional principal certamente não está sendo executado "dentro" do BIOS no sentido convencional.
Sim.
O kernel UNIX é o núcleo do sistema operacional UNIX. É a parte do UNIX que faz todo o material "bare metal" descrito acima.
A idéia de um "kernel" é que você tenta separar o software do sistema em itens essenciais (que requerem acesso ao dispositivo físico, toda a memória, etc.) e itens não essenciais. O kernel consiste no material principal.
Na realidade, a distinção entre kernel / core e não-kernel / non-core é mais complicada do que isso. E tem havido muito debate sobre o que realmente pertence a um kernel e o que não pertence. (Procure o micro-kernel, por exemplo.)
fonte
The idea of a "kernel" is that you try to separate the system software into core stuff
Fácil de lembrar, observando que o termokernel
é do alemãoKern
, que significa núcleo / núcleo.No começo, não havia energia na CPU.
E o Homem disse "deixe que haja energia", e a CPU começou a ler de um determinado endereço na memória e executar a instrução que estava presente lá. Depois, o próximo e assim por diante até o fim do poder.
Esta foi a inicialização. Sua tarefa era carregar outro software para obter acesso ao ambiente, onde estava o software principal, e carregá-lo.
Por fim, uma tela amigável convidou você para fazer logon.
fonte
0x7C00
para qualquerx86
arquitetura compatível e primeiro tem de ser preenchido pelo BIOS, que normalmente carrega o primeiro setor do dispositivo qualquer que ele prefere arranque ... resposta agradável embora: -7Desculpe o atraso, mas vou descrevê-lo da seguinte maneira:
A placa-mãe recebe energia.
Os circuitos de temporização iniciam e estabilizam, se necessário, com base apenas em suas características elétricas. Alguns dispositivos mais recentes podem realmente usar um microprocessador ou seqüenciador muito limitado.
- jkerian às 5:20 de 25 de outubro
A energia é dada à CPU e à RAM.
A CPU carrega (com base na fiação interna) dados do BIOS. Em algumas máquinas, o BIOS pode ser espelhado na RAM e depois executado a partir daí, mas isso é raro IIRC.
-Micheal Steil, 17 erros cometidos pela Microsoft no sistema de segurança Xbox ( arquivo )
O BIOS faz chamadas para as portas e endereços de hardware usados pela placa-mãe para E / S de disco e outros hardwares e gira discos, coloca o restante da RAM funcionando, entre outras coisas.
O código do BIOS (por meio das configurações do CMOS, armazenadas no hardware) usa comandos IDE ou SATA de baixo nível para ler o setor de inicialização de cada disco, na ordem especificada pelo CMOS ou pela substituição de um usuário por um menu.
O primeiro disco com um setor de inicialização executa seu setor de inicialização. Esse setor de inicialização é um Assembly que possui instruções para carregar mais dados do disco, carregar um maior
NTLDR
, estágios posterioresGRUB
, etc.Por fim, o código da máquina do SO é executado pelo carregador de inicialização, direta ou indiretamente, através do carregamento em cadeia, carregando um setor de inicialização a partir de um local alternativo ou offset.
Você então sente um pânico amigável no kernel, um pinguim sufocado ou seu disco pára devido a um choque na cabeça. =) No cenário alternativo, seu kernel configura tabelas de processo, estruturas na memória e monta discos, carregando drivers, módulos e uma GUI ou conjunto de serviços (se estiver em um servidor). Em seguida, os programas são executados à medida que seus cabeçalhos são lidos e seu assembly é trazido para a memória e mapeado de acordo.
fonte
Há muitas boas respostas, mas eu gostaria de acrescentar: Você mencionou que é originário de um Python. Python é uma linguagem não interpretada (ou "interpilada" ou qualquer outra coisa, pelo menos nos casos de uso típicos do CPython). Isso significa que você tem algum outro software (o interpretador Python) olhando a fonte e executando-a de alguma maneira. Este é um modelo fino e permite linguagens de alto nível bastante agradáveis, abstraídas do hardware real. A desvantagem é que você sempre precisa desse software de intérprete primeiro.
Esse software de intérprete, normalmente, é escrito em uma linguagem que é compilada no código da máquina, por exemplo, C ou C ++. Código de máquina é o que a CPU pode suportar. O que uma CPU pode fazer é ler alguns bytes da memória e, dependendo dos valores dos bytes, iniciar uma operação específica. Portanto, uma sequência de bytes é um comando para carregar alguns dados da memória em um registro, outra sequência para adicionar dois valores, outra para armazenar o valor de um registro na memória principal e em breve (um registro é uma área de memória especial que faz parte da cpu onde pode funcionar melhor), a maioria desses comandos é bastante baixa nesse nível. O legível para humanos para essas instruções de código de máquina é o código do montador. Esse código de máquina é basicamente o que é armazenado nos arquivos .exe ou.com no Windows ou nos binários Linux / Unix.
Agora, se um computador é iniciado, ele é burro, mas ele possui alguma fiação, a qual lerá essas instruções de código de máquina. Em um PC, normalmente (atualmente) é um chip EEPROM na placa principal que contém o BIOS (sistema de saída básica de entrada), esse sistema não pode fazer muito, pode facilitar o acesso a alguns hardwares etc. e, em seguida, executar uma operação importante: vá para o inicialize e copie os primeiros bytes (também conhecido como registro mestre de inicialização, MBR) na memória e diga à CPU "aqui, aqui está o seu programa"; a CPU tratará esses bytes como código de máquina e executará. Normalmente, este é um carregador de sistema operacional que carrega o kernel com alguns parâmetros e, em seguida, transfere o controle para o kernel, que carrega todos os seus drivers para acessar todo o hardware, carrega algum programa de desktop ou shell ou qualquer outra coisa e permite que o usuário faça o login e use o sistema
fonte
Você pergunta "Como um aplicativo pode ser executado sem estar em um sistema operacional". A resposta fácil é "um sistema operacional não é um aplicativo". Embora um sistema operacional possa ser criado com as mesmas ferramentas que um aplicativo e feito da mesma matéria-prima, eles não são a mesma coisa. Um sistema operacional não precisa jogar pelas mesmas regras que um aplicativo.
OTOH, você pode pensar no hardware e no firmware reais como o "SO" no qual o "aplicativo" do SO é executado. O hardware é um sistema operacional muito simples - ele sabe como executar instruções escritas em código de máquina e sabe que, quando inicializado, deve procurar um endereço de memória muito específico para sua primeira instrução. Portanto, ele inicia e executa imediatamente a primeira instrução, seguida pela segunda e assim por diante.
Portanto, o sistema operacional é simplesmente um código de máquina que existe em um local conhecido e que pode interagir diretamente com o hardware.
fonte
A resposta à sua pergunta requer o conhecimento de como o código nativo (para CPU) se parece e como é interpretado pela CPU.
Normalmente, todo o processo de compilação é baseado na tradução de coisas que você escreve em C, Pascal ou até Python (usando pypy) e C # em coisas que a CPU entende, isto é, instruções simples como "armazenar algo em [endereço de memória]", "adicionar números armazenados em registros eax e ebx "," chame a função foo "," compare eax a 10 ". Essas instruções, executadas uma a uma, fazem o que você queria fazer com seu código.
Agora pense sobre isso: você realmente não precisa de um sistema operacional para executar este código nativo! Tudo o que você precisa é carregar esse código na memória e informar à CPU que está lá e você deseja que ele seja executado. Não se preocupe muito com isso, no entanto. Esse é o trabalho que o BIOS deve se preocupar - ele carrega seu código (apenas um setor), logo após o início da CPU, no endereço físico 0x7C00. A CPU começa a executar esse setor (512 B) do seu código. E você pode fazer o que você imaginar! Sem, é claro, qualquer suporte do sistema operacional. Isso porque você é o sistema operacional. Legal né? Sem biblioteca padrão, sem impulso, sem python, sem programas, sem drivers! Você tem que escrever tudo sozinho.
E como você se comunica com o hardware? Bem, você tem duas opções:
Agora você está perguntando o que é o kernel. Logo, o kernel é tudo o que você não vê e experimenta diretamente. Ele gerencia, juntamente com os drivers, tudo, desde o teclado até quase todas as peças de hardware do seu PC. Você se comunica com ele por shell ou terminal gráfico. Ou por funções dentro do seu código, agora executadas, felizmente, com suporte do sistema operacional.
Para uma melhor compreensão, posso lhe dar um conselho: tente escrever seu próprio sistema operacional. Mesmo que ele escreva "Hello world" na tela.
fonte
Existem algumas diferenças na maneira como um sistema operacional opera que são extremamente dependentes do sistema. Para ser útil, um sistema precisa ter algum comportamento previsível na inicialização, como "iniciar a execução no endereço X". Para sistemas que possuem armazenamento não volátil (como memória Flash) mapeado em seu espaço de programa, isso é bastante fácil, pois você apenas coloca o código de inicialização no local correto no espaço de programa do processador. Isso é extremamente comum para microcontroladores. Alguns sistemas precisam recuperar seus programas de inicialização de outro local antes de executá-lo. Esses sistemas terão algumas operações com fio (ou quase com fio) neles. Existem alguns processadores que recuperam seu código de inicialização via i2c de outro chip,
Os sistemas que usam a família de processadores x86 geralmente usam um processo de inicialização em vários estágios que é bastante complexo devido a sua evolução e problemas de compatibilidade com versões anteriores. O sistema executa algum firmware (chamado BIOS - Basic Input / Output System, ou similar) que está em alguma memória não volátil da placa-mãe. Às vezes, parte ou todo esse firmware é copiado (realocado) para a RAM para torná-lo mais rápido. Este código foi escrito com o conhecimento de qual hardware estaria presente e utilizável na inicialização.
O firmware de inicialização geralmente é escrito com suposições sobre qual hardware estará presente no sistema. Anos atrás, em uma máquina 286, provavelmente haveria uma suposição de que haveria um controlador de unidade de disquete no endereço de E / S X e carregaria o setor 0 em um determinado local de memória se determinado conjunto de comandos (e o código no setor 0 sabe como usar as próprias funções do BIOS para carregar mais código e, eventualmente, código suficiente para carregar um sistema operacional). Em um microcontrolador, pode-se supor que exista uma porta serial operando com determinadas configurações que ela deve esperar pelos comandos (para atualizar firmware mais complexo) por um período de tempo X antes de continuar com o processo de inicialização.
O processo exato de inicialização de um determinado sistema não é tão importante para você quanto saber que ele difere em sistemas diferentes, mas também que todos eles têm coisas em comum. Geralmente, no código de inicialização (inicialização), quando é necessário executar a E / S, os dispositivos de E / S são pesquisados em vez de depender de interrupções. Isso ocorre porque as interrupções são complexas, use a pilha RAM (que ainda não está totalmente configurada) e você não precisa se preocupar em bloquear outras operações quando for a única operação.
Ao ser carregado pela primeira vez, o kernel do sistema operacional (o kernel é a parte principal da maioria dos sistemas operacionais) atuará inicialmente como o firmware. Ele precisará ser programado com o conhecimento ou descobrir o hardware presente, configurar alguma RAM como espaço de pilha, fazer vários testes, configurar várias estruturas de dados, possivelmente descobrir e montar um sistema de arquivos e, provavelmente, iniciar algum programa que seja mais como os programas que você está acostumado a escrever (um programa que conta com a presença de um sistema operacional).
O código do SO geralmente é escrito em uma mistura de C e montagem. O primeiro código para o kernel do sistema operacional provavelmente está sempre em montagem e faz coisas como configurar a pilha, na qual o código C se baseia, e depois chama uma função C. Outra montagem escrita à mão também estará lá porque algumas operações que um sistema operacional precisa realizar geralmente não são expressáveis em C (como pilhas de troca / troca de contexto). Freqüentemente, sinalizadores especiais precisam ser passados para o compilador C para dizer a ele para não confiar nas bibliotecas padrão usadas pela maioria dos programas em C e para não esperar que exista um
int main(int argc, char *argv[])
no programa. Além disso, opções especiais de vinculador que a maioria dos programadores de aplicativos nunca usa precisam ser usadas. Isso pode fazer com que o programa do kernel espere ser carregado em um determinado endereço ou configure coisas para parecer que existem variáveis externas em determinados locais, embora essas variáveis nunca tenham sido declaradas em nenhum código C (isso é útil para E / S mapeada na memória ou outros locais especiais de memória).Toda a operação parece mágica no começo, mas depois que você a examina e entende partes dela, a mágica se torna apenas um conjunto de programas que exigem muito mais planejamento e conhecimento do sistema para serem implementados. Depurá-los, no entanto, requer mágica.
fonte
Para entender como os sistemas operacionais funcionam, pode ser útil dividi-los em duas categorias: aqueles que simplesmente fornecem serviços aos aplicativos mediante solicitação e aqueles que usam recursos de hardware na CPU para impedir que os aplicativos façam coisas que não deveriam. O MS-DOS era do estilo anterior; todas as versões do Windows desde a versão 3.0 têm o último estilo (pelo menos ao executar algo mais poderoso que um 8086).
O IBM PC original executando o PC-DOS ou MS-DOS teria sido um exemplo do estilo antigo de "OS". Se um aplicativo desejasse exibir um personagem na tela, haveria algumas maneiras de fazê-lo. Pode chamar a rotina que solicitaria ao MS-DOS para enviá-la para "saída padrão". Se isso acontecesse, o MS-DOS verificaria se a saída estava sendo redirecionada e, se não, chamaria uma rotina armazenada na ROM (em uma coleção de rotinas que a IBM denominou Sistema Básico de Entrada / Saída) que exibirá um caractere no posição do cursor e mova o cursor ("escrever teletipo"). Essa rotina do BIOS armazenaria um par de bytes em algum lugar no intervalo 0xB800: 0 a 0xB800: 3999; o hardware no adaptador gráfico colorido buscará repetidamente pares de bytes dentro desse intervalo, usando o primeiro byte de cada par para selecionar uma forma de caractere e o segundo para selecionar as cores de primeiro e segundo plano. Os bytes são buscados e processados em sinais vermelhos, verdes e azuis, em uma sequência que produz uma exibição de texto legível.
Os programas no PC IBM podem exibir texto usando a rotina de "saída padrão" do DOS ou a rotina de "gravação teletipo" do BIOS ou armazenando-a diretamente para exibir a memória. Muitos programas que precisavam exibir muito texto optaram rapidamente pela última abordagem, pois ela pode ser literalmente centenas de vezes mais rápida que usar as rotinas do DOS. Isso não ocorreu porque as rotinas de DOS e BIOS eram excepcionalmente ineficientes; a menos que a tela esteja em branco, ela só poderá ser gravada em determinados momentos. A rotina do BIOS para gerar um caractere foi projetada para poder ser chamada a qualquer momento; portanto, cada solicitação teve que começar novamente, aguardando o momento certo para executar uma operação de gravação. Por outro lado, o código do aplicativo que sabia o que precisava fazer poderia se organizar em torno das oportunidades disponíveis para gravar a exibição.
Um ponto importante aqui é que, embora o DOS e o BIOS forneçam um meio de enviar texto para a tela, não havia nada de particularmente "mágico" nessas habilidades. Um aplicativo que desejasse escrever texto no monitor poderia fazê-lo com a mesma eficácia, pelo menos se o hardware do monitor funcionasse da maneira esperada pelo aplicativo (se alguém tivesse instalado um adaptador de monitor monocromático, semelhante ao CGA, mas com memória de caracteres localizado em 0xB000: 0000-0xB000: 3999), o BIOS geraria automaticamente caracteres ali; um aplicativo programado para funcionar com o MDA ou o CGA também poderia fazê-lo, mas um aplicativo programado apenas para o CGA seria totalmente inútil no MDA).
Em sistemas mais novos, as coisas são um pouco diferentes. Os processadores têm vários modos de "privilégio". Eles começam no modo mais privilegiado, onde o código pode fazer o que quiser. Eles podem alternar para um modo restrito, onde apenas intervalos de memória selecionados ou recursos de E / S estão disponíveis. O código não pode alternar diretamente de um modo restrito para o modo privilégio, mas o processador definiu pontos de entrada no modo privilegiado e o código no modo restrito pode solicitar ao processador que comece a executar o código em um desses pontos de entrada no modo privilegiado. Além disso, existem pontos de entrada no modo privilegiado associados a várias operações que seriam proibidas no modo restrito. Suponha, por exemplo, que alguém queira executar vários aplicativos do MS-DOS simultaneamente, cada um com sua própria tela. Se os aplicativos pudessem gravar diretamente no controlador de vídeo em 0xB800: 0, não haveria maneira de impedir que um aplicativo substituísse a tela de outro aplicativo. Por outro lado, um sistema operacional pode executar o aplicativo no modo restrito e interceptar todos os acessos à memória do monitor; se descobrisse que um aplicativo que deveria estar em segundo plano tentando gravar 0xB800: 160, poderia armazenar os dados em alguma memória que havia reservado como um buffer de tela de aplicativo em segundo plano. Se esse aplicativo for posteriormente alternado para o primeiro plano, o buffer poderá ser copiado para a tela real. um sistema operacional pode executar o aplicativo no modo restrito e interceptar todos os acessos à memória do monitor; se descobrisse que um aplicativo que deveria estar em segundo plano tentando gravar 0xB800: 160, poderia armazenar os dados em alguma memória que havia reservado como um buffer de tela de aplicativo em segundo plano. Se esse aplicativo for posteriormente alternado para o primeiro plano, o buffer poderá ser copiado para a tela real. um sistema operacional pode executar o aplicativo no modo restrito e interceptar todos os acessos à memória do monitor; se descobrisse que um aplicativo que deveria estar em segundo plano tentando gravar 0xB800: 160, poderia armazenar os dados em alguma memória que havia reservado como um buffer de tela de aplicativo em segundo plano. Se esse aplicativo for posteriormente alternado para o primeiro plano, o buffer poderá ser copiado para a tela real.
As principais coisas a serem observadas são (1) embora muitas vezes seja útil ter um conjunto padrão de rotinas para executar vários serviços padrão, como exibir texto, eles não fazem nada que um aplicativo que estava sendo executado no "modo privilegiado" não poderia fazer se foi programado corretamente para lidar com o hardware que foi instalado; (2) embora a maioria dos aplicativos em execução hoje seja impedida pelo sistema operacional de executar essas E / S diretamente, um programa que é iniciado no modo privilegiado pode fazer o que quiser e pode configurar as regras que deseja para o modo restrito programas.
fonte
Como Stephen C. disse, não se trata apenas de iniciar o sistema operacional, mas também de como ele roda, interage com o hardware e com o software em cima dele.
Vou acrescentar à sua resposta que você pode dar uma olhada em "Os elementos dos sistemas de computação" . É um livro e algumas ferramentas que explicam como um computador, sistema operacional e compiladores interagem. O único aspecto disso é que ele fornece as ferramentas para desenvolver rapidamente seu próprio sistema operacional em um ambiente simulado, ignorando os muitos detalhes necessários para um real, para que você possa entender os conceitos . Ele faz um ótimo trabalho em deixar você ver a floresta em vez das árvores.
Se você quiser entrar em mais detalhes sobre como o sistema operacional interage com o hardware, consulte o Minix .
fonte
Seu aplicativo está sendo executado em um sistema operacional. Este sistema operacional fornece serviços ao seu aplicativo, como abrir um arquivo e escrever bytes nele. Esses serviços geralmente são fornecidos por meio de chamadas do sistema.
O sistema operacional está sendo executado no hardware. O hardware fornece serviços ao sistema operacional, como definir a taxa de transmissão de uma porta serial e gravar bytes nela. Esses serviços geralmente são fornecidos por meio de registradores mapeados na memória ou portas de E / S.
Para dar um exemplo muito simplificado de como isso funciona:
Seu aplicativo diz ao sistema operacional para gravar algo em um arquivo. Para o seu aplicativo, o sistema operacional fornece conceitos como arquivos e diretórios.
No hardware, esses conceitos não existem. O hardware fornece conceitos como discos divididos em blocos fixos de 512 bytes. O sistema operacional decide quais blocos usar para o seu arquivo e alguns outros blocos para os metadados, como o nome, o tamanho e o local do disco. Em seguida, informa ao hardware: grave esses 512 bytes no setor com esse número no disco com esse número; escreva esses outros 512 bytes no setor com esse número diferente no disco com o mesmo número; e assim por diante.
A maneira como o sistema operacional instrui o hardware a fazer isso varia muito. Uma das funções de um sistema operacional é proteger os aplicativos dessas diferenças. Para o exemplo do disco, em um tipo de hardware, o sistema operacional precisaria gravar o número do disco e do setor em uma porta de E / S e, em seguida, gravar os bytes um a um em uma porta de E / S separada. Em outro tipo de hardware, o sistema operacional teria que copiar os 512 bytes inteiros de um setor em uma área de memória, gravar o local dessa área de memória em um local de memória especial e gravar o número do disco e do setor em outro local de memória especial.
O hardware de ponta de hoje é extremamente complicado. Os manuais que fornecem todos os detalhes de programação são batentes de porta com milhares de páginas; por exemplo, o mais recente manual da CPU Intel é de sete volumes, com um total de mais de 4000 páginas - e isso é apenas para a CPU. A maioria dos outros componentes expõe blocos de memória ou portas de E / S, que o sistema operacional pode solicitar à CPU para mapear para endereços dentro de seu espaço de endereço. Vários desses componentes expõem ainda mais coisas atrás de algumas portas de E / S ou endereços de memória; como exemplo, o RTC (Real Time Clock, o componente que mantém o tempo do computador desligado) expõe algumas centenas de bytes de memória atrás de um par de portas de E / S, e esse é um componente muito simples que remonta a o PC / AT original. Coisas como discos rígidos têm processadores separados inteiros, com o qual o sistema operacional se comunica por meio de comandos padronizados. GPUs são ainda mais complicadas.
Várias pessoas nos comentários acima sugeriram o Arduino. Concordo com eles, é muito mais simples de entender - o ATmega328, que faz tudo no Arduino Uno, exceto a exposição do conector USB como porta serial, possui um manual com apenas algumas centenas de páginas. No Arduino, você roda diretamente no hardware, sem sistema operacional no meio; apenas algumas pequenas rotinas de biblioteca, que você não precisará usar se não quiser.
fonte
Exemplos executáveis
Tecnicamente, um programa que é executado sem um sistema operacional é um sistema operacional. Então, vamos ver como criar e executar alguns SOs minúsculos do Hello World.
O código de todos os exemplos abaixo está presente neste repositório do GitHub .
Setor de inicialização
No x86, a coisa mais simples e de nível mais baixo que você pode fazer é criar um MBR (setor de inicialização mestre) , que é um tipo de setor de inicialização , e depois instalá-lo em um disco.
Aqui, criamos um com uma única
printf
chamada:Resultado:
Testado no Ubuntu 18.04, QEMU 2.11.1.
main.img
contém o seguinte:\364
in octal ==0xf4
in hex: a codificação de umahlt
instrução, que diz à CPU para parar de funcionar.Portanto, nosso programa não fará nada: apenas inicie e pare.
Usamos octal porque os
\x
números hexadecimais não são especificados pelo POSIX.Poderíamos obter essa codificação facilmente com:
mas a
0xf4
codificação também está documentada no manual da Intel, é claro.%509s
produzir 509 espaços. Necessário preencher o arquivo até o byte 510.\125\252
em octal ==0x55
seguido por0xaa
: bytes mágicos requeridos pelo hardware. Eles devem ser bytes 511 e 512.Se não estiver presente, o hardware não tratará isso como um disco inicializável.
Observe que, mesmo sem fazer nada, alguns caracteres já estão impressos na tela. Esses são impressos pelo firmware e servem para identificar o sistema.
Executar em hardware real
Os emuladores são divertidos, mas o hardware é o verdadeiro negócio.
Observe, no entanto, que isso é perigoso e você pode limpar seu disco por engano: faça isso apenas em máquinas antigas que não contêm dados críticos! Ou melhor ainda, devboards como o Raspberry Pi, veja o exemplo do ARM abaixo.
Para um laptop típico, você precisa fazer algo como:
Grave a imagem em um dispositivo USB (destruirá seus dados!):
conecte o USB ao computador
ligue
diga para inicializar a partir do USB.
Isso significa fazer com que o firmware escolha USB antes do disco rígido.
Se esse não é o comportamento padrão da sua máquina, continue pressionando Enter, F12, ESC ou outras teclas estranhas após a inicialização, até que você obtenha um menu de inicialização onde você pode selecionar para inicializar a partir do USB.
Geralmente, é possível configurar a ordem de pesquisa nesses menus.
Por exemplo, no meu antigo Lenovo Thinkpad T430, UEFI BIOS 1.16, posso ver:
Olá Mundo
Agora que criamos um programa mínimo, vamos para o mundo olá.
A pergunta óbvia é: como fazer IO? Algumas opções:
porta serial . Este é um protocolo padronizado muito simples que envia e recupera caracteres de um terminal host.
Fonte .
Infelizmente, ele não está exposto na maioria dos laptops modernos, mas é o caminho mais comum para as placas de desenvolvimento, veja os exemplos de ARM abaixo.
Isso é realmente uma pena, pois essas interfaces são realmente úteis para depurar o kernel do Linux, por exemplo .
use recursos de depuração de chips. O ARM chama de semi - hospedagem deles, por exemplo. Em hardware real, requer algum suporte extra de hardware e software, mas em emuladores pode ser uma opção conveniente e gratuita. Exemplo .
Aqui vamos fazer um exemplo de BIOS, pois é mais simples no x86. Mas observe que este não é o método mais robusto.
main.S
link.ld
Monte e vincule com:
Resultado:
Testado em: Lenovo Thinkpad T430, UEFI BIOS 1.16. Disco gerado em um host Ubuntu 18.04.
Além das instruções de montagem padrão da userland, temos:
.code16
: diz ao GAS para gerar código de 16 bitscli
: desativar interrupções de software. Isso pode fazer com que o processador volte a funcionar após ohlt
int $0x10
: faz uma chamada do BIOS. É isso que imprime os caracteres um por um.Os sinalizadores de link importantes são:
--oformat binary
: produz código de montagem binário bruto, não o distorce dentro de um arquivo ELF, como é o caso de executáveis regulares da terra do usuário.Use C em vez de montagem
Como o C é compilado no assembly, o uso do C sem a biblioteca padrão é bastante simples, você basicamente precisa:
main
, principalmente:TODO: link para algum exemplo x86 no GitHub. Aqui está um ARM que eu criei .
No entanto, as coisas ficam mais divertidas se você quiser usar a biblioteca padrão, já que não temos o kernel Linux, o que implementa grande parte da funcionalidade da biblioteca padrão C através do POSIX .
Algumas possibilidades, sem acessar um sistema operacional completo como o Linux, incluem:
Newlib
Exemplo detalhado em: https://electronics.stackexchange.com/questions/223929/c-standard-libraries-on-bare-metal/223931
No Newlib, você precisa implementar os syscalls, mas obtém um sistema muito mínimo e é muito fácil implementá-los.
Por exemplo, você pode redirecionar
printf
para os sistemas UART ou ARM ou implementarexit()
com semi - hospedagem .sistemas operacionais embarcados como FreeRTOS e Zephyr .
Esses sistemas operacionais normalmente permitem desativar o agendamento preventivo, fornecendo controle total sobre o tempo de execução do programa.
Eles podem ser vistos como uma espécie de Newlib pré-implementado.
BRAÇO
No ARM, as idéias gerais são as mesmas. Eu enviei:
alguns exemplos simples de baremetal do QEMU aqui no GitHub . O exemplo prompt.c recebe as entradas do seu terminal host e retorna a saída por todo o UART simulado:
Consulte também: https://stackoverflow.com/questions/38914019/how-to-make-bare-metal-arm-programs-and-run-them-on-qemu/50981397#50981397
uma configuração totalmente automatizada de blinker Raspberry Pi em: https://github.com/cirosantilli/raspberry-pi-bare-metal-blinker
Consulte também: https://stackoverflow.com/questions/29837892/how-to-run-ac-program-with-no-os-on-the-raspberry-pi/40063032#40063032
Para o Raspberry Pi, https://github.com/dwelch67/raspberrypi parece o tutorial mais popular disponível hoje.
Algumas diferenças do x86 incluem:
IO é feito escrevendo diretamente para endereços de mágica, não há
in
eout
instruções.Isso é chamado de E / S mapeada na memória .
para hardware real, como o Raspberry Pi, você pode adicionar o firmware (BIOS) à imagem do disco.
Isso é bom, pois torna a atualização desse firmware mais transparente.
Firmware
Na verdade, seu setor de inicialização não é o primeiro software executado na CPU do sistema.
O que realmente roda primeiro é o chamado firmware , que é um software:
Firmwares conhecidos incluem:
O firmware faz coisas como:
faça um loop sobre cada disco rígido, USB, rede etc. até encontrar algo inicializável.
Quando executamos o QEMU,
-hda
diz quemain.img
é um disco rígido conectado ao hardware ehda
é o primeiro a ser experimentado e é usado.carregue os primeiros 512 bytes no endereço de memória RAM
0x7c00
, coloque o RIP da CPU lá e deixe executarmostrar coisas como o menu de inicialização ou as chamadas de impressão do BIOS no visor
O firmware oferece funcionalidade semelhante ao sistema operacional da qual a maioria dos sistemas operacionais depende. Por exemplo, um subconjunto Python foi portado para execução no BIOS / UEFI: https://www.youtube.com/watch?v=bYQ_lq5dcvM
Pode-se argumentar que os firmwares são indistinguíveis dos sistemas operacionais, e que o firmware é a única programação bare metal "verdadeira" que se pode fazer.
Como este desenvolvedor do CoreOS coloca :
Estado inicial do BIOS pós
Como muitas coisas no hardware, a padronização é fraca, e uma das coisas em que você não deve confiar é o estado inicial dos registros quando o código começa a ser executado após o BIOS.
Então faça um favor a si mesmo e use algum código de inicialização como o seguinte: https://stackoverflow.com/a/32509555/895245
Os registros gostam
%ds
e%es
têm efeitos colaterais importantes, portanto, você deve zerá-los, mesmo que não os esteja usando explicitamente.Observe que alguns emuladores são mais agradáveis que o hardware real e fornecem um bom estado inicial. Então, quando você roda em hardware real, tudo quebra.
Inicialização múltipla GNU GRUB
Os setores de inicialização são simples, mas não são muito convenientes:
É por esses motivos que o GNU GRUB criou um formato de arquivo mais conveniente chamado de inicialização múltipla.
Exemplo de trabalho mínimo: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
Também o uso no repositório de exemplos do GitHub para poder executar facilmente todos os exemplos em hardware real sem queimar o USB um milhão de vezes. No QEMU, fica assim:
Se você preparar seu sistema operacional como um arquivo de inicialização múltipla, o GRUB poderá encontrá-lo dentro de um sistema de arquivos comum.
É isso que a maioria das distros faz, colocando as imagens do SO em baixo
/boot
.Os arquivos de inicialização múltipla são basicamente um arquivo ELF com um cabeçalho especial. Eles são especificados pelo GRUB em: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
Você pode transformar um arquivo de inicialização múltipla em um disco inicializável com
grub-mkrescue
.El Torito
Formato que pode ser gravado em CDs: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
Também é possível produzir uma imagem híbrida que funcione em ISO ou USB. Isso pode ser feito com
grub-mkrescue
( exemplo ) e também é feito pelo kernel do Linux nomake isoimage
usoisohybrid
.Recursos
fonte