Bem vindo !

Não perca tempo, registre-se agora mesmo! Membros registrados tem acesso a muito mais conteúdos, além de poder participar de bate-papos, discussões e compartilhar novidades com a comunidade. Não perca essa oportunidade!

Criando um Sistema Operacional do Zero

Discussão em 'Sistema Operacional' iniciado por Arkanun1000, 6 Out, 2017.

Compartilhe esta Página

  1. Arkanun1000 Game Developer & Java Developer

    Moderador Coordenador
    Arkanun1000
    Registro:
    25 Jan, 2011
    Posts:
    3,206
    Curtidas:
    558
    200/265
    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 Clique aqui e Registre-se para visualizar esse link.

    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 Clique aqui e Registre-se para visualizar esse link.

    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 Clique aqui e Registre-se para visualizar esse link
    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 Clique aqui e Registre-se para visualizar esse link.

    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:

    Código:
    struct multiboot_info {
       u32 flags;
       u32 low_mem;
       u32 high_mem;
       u32 boot_device;
       u32 cmdline;
       u32 mods_count;
       u32 mods_addr;
       struct {
           u32 num;
           u32 size;
           u32 addr;
           u32 shndx;
       } elf_sec;
       unsigned long mmap_length;
       unsigned long mmap_addr;
       unsigned long drives_length;
       unsigned long drives_addr;
       unsigned long config_table;
       unsigned long boot_loader_name;
       unsigned long apm_table;
       unsigned long vbe_control_info;
       unsigned long vbe_mode_info;
       unsigned long vbe_mode;
       unsigned long vbe_interface_seg;
       unsigned long vbe_interface_off;
       unsigned long vbe_interface_len;
    };
    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:

    Código:
    qemu-img create c.img 2M
    
    Precisamos agora particionar o disco usando fdisk:

    Código:
    fdisk ./c.img
    


    Código:
    # Switch to Expert commands
    > x
    
    # Change number of cylinders (1-1048576)
    > c
    > 4
    
    # Change number of heads (1-256, default 16):
    > h
    > 16
    
    # Change number of sectors/track (1-63, default 63)
    > s
    > 63
    
    # Return to main menu
    > r
    
    # Add a new partition
    > n
    
    # Choose primary partition
    > p
    
    # Choose partition number
    > 1
    
    # Choose first cylinder (1-4, default 1)
    > 1
    
    # Choose last cylinder, +cylinders or +size{K,M,G} (1-4, default 4)
    > 4
    
    # Toggle bootable flag
    > a
    
    # Choose first partition for bootable flag
    > 1
    
    # Write table to disk and exit
    > w


    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.

    Código:
    losetup -o 32256 /dev/loop1 ./c.img

    We create a EXT2 filesystem on this new device using:

    Código:
    mke2fs /dev/loop1
    We copy our files on a mounted disk:

    Código:
    mount  /dev/loop1 /mnt/
    cp -R bootdisk/* /mnt/
    umount /mnt/

    Install GRUB on the disk:

    Código:
    grub --device-map=/dev/null << EOF
    device (hd0) ./c.img
    geometry (hd0) 4 16 63
    root (hd0,0)
    setup (hd0)
    quit
    EOF
    E, finalmente, retirar o dispositivo de loop:

    Código:
    losetup -d /dev/loop1
    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:


    Código:
    void    itoa(char *buf, unsigned long int n, int base);
    
    void *  memset(char *dst,char src, int n);
    void *  memcpy(char *dst, char *src, int n);
    
    int     strlen(char *s);
    int     strcmp(const char *dst, char *src);
    int     strcpy(char *dst,const char *src);
    void    strcat(void *dest,const void *src);
    char *  strncpy(char *destString, const char *sourceString,int maxLength);
    int     strncmp( const char* s1, const char* s2, int c );
    

    These functions are defined in Clique aqui e Registre-se para visualizar esse link, Clique aqui e Registre-se para visualizar esse link, Clique aqui e Registre-se para visualizar esse link

    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:


    Código:
    # Linker LD=ld LDFLAG= -melf_i386 -static -L ./ -T ./arch/$(ARCH)/linker.ld # C++ compiler SC=g++ FLAG= $(INCDIR) -g -O2 -w -trigraphs -fno-builtin -fno-exceptions -fno-stack-protector -O0 -m32 -fno-rtti -nostdlib -nodefaultlibs # Assembly compiler ASM=nasm ASMFLAG=-f elf -o
    


    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.


    Código:
    /* put a byte on screen */
    void Io::putc(char c){
       kattr = 0x07;
       unsigned char *video;
       video = (unsigned char *) (real_screen+ 2 * x + 160 * y);
       // newline
       if (c == '\n') {
           x = 0;
           y++;
       // back space
       } else if (c == '\b') {
           if (x) {
               *(video + 1) = 0x0;
               x--;
           }
       // horizontal tab
       } else if (c == '\t') {
           x = x + 8 - (x % 8);
       // carriage return
       } else if (c == '\r') {
           x = 0;
       } else {
           *video = c;
           *(video + 1) = kattr;
    
           x++;
           if (x > 79) {
               x = 0;
               y++;
           }
       }
       if (y > 24)
           scrollup(y - 24);
    }



    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:


    [​IMG]


    E a estrutura C:


    Código:
    struct gdtr {
       u16 limite;
       u32 base;
    } __attribute__ ((packed));
    




    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:

    [​IMG]

    E a estrutura C:

    Código:
    struct gdtdesc {
       u16 lim0_15;
       u16 base0_15;
       u8 base16_23;
       u8 acces;
       u8 lim16_19:4;
       u8 other:4;
       u8 base24_31;
    } __attribute__ ((packed));



    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:

    Código:
    #define GDTBASE 0x00000800


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

    Código:
    void init_gdt_desc(u32 base, u32 limite, u8 acces, u8 other, struct gdtdesc *desc)
    {
       desc->lim0_15 = (limite & 0xffff);
       desc->base0_15 = (base & 0xffff);
       desc->base16_23 = (base & 0xff0000) >> 16;
       desc->acces = acces;
       desc->lim16_19 = (limite & 0xf0000) >> 16;
       desc->other = (other & 0xf);
       desc->base24_31 = (base & 0xff000000) >> 24;
       return;
    }
    



    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.

    Código:
    void init_gdt(void)
    {
       default_tss.debug_flag = 0x00;
       default_tss.io_map = 0x00;
       default_tss.esp0 = 0x1FFF0;
       default_tss.ss0 = 0x18;
    
       /* initialize gdt segments */
       init_gdt_desc(0x0, 0x0, 0x0, 0x0, &kgdt[0]);
       init_gdt_desc(0x0, 0xFFFFF, 0x9B, 0x0D, &kgdt[1]);  /* code */
       init_gdt_desc(0x0, 0xFFFFF, 0x93, 0x0D, &kgdt[2]);  /* data */
       init_gdt_desc(0x0, 0x0, 0x97, 0x0D, &kgdt[3]);      /* stack */
    
       init_gdt_desc(0x0, 0xFFFFF, 0xFF, 0x0D, &kgdt[4]);  /* ucode */
       init_gdt_desc(0x0, 0xFFFFF, 0xF3, 0x0D, &kgdt[5]);  /* udata */
       init_gdt_desc(0x0, 0x0, 0xF7, 0x0D, &kgdt[6]);      /* ustack */
    
       init_gdt_desc((u32) & default_tss, 0x67, 0xE9, 0x00, &kgdt[7]); /* descripteur de tss */
    
       /* initialize the gdtr structure */
       kgdtr.limite = GDTSIZE * 8;
       kgdtr.base = GDTBASE;
    
       /* copy the gdtr to its memory area */
       memcpy((char *) kgdtr.base, (char *) kgdt, kgdtr.limite);
    
       /* load the gdtr registry */
       asm("lgdtl (kgdtr)");
    
       /* initiliaz the segments */
       asm("   movw $0x10, %ax \n \
                movw %ax, %ds   \n \
                movw %ax, %es   \n \
                movw %ax, %fs   \n \
                movw %ax, %gs   \n \
                ljmp $0x08, $next   \n \
                next:       \n");
    }


    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:


    Código:
    struct idtr {
       u16 limite;
       u32 base;
    } __attribute__ ((packed));
    



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

    Código:
    struct idtdesc {
       u16 offset0_15;
       u16 select;
       u16 type;
       u16 offset16_31;
    } __attribute__ ((packed));
    



    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

    Código:
    void init_idt_desc(u16 select, u32 offset, u16 type, struct idtdesc *desc)
    {
       desc->offset0_15 = (offset & 0xffff);
       desc->select = select;
       desc->type = type;
       desc->offset16_31 = (offset & 0xffff0000) >> 16;
       return;
    }



    E agora podemos inicializar os interupts:

    Código:
    #define IDTBASE 0x00000000
    #define IDTSIZE 0xFF
    idtr kidtr;
    




    Código:
    void init_idt(void)
    {
       /* Init irq */
       int i;
       for (i = 0; i < IDTSIZE; i++)
           init_idt_desc(0x08, (u32)_asm_schedule, INTGATE, &kidt); //
    
       /* Vectors  0 -> 31 are for exceptions */
       init_idt_desc(0x08, (u32) _asm_exc_GP, INTGATE, &kidt[13]);     /* #GP */
       init_idt_desc(0x08, (u32) _asm_exc_PF, INTGATE, &kidt[14]);     /* #PF */
    
       init_idt_desc(0x08, (u32) _asm_schedule, INTGATE, &kidt[32]);
       init_idt_desc(0x08, (u32) _asm_int_1, INTGATE, &kidt[33]);
    
       init_idt_desc(0x08, (u32) _asm_syscalls, TRAPGATE, &kidt[48]);
       init_idt_desc(0x08, (u32) _asm_syscalls, TRAPGATE, &kidt[128]); //48
    
       kidtr.limite = IDTSIZE * 8;
       kidtr.base = IDTBASE;
    
    
       /* Copy the IDT to the memory */
       memcpy((char *) kidtr.base, (char *) kidt, kidtr.limite);
    
       /* Load the IDTR registry */
       asm("lidtl (kidtr)");
    }



    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)


    Código:
    void init_pic(void)
    {
       /* Initialization of ICW1 */
       io.outb(0x20, 0x11);
       io.outb(0xA0, 0x11);
    
       /* Initialization of ICW2 */
       io.outb(0x21, 0x20);    /* start vector = 32 */
       io.outb(0xA1, 0x70);    /* start vector = 96 */
    
       /* Initialization of ICW3 */
       io.outb(0x21, 0x04);
       io.outb(0xA1, 0x02);
    
       /* Initialization of ICW4 */
       io.outb(0x21, 0x01);
       io.outb(0xA1, 0x01);
    
       /* mask interrupts */
       io.outb(0x21, 0x0);
       io.outb(0xA1, 0x0);
    }
    



    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:


    Código:
    %macro  SAVE_REGS 0
       pushad
       push ds
       push es
       push fs
       push gs
       push ebx
       mov bx,0x10
       mov ds,bx
       pop ebx
    %endmacro
    
    %macro  RESTORE_REGS 0
       pop gs
       pop fs
       pop es
       pop ds
       popad
    %endmacro
    
    %macro  INTERRUPT 1
    global _asm_int_%1
    _asm_int_%1:
       SAVE_REGS
       push %1
       call isr_default_int
       pop eax ;;a enlever sinon
       mov al,0x20
       out 0x20,al
       RESTORE_REGS
       iret
    %endmacro
    


    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.

    [​IMG]

    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.

    [​IMG]

    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.


    [​IMG]
    [​IMG]

    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:

    Código:
    asm("  mov %%cr0, %%eax; \
           or %1, %%eax;     \
           mov %%eax, %%cr0" \
           :: "i"(0x80000000));
    
    Mas antes, é preciso inicializar o nosso diretório de páginas com pelo menos uma tabela de páginas.

    Fonte: Clique aqui e Registre-se para visualizar esse link
     
    Última edição: 6 Out, 2017
    FilipeDG curtiu isso.
Top