Ir para conteúdo
Faça parte da equipe! (2024) ×

Criando um Sistema Operacional do Zero


Arkanun1000
 Compartilhar

Posts Recomendados

  • Velha Guarda

Capítulo 1: Introdução à arquitetura x86 e sobre o nosso OS

 

 

 

Qual é a arquitetura x86?

 

O termo denota uma família x86 de arquiteturas de conjunto de instruções compatíveis com versões anteriores com base na CPU Intel 8086.

A arquitetura x86 é a arquitetura do conjunto de instruções mais comum desde a sua introdução, em 1981, para o IBM PC. Uma grande quantidade de software, incluindo sistemas operacionais (SO) como o DOS, Windows, Linux, BSD, Solaris e Mac OS X, com função de hardware baseado em x86.

 

Nós não estamos indo para a concepção de um sistema operacional para a arquitetura x86-64 mas para x86-32, graças à compatibilidade com versões anteriores, o nosso sistema operacional será compatível com nossos PCs mais recentes (mas tome cuidado se você quiser testá-lo em seu máquina real).

 

Nosso Sistema Operacional

 

O objetivo é construir um sistema operacional baseado em UNIX muito simples em C ++, mas o objetivo não é apenas construir uma “prova de conceito”. O sistema operacional deve ser capaz de inicializar, iniciar um shell userland e ser extensível.

 

O sistema operacional será construído para a arquitetura x86, rodando em 32 bits, e é compatível com PCs da IBM.

 

especificações:

 

Código em C ++

x86, arquitetura de 32 bits

Boot com Grub

Tipo de sistema modular para motoristas

Tipo de estilo UNIX

multitarefa

Executável ELF em espaço de usuário

Módulos (acessíveis no espaço de usuário usando / dev / …):

discos IDE

partições DOS

relógio

EXT2 (somente leitura)

Boch VBE

userland:

POSIX API

LibC

“Pode” executar um shell ou alguns executáveis como Lua, …

 

 

 

 

Capítulo 2: Configuração do ambiente de desenvolvimento

 

 

 

O primeiro passo é a configuração de um ambiente bom e viável de desenvolvimento. Usando Vagrant e Virtualbox, você vai ser capaz de compilar e testar o seu sistema operacional a partir de todas as OSs (Linux, Windows ou Mac).

 

Instale o Vagrant

 

Vagrant é um software livre e de código aberto para a criação e configuração de ambientes de desenvolvimento virtuais. Pode ser considerado um invólucro em torno do VirtualBox.

Vagrant vai nos ajudar a criar um ambiente de desenvolvimento virtual limpo em qualquer sistema que você está usando. O primeiro passo é fazer o download e instalar Vagrant para o seu sistema em http://www.vagrantup.com/.

 

Instale Virtualbox

 

Oracle VM VirtualBox é um pacote de software de virtualização para x86 e computadores baseados em Intel64 AMD64 /.

Vagrant precisa Virtualbox para trabalhar, baixe e instale em seu sistema em https://www.virtualbox.org/wiki/Downloads.

 

Iniciar e testar o ambiente de desenvolvimento

 

Uma vez que o Vagrant e o Virtualbox está instalado, você precisa baixar a imagem lucid32 ubuntu para Vagrant:

 

caixa vagrant adicionar lucid32 http://files.vagrantup.com/lucid32.box

Assim que a imagem lucid32 está pronto, precisamos definir nosso ambiente de desenvolvimento usando uma Vagrantfile, crie um arquivo chamado Vagrantfile. Este arquivo define o que os pré-requisitos nossas necessidades de ambiente: nasm, make, build-essential, grub e qemu.

 

Comece sua caixa usando:

 

Vagrant up

Agora você pode acessar sua caixa usando ssh para conectar-se a caixa virtual utilizando:

 

ssh vagrant

O código estará disponível no diretório / vagrant:

 

cd /vagrant

Construir e testar nosso sistema operacional

 

O arquivo Makefile define algumas regras básicas para a construção do kernel, o usuário libc e alguns programas de nível de usuário.

 

Constituição:

 

make all

Teste nosso sistema operacional com qemu:

 

make run

A documentação para qemu está disponível a documentação QEMU Emulator.

 

Você pode sair do emulador usando: <Ctrl-a x>.

 

 

 

 

Capítulo 3: O primeiro boot com o GRUB

 

 

 

Como se dá a inicialização?

 

Quando um computador baseado em x86 é ligado, ele começa um caminho complexo para chegar ao estágio em que o controle é transferido à rotina “main” do nosso kernel (kmain ()). Para este curso, vamos apenas considerar o método de inicialização do BIOS e não é sucessor (UEFI).

 

A seqüência de inicialização do BIOS é: RAM detecção -> Hardware detecção / Inicialização -> seqüência de inicialização.

 

O passo mais importante para nós é a “seqüência de inicialização”, onde a BIOS é feito com a sua inicialização e tenta transferir o controle para a próxima etapa do processo de bootloader.

 

Durante a “seqüência de inicialização”, o BIOS tentará determinar um “dispositivo de boot” (por exemplo, disquete, disco rígido, CD, dispositivo de memória flash USB ou de rede). Nosso sistema operacional será inicialmente de inicialização do disco rígido (mas será possível iniciá-la a partir de um CD ou um dispositivo de memória flash USB no futuro). Um dispositivo é considerado inicializável, caso o setor de inicialização contém a assinatura válida bytes 0x55 e 0xAA em deslocamentos 511 e 512, respectivamente (chamados bytes mágicos de Master Boot Record (MBR), Esta assinatura é representada (em binário) como 0b1010101001010101. O padrão de bit alternado foi pensado para ser uma proteção contra certas falhas (conduzir ou controlador). Se esse padrão é distorcido ou 0x00, o dispositivo não é considerado de arranque)

 

Buscas BIOS fisicamente por um dispositivo de arranque, o carregamento dos primeiros 512 bytes a partir do sector de arranque de cada dispositivo para a memória física, a começar no endereço 0x7C00 (1 KB abaixo da marca KB 32). Quando os bytes de assinatura válidos são detectados, as transferências de BIOS para controlar o endereço de memória 0x7C00 (através de uma instrução de salto), a fim de executar o código do setor de inicialização.

 

Durante todo este processo, a CPU foi executado em 16-bit modo real (o estado padrão para processadores x86, a fim de manter a compatibilidade com versões anteriores). Para executar as instruções de 32 bits dentro do nosso kernel, um bootloader é necessário para mudar a CPU em modo protegido.

 

O que é GRUB?

 

GNU GRUB (abreviação de GNU GRand Unified Bootloader) é um pacote de boot loader do Projeto GNU. GRUB é a implementação de referência da especificação Multiboot da Free Software Foundation, que oferece ao usuário a opção de iniciar um dos vários sistemas operacionais instalados em um computador ou selecionar uma configuração do kernel específico disponível em partições de um sistema operacional em particular.

Para tornar mais simples, o GRUB é a primeira coisa arrancado pela máquina (um boot-loader) e irá simplificar o carregamento de nosso kernel armazenados no disco rígido.

 

Por que estamos usando GRUB?

 

– GRUB é muito simples de usar

– Torná-lo muito simples para carregar kernels 32bits sem necessidades de código de 16bits

– Multiboot com Linux, Windows e outros

– Tornar mais fácil de carregar módulos externos de memória

Como usar o GRUB?

 

GRUB usa a especificação Multiboot, o binário executável deve ser 32bits e deve conter um cabeçalho especial (cabeçalho multiboot) em seus primeiros 8192 bytes. Nosso núcleo será um arquivo executável ELF (“Executable and Linkable Format”, um formato comum de arquivo padrão para arquivos executáveis no sistema UNIX mais).

 

A primeira seqüência de inicialização do nosso kernel está escrito em Assembly: start.asm e usamos um arquivo de ligação para definir nossa estrutura executável: linker.ld.

 

Este processo de inicialização inicializa também algum do nosso tempo de execução C ++, que será descrita no capítulo seguinte.

 

Estrutura de cabeçalho Multiboot:

 

É necessário se cadastrar para acessar o conteúdo.

 

Você pode usar os comandos mbchk kernel.elf para validar seu arquivo kernel.elf contra o padrão de inicialização múltipla. Você também pode usar o comando nm -n kernel.elf to validate the offset of the different objects in the ELF binary.

 

Criar uma imagem de disco para o nosso kernel e grub

 

O diskimage.sh script vai gerar uma imagem de disco rígido, que pode ser usado pelo QEMU.

 

O primeiro passo é criar uma imagem do disco rígido (c.img) usando qemu-img:

 

É necessário se cadastrar para acessar o conteúdo.

 

Precisamos agora particionar o disco usando fdisk:

É necessário se cadastrar para acessar o conteúdo.

 

É necessário se cadastrar para acessar o conteúdo.

 

 

 

Precisamos agora anexar a partição criada para o dispositivo de loop (que permite

que um arquivo seja de acesso como um dispositivo de bloco), utilizando losetup.

O deslocamento da partição é passado como um argumento e calculada usando:

offset= start_sector * bytes_by_sector.

Using fdisk -l -u c.img, you get: 63 * 512 = 32256.

 

É necessário se cadastrar para acessar o conteúdo.

 

 

We create a EXT2 filesystem on this new device using:

 

É necessário se cadastrar para acessar o conteúdo.

 

We copy our files on a mounted disk:

 

É necessário se cadastrar para acessar o conteúdo.

 

 

Install GRUB on the disk:

 

É necessário se cadastrar para acessar o conteúdo.

 

E, finalmente, retirar o dispositivo de loop:

 

É necessário se cadastrar para acessar o conteúdo.

 

See Also

 

 

 

 

Capítulo 4: Backbone da OS e C ++ runtime

 

 

 

C ++ do kernel em tempo de execução

Um kernel pode ser programado em C ++, é muito semelhante a fazer um kernel em C, exceto que existem algumas armadilhas que você deve levar em conta (o suporte de tempo de execução, construtores, …)

 

O compilador irá assumir que todo o C ++ runtime suporte necessário está disponível por padrão, mas como não estão ligando em libsupc ++ em seu kernel do C ++, nós precisamos adicionar algumas funções básicas que podem ser encontrados no arquivo cxx.cc.

 

Atenção: Os operadores new e delete não podem ser usados antes da memória virtual e paginação foram inicializados.

 

 

 

Funções básicas C/C++

 

O código do kernel não pode utilizar as funções das bibliotecas padrão, então precisamos adicionar algumas funções básicas para o gerenciamento de memória e strings:

 

 

É necessário se cadastrar para acessar o conteúdo.

 

 

These functions are defined in string.cc, memory.cc, itoa.cc

 

C types

During the next step, we are going to use different types in our code, most of the types we are going to use unsigned types (all the bits are used to stored the integer, in signed types one bit is used to signal the sign):

 

 

 

Compilar nosso kernel

Compilando um kernel não é a mesma coisa que a compilação de um executável linux, não podemos usar a biblioteca padrão e não devem ter dependências para o sistema.

 

Nossa Makefile irá definir o processo para compilar e linkar nosso kernel.

 

Para arquitetura x86, os argumentos de partidários serão utilizados para gcc / g ++ / ld:

 

 

É necessário se cadastrar para acessar o conteúdo.

 

 

 

 

 

Capítulo 5: As classes base para o gerenciamento de arquitetura x86

 

 

 

Agora que sabemos como compilar o nosso kernel do C ++ e inicie o binário usando GRUB, podemos começar a fazer algumas coisas legais em C / C ++.

 

Imprimindo no console tela

 

Nós estamos indo para usar o modo padrão VGA (03h) para exibir algum texto para o usuário. A tela pode ser acessado diretamente através da memória de vídeo em 0xb8000. A resolução da tela é de 80×25 e cada personagem na tela é definido por dois bytes: um para o código de caracteres, e um para a bandeira de estilo. Isto significa que o tamanho total da memória de vídeo é 4000B (80B 25B * * 2B).

 

Na classe IO (io.cc),:

 

x, y: definir a posição do cursor no ecrã

real_screen: definir o ponteiro de memória de vídeo

putc (char c): imprimir um caráter único na tela e controlar a posição do cursor

printf (char * s, …): imprimir uma string

Nós adicionamos um método putc à classe IO de colocar um personagem na tela e atualizar a (x, y) posição.

 

 

É necessário se cadastrar para acessar o conteúdo.

 

 

Nós também devemos adicionar um método útil e muito conhecido: printf.

 

 

 

 

Capítulo 6: GDT

 

 

 

Graças ao GRUB, o kernel não está mais em modo real, mas já em modo protegido,

este modo permite-nos usar todas as possibilidades do microprocessador,

como gerenciamento de memória virtual, paginação e seguro multi-tasking.

 

Qual é o GDT?

 

O GDT ("Descriptor Table global") é uma estrutura de dados utilizada para

definir as diferentes áreas de memória: o endereço de base, os privilégios

de tamanho e de acesso, como executar e escrever. Estas áreas de memória

são chamados "segmentos".

 

Nós vamos usar o GDT para definir diferentes segmentos de memória:

 

"code": Código do kernel, usado para armazenamento do código binário executável;

"data": Dados do kernel;

"stack": Pilha do kernel, usado para armazenado na pilha de chamadas durante a

execução do kernel;

"ucode": Código do usuário, utilizado para armazenamento do código binário executá

vel para o programa do usuário;

"udata": Dados do programa do usuário;

"ustack": Pilha do usuário, usado para armazenado na pilha de chamadas durante a

execução no espaço de usuário;

 

Como carregar a nossa GDT?

 

GRUB inicializa uma GDT, porém GDT não corresponde ao nosso kernel.

O GDT é carregado usando as instruções de montagem LGDT.

Ele espera que a localização de uma descrição da estrutura do GDT:

 

 

gdtr.png

 

 

E a estrutura C:

 

 

É necessário se cadastrar para acessar o conteúdo.

 

Cuidado: a directiva __attribute__ ((packed)) sinal para gcc que a estrutura deve

usar o mínimo de memória possível. Sem esta diretiva, gcc inclui alguns bytes para

otimizar o alinhamento de memória e o acesso durante a execução.

 

Agora precisamos definir nossa mesa GDT e depois carregá-la usando LGDT. A tabela

GDT pode ser armazenada onde quisermos na memória, o endereço deve ser apenas um

sinal para o processo usando o registro GDTR.

 

A tabela GDT é composta de segmentos com a seguinte estrutura:

 

gdtentry.png

 

E a estrutura C:

 

É necessário se cadastrar para acessar o conteúdo.

 

 

Como definir nossa mesa GDT?

 

Precisamos agora definir a nossa GDT na memória e, finalmente, carregá-lo usando o registro GDTR.

 

Estamos indo para armazenar nossa GDT no endereço:

 

É necessário se cadastrar para acessar o conteúdo.

 

A função init_gdt_desc in x86.cc inicializa um descritor de segmento gdt

 

É necessário se cadastrar para acessar o conteúdo.

 

 

E a função de inicializar o GDT init_gdt, algumas partes da função abaixo serão

explicados mais tarde, e são usados para multitarefa.

 

É necessário se cadastrar para acessar o conteúdo.

 

 

 

 

 

Capítulo 7: IDT e interrupções

 

 

 

Uma interrupção é um sinal para o processador emitida por hardware ou software que

indica um acontecimento que necessite de atenção imediata.

 

Existem 3 tipos de interrupção:

 

Interrupções de hardware: são enviadas para o processador de um dispositivo externo

(teclado, mouse, disco rígido, ...). Interrupções de hardware foram introduzidos

como uma forma de reduzir o desperdício de tempo valioso do processador em loops

de votação, à espera de eventos externos.

Interrupções de software: são iniciados voluntariamente pelo software.

Ele é usado para gerenciar as chamadas do sistema.

Exceções: são usados por erros ou eventos que ocorrem durante a execução

do programa que são excepcionais o suficiente para que eles não podem ser

tratadas no próprio programa (divisão por zero, falha de página, ...)

O exemplo de teclado:

 

Quando o usuário pressionar uma tecla no teclado, o controlador do teclado vai

sinalizar uma interrupção para o controlador de interrupção. Se a interrupção

não é mascarado, o controlador irá sinalizar a interrupção para o processador,

o processador irá executar uma rotina para gerenciar a interrupção

(tecla pressionada ou a chave liberada), esta rotina pode, por exemplo,

obter a tecla pressionada a partir do controlador de teclado e imprimir

a chave para a tela. Uma vez que a rotina de processamento de caracteres

é concluído, o trabalho interrompido pode ser retomado.

 

Qual é o PIC?

 

O PIC (controlador de interrupção programável) é um dispositivo que é

usado para combinar várias fontes de interrupção para uma ou mais linhas

de CPU, enquanto permite que os níveis de prioridade a ser atribuído às

suas saídas de interrupção. Quando o dispositivo tem várias saídas de

interrupção para afirmar, afirma-los em ordem de prioridade relativa.

 

O mais conhecido é o PIC 8259A, 8259A pode lidar com cada oito dispositivos,

mas a maioria dos computadores tem dois controladores: um mestre e um escravo,

isso permite que o computador para gerenciar as interrupções a partir de 14 dispositivos.

 

Neste capítulo, vamos precisar de programar este controlador para inicializar

e interrupções de máscara.

 

Qual é o IDT?

 

A interrupção Descriptor Table (IDT) é uma estrutura de dados utilizada pela

arquitetura x86 para implementar uma tabela de vetores de interrupção.

O IDT é usada pelo processador para determinar a resposta correta para

interrupções e excepções.

Nosso núcleo vai usar o IDT para definir as diferentes funções a serem

executadas quando uma interrupção ocorreu.

 

Como o GDT, IDT é carregado usando as instruções de montagem LIDTL.

Ele espera que a localização de uma descrição da estrutura do IDT:

 

 

É necessário se cadastrar para acessar o conteúdo.

 

 

A tabela IDT é composta de segmentos de IDT com a seguinte estrutura:

 

É necessário se cadastrar para acessar o conteúdo.

 

 

Cuidado: A diretiva __attribute__ ((packed)) sinalizar para gcc que a

estrutura deve usar o mínimo de memória possível. Sem esta directiva,

gcc inclui alguns bytes para otimizar o alinhamento de memória eo acesso durante a execução.

 

Agora precisamos definir nossa mesa IDT e, em seguida, carregá-lo usando

LIDTL. A tabela IDT podem ser armazenados onde quisermos na memória,

o endereço deve ser apenas um sinal para o processo usando o registro IDTR.

 

Aqui está uma tabela de interrupções comuns (interrupção de hardware Maskable são chamados de IRQ):

 

IRQ Descrição

0 de interrupção programável temporizador de interrupção

1 Interrupção de teclado

2 Cascade (usado internamente pelos dois PICs. Nunca levantou)

3 COM2 (se habilitado)

4 COM1 (se habilitado)

5 LPT2 (se habilitado)

6 Disquete

7 LPT1

8 CMOS relógio de tempo real (se habilitado)

9 Gratuito para os periféricos / legado SCSI / NIC

10 Grátis para os periféricos / SCSI / NIC

11 Grátis para os periféricos / SCSI / NIC

12 Mouse PS2

13 FPU / coprocessador / Inter-processador

14 Primária ATA Hard Disk

15 ATA Secundária Hard Disk

 

 

Como inicializar as interrupções?

 

Este é um método simples de definir um segmento de IDT

 

É necessário se cadastrar para acessar o conteúdo.

 

 

E agora podemos inicializar os interupts:

 

É necessário se cadastrar para acessar o conteúdo.

 

 

 

É necessário se cadastrar para acessar o conteúdo.

 

Após intialization do nosso IDT, precisamos ativar interrupções configurando o PIC. A função a seguir irá configurar os dois PICs pela escrita nos seus registos internos utilizando os portos do io.outb processador de saída. Nós configurar as PICs usando as portas:

 

 

  • Master PIC: 0x20 and 0x21
  • Slave PIC: 0xA0 and 0xA1

 

 

Para um PIC, existem 2 tipos de registros:

 

ICW (Inicialização Palavra de Comando): reinit o controlador

OCW (Word Controle da Operação): configurar o controlador de uma vez inicializado (usado para mascarar / desmascarar as interrupções)

 

 

É necessário se cadastrar para acessar o conteúdo.

 

 

PIC ICW configurações detalhes

 

Os registros devem ser configurados em ordem.

 

 

ICW1 (port 0x20 / port 0xA0)

 

|0|0|0|1|x|0|x|x|

| | +--- with ICW4 (1) or without (0)

| +----- one controller (1), or cascade (0)

+--------- triggering by level (level) (1) or by edge (edge) (0)

 

 

 

ICW2 (port 0x21 / port 0xA1)

 

|x|x|x|x|x|0|0|0|

| | | | |

+----------------- base address for interrupts vectors

 

 

 

ICW2 (port 0x21 / port 0xA1)

 

For the master:

 

|x|x|x|x|x|x|x|x|

| | | | | | | |

+------------------ slave controller connected to the port yes (1), or no (0)

 

 

 

For the slave:

 

|0|0|0|0|0|x|x|x| pour l'esclave

| | |

+-------- Slave ID which is equal to the master port

 

 

ICW4 (port 0x21 / port 0xA1)

 

Ele é usado para definir em modo que o controlador deve funcionar.

|0|0|0|x|x|x|x|1|

| | | +------ mode "automatic end of interrupt" AEOI (1)

| | +-------- mode buffered slave (0) or master (1)

| +---------- mode buffered (1)

+------------ mode "fully nested" (1)

 

 

Por que compensar segmentos IDT nossas funções ASM?

 

Você deve ter notado que quando eu estou inicializando nossos segmentos de IDT, estou usando deslocamentos para o segmento de código em Assembly. As diferentes funções são definidas no x86int.asm e são do seguinte esquema:

 

 

É necessário se cadastrar para acessar o conteúdo.

 

Estas macros serão usadas para definir o segmento de interrupção que irá prevenir a corrupção dos registos diferentes, será muito útil para a multitarefa.

 

 

 

Capítulo 8: Gerenciamento de memória: físico e virtual

 

 

No capítulo relacionado com o GDT, vimos que o uso de segmentação um endereço de memória física é calculada usando um seletor de segmento e um deslocamento.

 

Neste capítulo, vamos implementar a paginação, paginação irá traduzir um endereço linear de segmentação em um endereço físico.

 

Por que precisamos de paginação?

 

Paginação permitirá que nosso kernel:

 

Use o disco rígido como memória e não ser limitado pelo limite de memória RAM da máquina;

para ter um único espaço de memória para cada processo de

para permitir e espaço de memória unallow de forma dinâmica

Em um sistema paginado, cada processo pode executar em sua própria área de 4GB de memória, sem qualquer chance de efetuar a memória de qualquer outro processo, ou o kernel do. Ele simplifica multitarefa.

 

processes.png

 

Como isso funciona?

 

A tradução de um endereço linear para um endereço físico é feita em vários passos:

 

O processador usa o CR3 registro para saber o endereço físico do diretório páginas.

Os primeiros 10 bits do endereço linear representam um deslocamento (entre 0 e 1023), apontando para uma entrada no diretório de páginas. Esta entrada contém o endereço físico de uma tabela de páginas.

os próximos 10 bits do endereço linear representam um deslocamento, apontando para uma entrada na tabela de páginas. Esta entrada está apontando para uma página 4KO.

Os últimos 12 bits do endereço representam um deslocamento linear (entre 0 e 4095), o que indica a posição na página 4KO.

 

paging_memory.png

 

Como isso funciona?

 

A tradução de um endereço linear para um endereço físico é feita em vários passos:

 

O processador usa o CR3 registro para saber o endereço físico do diretório páginas.

Os primeiros 10 bits do endereço linear representam um deslocamento (entre 0 e 1023), apontando para uma entrada no diretório de páginas. Esta entrada contém o endereço físico de uma tabela de páginas.

os próximos 10 bits do endereço linear representam um deslocamento, apontando para uma entrada na tabela de páginas. Esta entrada está apontando para uma página 4KO.

Os últimos 12 bits do endereço representam um deslocamento linear (entre 0 e 4095), o que indica a posição na página 4KO.

 

 

page_directory_entry.png

page_table_entry.png

 

P: indicar se a página ou tabela está na memória física

R / W: indica se a página ou tabela é acessível em escrita (igual a 1)

L / S: é igual a 1 para permitir o acesso a tarefas não preferidos

A: indicar se a página ou tabela foi acessada

D: (somente para a tabela de páginas) indicar se a página foi escrita

PS (apenas para o diretório de páginas) indicam o tamanho das páginas:

0 = 4KO

1 = 4mo

 

Note: Physical addresses in the pages diretcory or pages table are written using 20 bits because these addresses are aligned on 4ko, so the last 12bits should be equal to 0.

A pages directory or pages table used 1024*4 = 4096 bytes = 4kA pages table can address 1024 * 4k = 4 MoA pages directory can address 1024 * (1024 * 4k) = 4 Go

Como habilitar a paginação?

Para ativar a paginação, só precisamos definir bit 31 do CR0registry a 1:

 

É necessário se cadastrar para acessar o conteúdo.

 

Mas antes, é preciso inicializar o nosso diretório de páginas com pelo menos uma tabela de páginas.

 

 

 

Fonte: https://github.com/SamyPesse/How-to-Make-a-Computer-Operating-System

qRXaV1L.png

Link para o comentário
Compartilhar em outros sites

Este tópico está impedido de receber novos posts.
 Compartilhar

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...

Informação Importante

Nós fazemos uso de cookies no seu dispositivo para ajudar a tornar este site melhor. Você pode ajustar suas configurações de cookies , caso contrário, vamos supor que você está bem para continuar.