Escola de montagem: desenvolvimento de sistema operacional. Vôo do Beija-flor. Do que é capaz um sistema operacional escrito inteiramente em linguagem assembly. Montagem e compilação

Recentemente decidi aprender assembler, mas não estava interessado em desperdiçar linhas de código. Achei que, ao estudar montador, iria dominar alguma área do assunto. Então minha escolha recaiu em escrever um bootloader. O resultado das minhas descobertas está aqui neste blog.

Gostaria de dizer desde já que adoro a teoria aliada à prática, então vamos começar.

Primeiro vou mostrar como criar um simples MBR para que possamos aproveitar o resultado o mais rápido possível. À medida que nos tornamos mais complexos com exemplos práticos, darei informações teóricas.

Primeiro, vamos fazer um bootloader para uma unidade flash USB!

Atenção!!! Nosso primeiro programa assembler funcionará tanto para uma unidade flash quanto para outros dispositivos, como um disquete ou Disco rígido. Posteriormente, para que todos os exemplos funcionem corretamente, darei uma série de esclarecimentos sobre o funcionamento do código nos diferentes dispositivos.

Vamos escrever em Fasm, já que é considerado o melhor compilador para escrever carregadores, o que é MBR. A segunda razão para escolher o Fasm é que ele torna a compilação de arquivos muito fácil. Sem diretivas linha de comando e assim por diante. bobagem que pode desencorajá-lo completamente de aprender assembler e atingir seus objetivos. Portanto, no estágio inicial precisaremos de dois programas e alguns desnecessário Unidade flash de tamanho mínimo. Desenterrei 1 Gb (formata rapidamente e não é uma pena). Depois que nosso bootloader funcionar, a unidade flash não funcionará mais normalmente. Meu Windows 7 se recusa a formatar a unidade flash. Eu recomendo usar um utilitário para trazer a unidade flash de volta à vida HP Disco USB Ferramenta de formato de armazenamento ( HPUSBFW.EXE) ou outros utilitários para formatar unidades flash.

Nós os instalamos e colocamos os atalhos correspondentes na área de trabalho ou onde você quiser.

A preparação está concluída, vamos passar à ação

Abra Fasmw.exe e escreva o seguinte lá. Esboçaremos o mínimo de código para ver o resultado. Mais tarde analisaremos o que está rabiscado aqui. Darei meus comentários brevemente.

Código FASM: ============= boot.asm ==============

organização 7C00h ; Os endereços dos nossos programas são calculados tendo em conta esta directiva

usar16; código hexadecimal é gerado

cli ;desativa interrupções para alteração de endereços em registradores de segmento

machado mov, 0

movimento sp, 7h00

sti ;habilitar interrupções (depois de alterar endereços)

mov ax, 0003h ;define o modo de vídeo para exibir uma linha na tela

dentro das 10h

mov ax, 1301h ;a saída real da string é a função 13h int 10h (mais detalhes posteriormente)

mov bp, stroka ;endereço da string de saída

mov dx, 0000h ;linha e coluna em que o texto é exibido

mov cx, 15 ;número de caracteres da string de saída

mov bx, 000eh ;00 número da página do vídeo (melhor não tocar) atributos de 0e caracteres (cor, fundo)

dentro das 10h

jmp $ ;tread water (faz um loop no programa neste ponto)

string db "Ok, MBR carregado!"

vezes 510 - ($ - $$) db 0 ;preenchendo o espaço entre o byte anterior e o próximo com zeros

db 0x55, 0xAA; últimos dois bytes

Compile este código (Ctrl+F9) no fasm"e e salve o arquivo binário resultante como boot.bin em algum local conveniente. Antes de gravar nosso binário em uma unidade flash, um pouco de teoria.

Quando você conecta uma unidade flash ao computador, não é absolutamente óbvio para o sistema BIOS que você deseja inicializar a partir da unidade flash, portanto, nas configurações do BIOS, você precisa selecionar o dispositivo a partir do qual deseja inicializar. Então escolhemos para inicializar a partir de USB (você terá que descobrir como fazer isso sozinho, já que a interface do BIOS tem diferentes variações... você pode pesquisar no Google Configurações do BIOS Para o seu placa-mãe. Não há nada complicado aí, via de regra).

Agora que o BIOS sabe que você deseja inicializar a partir de uma unidade flash, ele deve garantir que o setor zero da unidade flash seja inicializável. Para fazer isso, o BIOS verifica últimos dois bytes do setor zero e, se forem iguais a 0x55 0xAA, só então será carregado em BATER. Caso contrário, o BIOS simplesmente ignorará sua unidade flash. Tendo encontrado esses dois bytes mágicos, ele carrega o setor zero na RAM no endereço 0000:7С00h e depois esquece a unidade flash e transfere o controle para esse endereço. Agora todo o poder do computador pertence ao seu bootloader e ele, agindo a partir da RAM, pode carregar código adicional de uma unidade flash. Agora veremos como fica esse mesmo setor no programa DMDE.

1.Insira sua unidade flash no computador e certifique-se de que ela não contém as informações necessárias.

2.Abra o programa DMDE. Leia todas as etapas adicionais nas fotos:

Depois de assistir a esta história em quadrinhos, você terá a habilidade de carregar seu MBR em uma unidade flash. E é assim que fica o tão esperado resultado do nosso carregador:


A propósito, se falarmos sobre o código mínimo do bootloader, pode ser assim:

Organização 7C00h
jmp$
db 508 dup(0)
banco de dados 0x55.0xAA

Tal bootloader, tendo recebido o controle, simplesmente desliga o computador, executando um comando jmp $ sem sentido em um loop. Eu a chamo de pisar na água.

Postei um vídeo no YouTube que pode te ajudar:

Finalmente, alguns breves fatos sobre o bootloader:

1. O bootloader, também conhecido como bootloader, também conhecido como MBR, tem tamanho de 512 bytes. Historicamente,
que esta condição deve ser atendida para suportar mídias e dispositivos mais antigos.
2. O bootloader está sempre localizado no setor zero de uma unidade flash, disquete, disco rígido, do ponto de vista do programa DMDE ou outros editores hexadecimais que permitem trabalhar com dispositivos. Para carregar um binário (nosso boot.bin) em um dos dispositivos listados, não precisamos pensar em sua estrutura física interna. O programa DMDE simplesmente sabe ler os setores nesses dispositivos e exibi-los no modo LBA (simplesmente os numera de 0 até o último setor). Você pode ler sobre o LBA
3. O bootloader deve sempre terminar com dois bytes 0x55 0xAA.
4. O bootloader é sempre carregado na memória no endereço 0000:7С00h.
5. O sistema operacional começa com o bootloader.


Original: AsmSchool: Faça um sistema operacional
Autor: Mike Saunders
Data de publicação: 15 de abril de 2016
Tradução: A. Panin
Data de tradução: 16 de abril de 2016

Parte 4: Com as habilidades que você adquiriu ao ler os artigos anteriores desta série, você pode começar a desenvolver seu próprio sistema operacional!

Para que serve?

  • Para entender como funcionam os compiladores.
  • Para entender as instruções da CPU.
  • Para otimizar seu código para desempenho.

Ao longo de vários meses, percorremos um caminho difícil, que começou com o desenvolvimento programas simples em linguagem assembly para Linux e terminou no último artigo da série com o desenvolvimento de código independente que roda em um computador pessoal sem sistema operacional. Bem, agora vamos tentar coletar todas as informações e criar um sistema operacional real. Sim, seguiremos os passos de Linus Torvalds, mas primeiro precisamos de responder às seguintes questões: "O que é um sistema operativo? Quais das suas funções teremos de recriar?"

Neste artigo vamos nos concentrar apenas nas funções básicas do sistema operacional: carregar e executar programas. Sistemas operacionais complexos executam muito mais funções, como gerenciamento e processamento de memória virtual pacotes de rede, mas sua correta implementação requer anos de trabalho contínuo, portanto neste artigo consideraremos apenas as funções básicas presentes em qualquer sistema operacional. No mês passado desenvolvemos um pequeno programa que cabe no setor de 512 bytes de um disquete (seu primeiro setor), e agora vamos modificá-lo um pouco para adicionar a função de carregar dados adicionais do disco.

Desenvolvimento de carregador de boot

Poderíamos tentar reduzir ao máximo o tamanho do código binário do nosso sistema operacional para encaixá-lo no primeiro setor de 512 bytes do disquete, aquele que é carregado pelo BIOS, mas neste caso não seríamos capazes para implementar quaisquer funções interessantes. Portanto, usaremos esses 512 bytes para armazenar o código binário de um carregador de inicialização de sistema simples, que carregará o código binário do kernel do sistema operacional na RAM e o executará. (Depois disso, desenvolveremos o próprio kernel do SO, que carregará o código binário de outros programas do disco e também o executará, mas falaremos sobre isso um pouco mais tarde.)

Você pode baixar o código-fonte dos exemplos discutidos neste artigo em www.linuxvoice.com/code/lv015/asmschool.zip. E este é o código do gerenciador de inicialização do nosso sistema a partir de um arquivo chamado boot.asm:

BITS 16 jmp início curto; Ir para o rótulo, pulando a descrição do disco nop ; Adição antes da descrição do disco %include "bpb.asm" start: mov ax, 07C0h ; Carregar endereço mov ds, ax ; Segmento de dados mov ax, 9000h ; Preparação de pilha mov ss, ax mov sp, 0FFFFh ; A pilha diminui! cld; Configurando o sinalizador de direção mov si, kern_filename call load_file jmp 2000h:0000h ; Transição para o código binário do kernel do SO carregado do arquivo kern_filename db "MYKERNELBIN" %include "disk.asm" vezes 510-($-$$) db 0 ; Preenchimento do código binário com zeros até 510 bytes dw 0AA55h ; Buffer de marcador final binário do carregador de inicialização: ; Início do buffer para conteúdo do disco

Neste código, a primeira instrução da CPU é a instrução jmp, localizada após a diretiva BITS, que informa ao montador NASM que o modo de 16 bits está sendo usado. Como você provavelmente se lembra do artigo anterior da série, a execução do código binário de 512 bytes carregado do BIOS a partir do disco começa desde o início, mas temos que pular para um rótulo para pular um conjunto especial de dados. Obviamente, no mês passado simplesmente escrevemos o código no início do disco (usando o utilitário dd) e deixamos o restante do espaço em disco vazio.

Agora teremos que usar um disquete com sistema de arquivos MS-DOS adequado (FAT12), e para funcionar corretamente com este sistema de arquivos, precisamos adicionar um conjunto de dados especiais próximo ao início do setor. Esse conjunto é chamado de BIOS Parameter Block (BPB) e contém dados como rótulo do disco, número de setores e assim por diante. Não deveria nos interessar nesta fase, uma vez que mais de uma série de artigos poderia ser dedicada a tais tópicos, e é por isso que colocamos todas as instruções e dados associados a ele em um arquivo de código-fonte separado chamado bpb.asm.

Com base no exposto, esta diretiva do nosso código é extremamente importante:

%incluir "bpb.asm"

Esta é uma diretiva NASM que permite que o conteúdo de um arquivo fonte especificado seja incluído no arquivo fonte atual durante a montagem. Desta forma, podemos tornar o código do nosso bootloader o mais curto e compreensível possível, colocando todos os detalhes da implementação do bloco de parâmetros do BIOS em um arquivo separado. O bloco de parâmetros do BIOS deve estar localizado três bytes após o início do setor, e como a instrução jmp ocupa apenas dois bytes, temos que usar a instrução nop (seu nome significa "sem operação" - esta é uma instrução que faz nada além de desperdício de ciclos de CPU) para preencher o byte restante.

Trabalhando com a pilha

A seguir teremos que usar instruções semelhantes às discutidas no último artigo para preparar os registradores e a pilha, bem como a instrução cld (significa "clear direction"), que nos permite definir o sinalizador de direção para determinadas instruções, como como a instrução lodsb, que, quando executada, aumentará o valor no registrador SI em vez de decrementá-lo.

Depois disso, colocamos o endereço da string no registrador SI e chamamos nossa função load_file. Mas pense nisso por um minuto: ainda não desenvolvemos esse recurso! Sim, isso é verdade, mas sua implementação pode ser encontrada em outro arquivo de código-fonte que incluímos, chamado disk.asm.

O sistema de arquivos FAT12 usado em disquetes formatados em MS-DOS é um dos mais simples disponíveis sistemas de arquivos, mas trabalhar com seu conteúdo também requer uma quantidade considerável de código. A sub-rotina load_file tem cerca de 200 linhas e não será mostrada neste artigo, pois estamos considerando o processo de desenvolvimento de um sistema operacional, não um driver para um sistema de arquivos específico, portanto, não é muito sensato desperdiçar espaço no registrar páginas dessa maneira. Em geral, incluímos o arquivo de código-fonte disk.asm quase antes do final do arquivo de código-fonte atual e podemos esquecê-lo. (Se você ainda estiver interessado na estrutura do sistema de arquivos FAT12, você pode ler a excelente visão geral em http://tinyurl.com/fat12spec e, em seguida, consultar o arquivo de código-fonte disk.asm - o código contido nele é bem comentado.)

Em ambos os casos, a rotina load_file carrega o código binário do arquivo nomeado no registrador SI no segmento 2000 no deslocamento 0, após o qual saltamos para o início dele para execução. E isso é tudo - o kernel do sistema operacional está carregado e o bootloader do sistema completou sua tarefa!

Você deve ter notado que nosso código usa MYKERNELBIN em vez de MYKERNEL.BIN como nome de arquivo do kernel do sistema operacional, que se encaixa bem no esquema de nomenclatura 8+3 usado em disquetes no DOS. Na verdade, o sistema de arquivos FAT12 usa uma representação interna de nomes de arquivos, e economizamos espaço usando um nome de arquivo que não exige que nossa rotina load_file implemente um mecanismo para procurar o caractere de ponto e converter o nome do arquivo para o representação interna do sistema de arquivos.

Após a linha com a diretiva para conectar o arquivo de código-fonte disk.asm, há duas linhas projetadas para preencher o código binário do carregador de inicialização do sistema com zeros de até 512 bytes e incluir a marca final de seu código binário (isso foi discutido no artigo anterior). Finalmente, bem no final do código está o rótulo "buffer", que é usado pela rotina load_file. Basicamente, a rotina load_file precisa de espaço livre na RAM para realizar algum trabalho intermediário enquanto procura um arquivo no disco, e temos bastante espaço livre após carregar o carregador de boot, então colocamos o buffer aqui.

Para montar o carregador de boot do sistema, use o seguinte comando:

Nasm -f bin -o boot.bin boot.asm

Agora precisamos criar uma imagem de disquete virtual no formato MS-DOS e adicionar nosso código binário do bootloader aos primeiros 512 bytes usando os seguintes comandos:

Mkdosfs -C disquete.img 1440 dd conv=notrunc if=boot.bin of=floppy.img

Neste ponto, o processo de desenvolvimento de um gerenciador de inicialização do sistema pode ser considerado completo! Agora temos uma imagem de disquete inicializável que nos permite carregar o código binário do kernel do sistema operacional a partir de um arquivo chamado mykernel.bin e executá-lo. A seguir, uma parte mais interessante do trabalho nos espera - o desenvolvimento do próprio kernel do sistema operacional.

Kernel do sistema operacional

Queremos que o kernel do nosso sistema operacional execute muitas tarefas importantes: exibir uma mensagem de saudação, aceitar a entrada do usuário, determinar se a entrada é um comando compatível e executar programas do disco quando o usuário especificar seus nomes. Este é o código do kernel do sistema operacional do arquivo mykernel.asm:

Mov ax, 2000h mov ds, ax mov es, ax loop: mov si, prompt call lib_print_string mov si, user_input call lib_input_string cmp byte, 0 je loop cmp word, "ls" je list_files mov ax, si mov cx, 32768 call lib_load_file jc load_fail call 32768 jmp loop load_fail: mov si, load_fail_msg call lib_print_string jmp loop list_files: mov si, file_list call lib_get_file_list call lib_print_string jmp loop prompt db 13, 10, "MyOS > ", 0 load_fail_msg db 13, 10, "Not found! ", 0 user_input vezes 256 db 0 file_list vezes 1024 db 0% inclui "lib.asm"

Antes de olhar o código, você deve prestar atenção na última linha com a diretiva para incluir o arquivo de código-fonte lib.asm, que também está localizado no arquivo asmschool.zip do nosso site. Esta é uma biblioteca de rotinas úteis para trabalhar com tela, teclado, strings e discos que você também pode usar - neste caso incluímos este arquivo de código-fonte bem no final do arquivo de código-fonte principal do kernel do sistema operacional em ordem para tornar este último o mais compacto e bonito possível. Consulte a seção Rotinas da Biblioteca lib.asm para Informações adicionais sobre todas as sub-rotinas disponíveis.

Nas três primeiras linhas do código do kernel do sistema operacional, preenchemos os registradores de segmento com dados para apontar para o segmento 2000 no qual o código binário foi carregado. Isto é importante para garantir operação correta instruções como lodsb, que devem ler dados do segmento atual e não de qualquer outro. Depois disso, não realizaremos nenhuma operação adicional nos segmentos; nosso sistema operacional funcionará com 64 KB de RAM!

Mais adiante no código há um rótulo correspondente ao início do loop. Em primeiro lugar, usamos uma das rotinas da biblioteca lib.asm, nomeadamente lib_print_string, para imprimir a saudação. Os bytes 13 e 10 antes da linha de saudação são caracteres de escape. nova linha, graças ao qual a saudação não será exibida imediatamente após a saída de nenhum programa, mas sempre em uma nova linha.

Depois disso, usamos outra rotina da biblioteca lib.asm chamada lib_input_string, que pega a entrada do teclado do usuário e a armazena em um buffer apontado no registrador SI. No nosso caso, o buffer é declarado próximo ao final do código do kernel do sistema operacional da seguinte forma:

Entrada_do_usuário vezes 256 db 0

Esta declaração permite criar um buffer de 256 caracteres, preenchido com zeros - seu comprimento deve ser suficiente para armazenar comandos para um sistema operacional simples como o nosso!

Em seguida, realizamos a validação de entrada do usuário. Se o primeiro byte do buffer user_input for zero, o usuário simplesmente pressionou a tecla Enter sem inserir nenhum comando; Não esqueça que todas as strings terminam com caracteres nulos. Portanto, neste caso devemos apenas ir ao início do loop e imprimir a saudação novamente. Porém, caso o usuário insira algum comando, teremos que verificar primeiro se ele digitou o comando ls. Até agora, você só podia observar comparações de bytes individuais em nossos programas em linguagem assembly, mas não esqueça que também é possível comparar valores de bytes duplos ou palavras de máquina. Neste código, comparamos a primeira palavra de máquina do buffer user_input com a palavra de máquina correspondente à linha ls e, se forem idênticas, passamos para o bloco de código abaixo. Dentro deste bloco de código, usamos outra rotina de lib.asm para obter uma lista separada por vírgulas de arquivos no disco (que devem ser armazenados no buffer file_list), imprimir essa lista na tela e voltar para o loop para processar a entrada do usuário.

Execução de programas de terceiros

Se o usuário não inserir o comando ls, presumiremos que ele inseriu o nome do programa no disco, portanto, faz sentido tentar carregá-lo. Nossa biblioteca lib.asm contém uma implementação da útil rotina lib_load_file, que analisa as tabelas do sistema de arquivos do disco FAT12: leva um ponteiro para o início de uma linha com o nome do arquivo através do registro AX, bem como um valor de deslocamento para carregar código binário de um arquivo de programa por meio do registro CX. Já utilizamos o registrador SI para armazenar um ponteiro para a string que contém a entrada do usuário, então copiamos esse ponteiro para o registrador AX e depois colocamos o valor 32768, que é usado como offset para carregar o código binário do arquivo do programa, no registro CX.

Mas por que usamos esse valor específico como deslocamento para carregar o código binário de um arquivo de programa? Bem, esta é apenas uma das opções de mapa de memória para o nosso sistema operacional. Como estamos trabalhando em um único segmento de 64 KB e nosso binário do kernel é carregado no deslocamento 0, temos que usar os primeiros 32 KB de memória para dados do kernel e os 32 KB restantes para carregar dados do programa. Assim, o deslocamento 32768 está no meio do nosso segmento e nos permite fornecer RAM suficiente para o kernel do sistema operacional e para os programas carregados.

A rotina lib_load_file então executa uma operação muito importante: se não conseguir encontrar um arquivo com o nome fornecido no disco, ou por algum motivo não puder lê-lo do disco, ela simplesmente sai e define um sinalizador de transporte especial. Este é um sinalizador de estado da CPU que é definido durante a execução de algumas operações matemáticas e não deve nos interessar no momento, mas ao mesmo tempo podemos determinar a presença deste sinalizador para tomar decisões rápidas. Se a rotina lib_load_asm definir o sinalizador de carry, usamos a instrução jc (jump if carry) para pular para um bloco de código que imprime a mensagem de erro e retorna ao início do loop de entrada do usuário.

No mesmo caso, se o sinalizador de transferência não estiver definido, podemos concluir que a sub-rotina lib_load_asm carregou com sucesso o código binário do arquivo de programa na RAM no endereço 32768. Tudo o que precisamos neste caso é iniciar a execução do binário código carregado neste endereço, ou seja, comece a executar o programa especificado pelo usuário! E depois que a instrução ret for usada neste programa (para retornar ao código de chamada), precisaremos simplesmente retornar ao loop para processar a entrada do usuário. Assim criamos um sistema operacional: ele consiste nos mais simples mecanismos de análise de comandos e carregamento de programas, implementados em cerca de 40 linhas de código assembly, embora com muita ajuda de rotinas da biblioteca lib.asm.

Para montar o código do kernel do sistema operacional, use o seguinte comando:

Nasm -f bin -o meukernel.bin meukernel.asm

Depois disso teremos que adicionar de alguma forma o arquivo mykernel.bin ao arquivo de imagem do disquete. Se você estiver familiarizado com o truque de montar imagens de disco usando dispositivos de loopback, poderá acessar o conteúdo da imagem de disco usando floppy.img, mas há uma maneira mais fácil usando GNU Mtools (www.gnu.org/software/mtools). Este é um conjunto de programas para trabalhar com disquetes que utilizam sistemas de arquivos MS-DOS/FAT12, disponíveis em repositórios de pacotes Programas todos populares Distribuições Linux, então tudo que você precisa fazer é usar o apt-get, yum, pacman ou qualquer outro utilitário usado para instalar pacotes de software em sua distribuição.

Depois de instalar o pacote de software apropriado, você terá que executar o seguinte comando para adicionar o arquivo mykernel.bin ao arquivo de imagem de disco floppy.img:

Mcopy -i disquete.img meukernel.bin::/

Observe os símbolos engraçados no final do comando: dois pontos, dois pontos e barra. Agora estamos quase prontos para lançar nosso sistema operacional, mas de que adianta se não existem aplicativos para ele? Vamos corrigir esse mal-entendido desenvolvendo um aplicativo extremamente simples. Sim, agora você desenvolverá um aplicativo para seu próprio sistema operacional - imagine o quanto sua autoridade aumentará entre os geeks. Salve o seguinte código em um arquivo chamado test.asm:

Org 32768 mov ah, 0Eh mov al, "X" int 10h ret

Esse código simplesmente usa a função do BIOS para imprimir um “X” na tela e, em seguida, retorna o controle ao código que o chamou – no nosso caso, esse código é o código do sistema operacional. A linha organizacional que inicia o código-fonte do aplicativo não é uma instrução de CPU, mas uma diretiva assembler NASM informando que o código binário será carregado na RAM no deslocamento 32768 e, portanto, todos os deslocamentos devem ser recalculados para compensar isso.

Este código também precisa ser montado e o arquivo binário resultante precisa ser adicionado ao arquivo de imagem do disquete:

Nasm -f bin -o test.bin test.asm mcopy -i floppy.img test.bin::/

Agora respire fundo, prepare-se para contemplar os resultados incomparáveis ​​do seu próprio trabalho e inicialize a imagem do disquete usando um emulador de PC como Qemu ou VirtualBox. Por exemplo, o seguinte comando pode ser usado para esta finalidade:

Qemu-system-i386 -fda disquete.img

Voila: o bootloader do sistema boot.img, que integramos ao primeiro setor da imagem do disco, carrega o kernel do sistema operacional mykernel.bin, que exibe uma mensagem de boas-vindas. Digite o comando ls para obter os nomes de dois arquivos localizados no disco (mykernel.bin e test.bin) e, a seguir, insira o nome do último arquivo para executá-lo e exiba um X na tela.

É legal, não é? Agora você pode começar a finalizar shell de comando seu sistema operacional, adicionar implementações de novos comandos e também adicionar arquivos de programas adicionais ao disco. Se você deseja executar este sistema operacional em um PC real, consulte a seção “Executando o bootloader em uma plataforma de hardware real” do artigo anterior da série - você precisará exatamente dos mesmos comandos. No próximo mês, tornaremos nosso sistema operacional mais poderoso, permitindo que programas para download usem funções do sistema, introduzindo um conceito de compartilhamento de código para reduzir a duplicação de código. Grande parte do trabalho ainda está por vir.

rotinas da biblioteca lib.asm

Como mencionado anteriormente, a biblioteca lib.asm fornece um grande conjunto de rotinas úteis para uso em seus kernels sistemas operacionais e programas individuais. Alguns deles usam instruções e conceitos que ainda não foram abordados nos artigos desta série, outros (como rotinas de disco) estão intimamente relacionados ao design de sistemas de arquivos, mas se você se considera competente nesses assuntos, pode ler familiarize-se com suas implementações e entenda o princípio de operação. No entanto, é mais importante descobrir como chamá-los a partir do seu próprio código:

  • lib_print_string - aceita um ponteiro para uma string terminada em nulo através do registro SI e imprime a string na tela.
  • lib_input_string - aceita um ponteiro para um buffer através do registro SI e preenche esse buffer com caracteres inseridos pelo usuário usando o teclado. Depois que o usuário pressiona a tecla Enter, a linha no buffer termina em nulo e o controle retorna ao código do programa de chamada.
  • lib_move_cursor - move o cursor na tela para uma posição com coordenadas transmitidas através dos registradores DH (número da linha) e DL (número da coluna).
  • lib_get_cursor_pos - esta sub-rotina deve ser chamada para obter os números atuais das linhas e colunas usando os registradores DH e DL, respectivamente.
  • lib_string_uppercase - pega um ponteiro para o início de uma string terminada em nulo usando o registro AX e converte os caracteres da string em letras maiúsculas.
  • lib_string_length - pega um ponteiro para o início de uma string terminada em nulo por meio do registro AX e retorna seu comprimento por meio do registro AX.
  • lib_string_compare - aceita ponteiros para o início de duas strings terminadas em nulo usando os registradores SI e DI e compara essas strings. Define o sinalizador de transporte se as linhas forem idênticas (para usar uma instrução de salto dependendo do sinalizador de transporte jc) ou limpa esse sinalizador se as linhas forem diferentes (para usar a instrução jnc).
  • lib_get_file_list - Pega um ponteiro para o início de um buffer por meio do registro SI e coloca nesse buffer uma string terminada em nulo contendo uma lista separada por vírgulas de nomes de arquivos do disco.
  • lib_load_file - leva um ponteiro para o início de uma linha contendo o nome do arquivo usando o registro AX e carrega o conteúdo do arquivo no deslocamento passado pelo registro CX. Retorna o número de bytes copiados para a memória (ou seja, o tamanho do arquivo) usando o registrador BX ou define o sinalizador de transporte se um arquivo com o nome fornecido não for encontrado.

Direi imediatamente: não feche o artigo com pensamentos “Droga, outro Popov”. Ele só tem um Ubuntu polido, enquanto eu tenho tudo do zero, inclusive o kernel e os aplicativos. Então, continuação sob o corte.

Grupo de SO: Aqui.
Primeiro, darei a você uma captura de tela.

Não existem mais deles, e agora vamos falar com mais detalhes sobre por que estou escrevendo isso.

Era uma noite quente de abril, quinta-feira. Desde criança sonhava em escrever um SO, quando de repente pensei: “Agora que conheço as vantagens e as coisas, por que não realizar meu sonho?” Pesquisei sites sobre esse assunto no Google e encontrei um artigo de Habr: “Como começar e não parar de escrever um sistema operacional”. Agradecimentos ao seu autor pelo link para o Wiki OSDev abaixo. Fui lá e comecei a trabalhar. Havia todos os dados sobre o sistema operacional mínimo em um artigo. Comecei a construir cross-gcc e binutils e reescrevi tudo a partir daí. Você deveria ter visto minha alegria quando vi a inscrição “Hello, kernel World!” Pulei da cadeira e percebi que não iria desistir. Escrevi "console" (entre aspas; não tinha acesso a teclado), mas decidi escrever um sistema de janelas. No final funcionou, mas não tive acesso ao teclado. E então decidi criar um nome baseado no X Window System. Pesquisei no Y Window System - ele existe. Como resultado, chamei Z Window System 0.1, incluído no OS365 pré-alfa 0.1. E sim, ninguém a viu, exceto eu. Então descobri como implementar o suporte ao teclado. Captura de tela da primeira versão, quando ainda não havia nada, nem mesmo um sistema de janelas:

O cursor de texto nem se moveu, como você pode ver. Então eu escrevi alguns aplicações simples baseado em Z. E aqui está a versão 1.0.0 alfa. Havia muitas coisas lá, até menus do sistema. A gerenciador de arquivos e a calculadora simplesmente não funcionou.

Fui diretamente aterrorizado por um amigo que só se preocupa com a beleza (Mitrofan, desculpe). Ele disse: “Lave o modo VBE 1024*768*32, lave, lave! Bem, vamos beber! Bem, eu já estava cansado de ouvi-lo e ainda assim o cortei. Sobre a implementação abaixo.

Fiz tudo com meu bootloader, ou seja, GRUB, com sua ajuda você pode configurar o modo gráfico sem complicações adicionando algumas linhas mágicas ao cabeçalho Multiboot.

Definir ALINHAR, 1<<0 .set MEMINFO, 1<<1 .set GRAPH, 1<<2 .set FLAGS, ALIGN | MEMINFO | GRAPH .set MAGIC, 0x1BADB002 .set CHECKSUM, -(MAGIC + FLAGS) .align 4 .long MAGIC .long FLAGS .long CHECKSUM .long 0, 0, 0, 0, 0 .long 0 # 0 = set graphics mode .long 1024, 768, 32 # Width, height, depth
E então, da estrutura de informações do Multiboot, pego o endereço do framebuffer e a resolução da tela e escrevo pixels lá. VESA fez tudo de forma muito confusa - as cores RGB devem ser inseridas na ordem inversa (não R G B, mas B G R). Por vários dias não entendi porque os pixels não eram exibidos!? No final, percebi que esqueci de alterar os valores das 16 constantes de cores de 0...15 para seus equivalentes RGB. Como resultado, eu o soltei e ao mesmo tempo cortei o fundo gradiente. Depois fiz um console, 2 aplicativos e lancei o 1.2. Ah, sim, quase esqueci - você pode baixar o sistema operacional em

Montador

Montador(do inglês assemble - assemble) - um compilador de linguagem assembly para comandos de linguagem de máquina.
Existe um montador para cada arquitetura de processador e para cada sistema operacional ou família de sistemas operacionais. Existem também os chamados “cross-assemblers” que permitem montar programas para outra arquitetura de destino ou outro sistema operacional em máquinas com uma arquitetura (ou no ambiente de um sistema operacional) e obter código executável em um formato adequado para execução em na arquitetura de destino ou no sistema operacional do ambiente de destino.

arquitetura x86

Montadores para DOS

Os montadores mais famosos do sistema operacional DOS foram Borland Turbo Assembler (TASM) e Microsoft Macro Assembler (MASM). O simples montador A86 também foi popular ao mesmo tempo.
Inicialmente, eles suportavam apenas instruções de 16 bits (até o advento do processador Intel 80386). Versões posteriores do TASM e MASM suportam instruções de 32 bits, bem como todas as instruções introduzidas em processadores mais modernos, e sistemas de instrução específicos de arquitetura (como, por exemplo, MMX, SSE, 3DNow!, etc.).

Microsoft Windows

Com o advento do sistema operacional Microsoft Windows, surgiu uma extensão TASM chamada TASM32, que possibilitou a criação de programas para rodar no ambiente Windows. A versão mais recente conhecida do Tasm é a 5.3, que suporta instruções MMX e está atualmente incluída no Turbo C++ Explorer. Mas oficialmente o desenvolvimento do programa foi completamente interrompido.
A Microsoft mantém um produto chamado Microsoft Macro Assembler. Ele continua a se desenvolver até hoje, com as versões mais recentes incluídas nos DDKs. Mas a versão do programa voltada para a criação de programas para DOS não está sendo desenvolvida. Além disso, Stephen Hutchesson criou um pacote de programação MASM chamado "MASM32".

GNU e GNU/Linux

O sistema operacional GNU inclui o compilador gcc, que inclui o montador gas (GNU Assembler), que usa a sintaxe AT&T, ao contrário da maioria dos outros montadores populares, que usam a sintaxe Intel.

Montadores portáteis

Existe também um projeto assembler de código aberto, cujas versões estão disponíveis para diversos sistemas operacionais, e que permite obter arquivos objetos para esses sistemas. Este montador é denominado NASM (Netwide Assembler).
YASM é uma versão reescrita do NASM sob a licença BSD (com algumas exceções).
FASM (Flat Assembler) é um jovem montador sob uma licença BSD modificada para proibir o relicenciamento (inclusive sob a GNU GPL). Existem versões para KolibriOS, GNU/Linux, MS-DOS e Microsoft Windows, usa sintaxe Intel e suporta instruções AMD64.

Arquiteturas RISC


MCS-51
AVR
No momento existem 2 compiladores produzidos pela Atmel (AVRStudio 3 e AVRStudio4). A segunda versão é uma tentativa de corrigir a primeira, que não teve muito sucesso. O montador também está incluído no WinAVR.
BRAÇO
AVR32
MSP430
Power PC

Montagem e compilação

O processo de tradução de um programa em linguagem assembly em código objeto é geralmente chamado de assembly. Ao contrário da compilação, a montagem é um processo mais ou menos inequívoco e reversível. Na linguagem assembly, cada mnemônico corresponde a uma instrução de máquina, enquanto nas linguagens de programação de alto nível cada expressão pode ocultar um grande número de instruções diferentes. Em princípio, esta divisão é bastante arbitrária, por isso às vezes a tradução de programas assembly também é chamada de compilação.

Linguagem assembly

Linguagem assembly- um tipo de linguagem de programação de baixo nível, que é um formato de gravação de comandos de máquina conveniente para a percepção humana. Muitas vezes, por uma questão de brevidade, é simplesmente chamado de assembler, o que não é verdade.

Os comandos em linguagem assembly correspondem um a um aos comandos do processador e, de fato, representam uma forma simbólica conveniente de gravação (código mnemônico) de comandos e seus argumentos. A linguagem assembly também fornece abstrações básicas de software: vinculando partes e dados do programa por meio de rótulos com nomes simbólicos (durante a montagem, um endereço é calculado para cada rótulo, após o qual cada ocorrência do rótulo é substituída por este endereço) e diretivas.
As diretivas assembly permitem incluir blocos de dados (descritos explicitamente ou lidos de um arquivo) em um programa; repetir um determinado fragmento um determinado número de vezes; compilar o fragmento de acordo com a condição; definir o endereço de execução do fragmento diferente do endereço de localização da memória [especificar!]; alterar os valores dos rótulos durante a compilação; use definições de macro com parâmetros, etc.
Cada modelo de processador, em princípio, possui seu próprio conjunto de instruções e uma linguagem assembly (ou dialeto) correspondente.

Vantagens e desvantagens

Vantagens da linguagem assembly

Código redundante mínimo, ou seja, o uso de menos instruções e acessos à memória, permite maior velocidade e redução do tamanho do programa.
Garantir total compatibilidade e aproveitamento máximo das capacidades da plataforma desejada: utilização de instruções especiais e características técnicas desta plataforma.
Ao programar em linguagem assembly, recursos especiais tornam-se disponíveis: acesso direto ao hardware, portas de entrada/saída e registros especiais do processador, bem como a capacidade de escrever código automodificável (ou seja, metaprogramação, sem a necessidade de um intérprete de software). .
As mais recentes tecnologias de segurança introduzidas nos sistemas operativos não permitem a criação de código automodificável, pois excluem a possibilidade simultânea de execução de instruções e escrita na mesma área de memória (tecnologia W^X em sistemas BSD, DEP em Windows).

Desvantagens da linguagem assembly

Grandes quantidades de código e um grande número de pequenas tarefas adicionais, o que faz com que o código se torne muito difícil de ler e compreender e, portanto, a depuração e modificação do programa se torne mais difícil, bem como a dificuldade de implementação de paradigmas de programação e quaisquer outras convenções. o que leva à complexidade do desenvolvimento conjunto.
Menor número de bibliotecas disponíveis, baixa compatibilidade entre si.
Não é portável para outras plataformas (exceto aquelas compatíveis com binários).

Aplicativo

Segue diretamente das vantagens e desvantagens.
Como grandes programas em linguagem assembly são extremamente inconvenientes de escrever, eles são escritos em linguagens de alto nível. Em assembler, são escritos pequenos fragmentos ou módulos, para os quais os seguintes são críticos:
desempenho (motoristas);
tamanho do código (setores de boot, software para microcontroladores e processadores com recursos limitados, vírus, proteção de software);
capacidades especiais: trabalhar diretamente com hardware ou código de máquina, ou seja, carregadores de sistema operacional, drivers, vírus, sistemas de segurança.

Vinculando código assembly a outras linguagens

Como apenas fragmentos de um programa são geralmente escritos em linguagem assembly, eles precisam ser vinculados a outras partes em outras linguagens. Isto é conseguido de 2 maneiras principais:
Em fase de compilação— inserção de fragmentos assembler (assembler inline) em um programa usando diretivas de linguagem especiais, incluindo procedimentos de escrita em linguagem assembly. O método é conveniente para transformações simples de dados, mas código assembly completo com dados e sub-rotinas, incluindo sub-rotinas com muitas entradas e saídas que não são suportadas por linguagens de alto nível, não pode ser criado usando-o.
Na fase de layout ou compilação separada. Para que os módulos montados interajam, é suficiente que as funções de conexão suportem as convenções de chamada e os tipos de dados necessários. Módulos individuais podem ser escritos em qualquer linguagem, incluindo linguagem assembly.

Sintaxe

Não existe um padrão geralmente aceito para a sintaxe das linguagens assembly. No entanto, existem padrões que a maioria dos desenvolvedores de linguagem assembly adere. Os principais padrões são a sintaxe Intel e a sintaxe AT&T.

Instruções

O formato geral para gravação de instruções é o mesmo para ambos os padrões:

[rótulo:] código de operação [operandos] [;comentário]

onde opcode é o mnemônico direto das instruções para o processador. Podem ser adicionados prefixos (repetições, mudanças no tipo de endereçamento, etc.).
Os operandos podem ser constantes, nomes de registros, endereços em RAM, etc. As diferenças entre os padrões Intel e AT&T estão relacionadas principalmente à ordem em que os operandos são listados e à sua sintaxe para diferentes métodos de endereçamento.
Os mnemônicos utilizados geralmente são os mesmos para todos os processadores da mesma arquitetura ou família de arquiteturas (entre os mais conhecidos estão os mnemônicos para processadores e controladores Motorola, ARM, x86). Eles estão descritos nas especificações do processador. Possíveis exceções:
Se o montador usar sintaxe AT&T multiplataforma (mnemônicos originais são convertidos para sintaxe AT&T)
Se inicialmente existissem dois padrões para gravação de mnemônicos (o sistema de comando foi herdado de um processador de outro fabricante).
Por exemplo, o processador Zilog Z80 herdou o sistema de instruções Intel i8080, expandiu-o e alterou os mnemônicos (e designações de registro) à sua maneira. Por exemplo, mudei o mov da Intel para ld. Os processadores Motorola Fireball herdaram o sistema de instruções Z80, reduzindo-o um pouco. Ao mesmo tempo, a Motorola retornou oficialmente aos mnemônicos da Intel. E no momento, metade dos montadores do Fireball trabalham com mnemônicos Intel e metade com mnemônicos Zilog.

Diretivas

Além das instruções, um programa pode conter diretivas: comandos que não são traduzidos diretamente em instruções de máquina, mas controlam a operação do compilador. Seu conjunto e sintaxe variam significativamente e não dependem da plataforma de hardware, mas do compilador utilizado (gerando dialetos de linguagens dentro de uma mesma família de arquiteturas). Como um “conjunto de diretrizes para cavalheiros”, podemos destacar:
definição de dados (constantes e variáveis)
gerenciando a organização do programa na memória e parâmetros do arquivo de saída
definindo o modo de operação do compilador
todos os tipos de abstrações (ou seja, elementos de linguagens de alto nível) - desde o design de procedimentos e funções (para simplificar a implementação do paradigma de programação processual) até construções condicionais e loops (para o paradigma de programação estruturada)
macros

Programa de exemplo

Um exemplo de programa Hello World para MS-DOS para arquitetura x86 no dialeto TASM:

.MODEL TINY CODE SEGMENT ASSUME CS:CODE, DS:CODE ORG 100h START: mov ah,9 mov dx,OFFSET Msg int 21h int 20h Msg DB "Hello World",13,10,"$" CODE FINS END START

Origens e críticas do termo "linguagem assembly"

Esse tipo de linguagem recebe o nome do nome do tradutor (compilador) dessas linguagens - assembler (assembler inglês). O nome deste último se deve ao fato de que nos primeiros computadores não existiam linguagens de nível superior, e a única alternativa à criação de programas em assembler era programar diretamente em códigos.
A linguagem assembly em russo é frequentemente chamada de "assembler" (e algo relacionado a ela - "assembler"), o que, de acordo com a tradução inglesa da palavra, está incorreto, mas se enquadra nas regras da língua russa. No entanto, o próprio assembler (o programa) também é chamado simplesmente de “assembler” e não de “compilador de linguagem assembly”, etc.
O uso do termo "linguagem assembly" também pode levar ao equívoco de que existe uma única linguagem de baixo nível, ou pelo menos um padrão para tais linguagens. Ao nomear a linguagem em que um programa específico é escrito, é aconselhável esclarecer a que arquitetura ele se destina e em que dialeto da linguagem está escrito.

Hoje em nosso armário de curiosidades há um exemplo curioso - um sistema operacional escrito em assembler puro. Juntamente com drivers, shell gráfico, dezenas de programas e jogos pré-instalados, ocupa menos de um megabyte e meio. Conheça o sistema operacional “Hummingbird” excepcionalmente rápido e predominantemente russo.

O desenvolvimento do "Hummingbird" prosseguiu rapidamente até 2009. O pássaro aprendeu a voar em hardware diferente, exigindo minimamente o primeiro Pentium e oito megabytes de RAM. Os requisitos mínimos de sistema para o Hummingbird são:

  • CPU: Pentium, AMD 5x86 ou Cyrix 5x86 sem MMX com frequência de 100 MHz;
  • RAM: 8 MB;
  • placa de vídeo: compatível com VESA com suporte para modo VGA (640 × 480 × 16).

O “Hummingbird” moderno é uma “compilação noturna” atualizada regularmente da versão oficial mais recente, lançada no final de 2009. Testamos a versão 0.7.7.0+ datada de 20 de agosto de 2017.

AVISO

Nas configurações padrão, o KolibriOS não tem acesso aos discos visíveis através do BIOS. Pense bem e faça um backup antes de alterar esta configuração.

As mudanças nas compilações noturnas, embora pequenas, acumularam-se bastante ao longo dos anos. O "Hummingbird" atualizado pode gravar em partições FAT16–32 / ext2 - ext4 e oferece suporte a outros sistemas de arquivos populares (NTFS, XFS, ISO-9660) no modo de leitura. Ele adicionou suporte para placas USB e de rede e adicionou uma pilha TCP/IP e codecs de áudio. Em geral, você já pode fazer algo nele, e não apenas olhar uma vez para um sistema operacional ultraleve com GUI e ficar impressionado com a velocidade de inicialização.



Como nas versões anteriores, o “Hummingbird” mais recente é escrito em flat assembler (FASM) e ocupa um disquete - 1,44 MB. Graças a isso, pode ser colocado inteiramente em alguma memória especializada. Por exemplo, os artesãos escreveram o KolibriOS diretamente no Flash BIOS. Durante a operação, ele pode ficar inteiramente localizado no cache de alguns processadores. Imagine só: todo o sistema operacional, junto com programas e drivers, está armazenado em cache!

INFORMAÇÕES

Ao visitar o site kolibrios.org, o navegador pode alertá-lo sobre o perigo. O motivo, aparentemente, são os programas assembler da distribuição. O VirusTotal agora define o site como totalmente seguro.

O "Hummingbird" pode ser facilmente carregado a partir de um disquete, disco rígido, unidade flash, Live CD ou em uma máquina virtual. Para emular, basta especificar o tipo de SO “outro”, alocar um núcleo de processador e um pouco de RAM para ele. Não é necessário conectar o drive, e se você tiver um roteador com DHCP, o “Hummingbird” se conectará instantaneamente à Internet e à rede local. Imediatamente após o download, você verá uma notificação correspondente.


Um problema é que o protocolo HTTPS não é suportado pelo navegador integrado ao Kolibri. Portanto, não foi possível visualizar o site nele, assim como abrir as páginas do Google, Yandex, Wikipedia, Sberbank... na verdade, nenhum endereço usual. Todo mundo mudou há muito tempo para um protocolo seguro. O único site com HTTP puro da velha escola que encontrei foi o “Portal do Governo Russo”, mas também não parecia o melhor em um navegador de texto.



As configurações de aparência no Hummingbird melhoraram ao longo dos anos, mas ainda estão longe do ideal. Uma lista de modos de vídeo suportados é exibida na tela de carregamento do Hummingbird quando você pressiona a tecla a.



A lista de opções disponíveis é pequena e a resolução necessária pode não estar disponível. Se você tiver uma placa de vídeo com GPU AMD (ATI), poderá adicionar configurações personalizadas imediatamente. Para fazer isso, você precisa passar o parâmetro -m para o carregador ATIKMS x x , Por exemplo:

/RD/1/DRIVERS/ATIKMS -m1280x800x60 -1

Aqui /RD/1/DRIVERS/ATIKMS é o caminho para o bootloader (RD - Disco RAM).

Quando o sistema está em execução, o modo de vídeo selecionado pode ser visualizado com o comando vmode e (teoricamente) alternado manualmente. Se o “Hummingbird” estiver sendo executado em uma máquina virtual, esta janela permanecerá vazia, mas com uma inicialização limpa, os drivers de vídeo Intel podem ser adicionados do i915 ao Skylake inclusive.

Surpreendentemente, o KolibriOS pode acomodar muitos jogos. Entre eles estão jogos de lógica e arcade, tag, cobra, tanques (não, não WoT) - um “Game Center” completo! Até Doom e Quake foram portados para Kolibri.



Outra coisa importante foi o leitor FB2READ. Funciona corretamente com cirílico e possui configurações de exibição de texto.



Recomendo armazenar todos os arquivos do usuário em uma unidade flash, mas ela deve estar conectada através de uma porta USB 2.0. Nossa unidade flash USB 3.0 (em uma porta USB 2.0) com capacidade de 16 GB com sistema de arquivos NTFS foi identificada imediatamente. Se precisar gravar arquivos, você deve conectar uma unidade flash com partição FAT32.



A distribuição Kolibri inclui três gerenciadores de arquivos, utilitários para visualização de imagens e documentos, reprodutores de áudio e vídeo e outros aplicativos de usuário. No entanto, seu foco principal está no desenvolvimento de linguagem assembly.



O editor de texto integrado possui destaque de sintaxe ASM e ainda permite iniciar imediatamente programas digitados.



Entre as ferramentas de desenvolvimento está o compilador Oberon-07/11 para i386 Windows, Linux e KolibriOS, além de emuladores de baixo nível: E80 - emulador ZX Spectrum, FCE Ultra - um dos melhores emuladores NES, DOSBox v.0.74 e outros. Todos eles foram portados especialmente para Kolibri.

Se você sair do KolibriOS por alguns minutos, o protetor de tela será iniciado. Linhas de código aparecerão na tela, nas quais você poderá ver uma referência ao MenuetOS.

A continuação está disponível apenas para membros

Opção 1. Junte-se à comunidade do “site” para ler todos os materiais do site

A adesão à comunidade dentro do período especificado lhe dará acesso a TODOS os materiais do Hacker, aumentará seu desconto cumulativo pessoal e permitirá que você acumule uma classificação profissional do Xakep Score!


Principal