Este tutorial tem o propósito de mostrar como desenvolver um kernel simples. Comecemos com a entrada do kernel que está em nosso exemplo no arquivo kernel_start.asm.
[BITS 32]
[global start]
[extern _k_main]; isto está no arquivo c
start:
call _k_main
cli; para as interrupções
hlt; para o CPU
Certo, este é um código de 32-bits (o [BITS 32] simboliza isso) e chama a função k_main que está definida em um arquivo C chamado kernel.c. Agora você provavelmente está desejando saber por que é chamado k_main no arquivo C, e _k_main no arquivo de assembly. Isto é porque os compiladores de C/C++ adicionam um unsderscore (_) na frente de todas as funções de C/C++... A menos que você una a um arquivo ELF. ELF não precisa do underscore. Uma vez que k_main é chamado, a instrução cli é executada. cli desliga interrupções(entretanto elas nunca foram usadas neste exemplo). Então, hlt é executado que diz para o CPU que deixe de executar. Nós poderíamos fazer jmp $ em vez de hlt, mas isso demora muito e poderia aquecer o CPU demais. Nota a interrupção pode despertar o CPU de uma instrução de hlt. Por isso é que nós desabilitamos as interrupções antes de fazer hlt como queremos uma parada completa.
Agora, passaremos para as definições e protótipos de função definidos ao começo de kernel.c.
#define WHITE_TXT 0x07 // branco em texto preto
void k_clear_screen();
unsigned int k_printf(char *message, unsigned int line);
void update_cursor(int row, int col);
Nada de especial aqui com exceção do #define WHITE_TXT 0x07. Nós voltaremos a isso daqui a pouco, agora, só se lembre que está lá.
Agora, passaremos para a função k_main.
k_main() // semelhante a função main num programa C normal
{
k_clear_screen();
k_printf("Oi!\nComo esta para o comeco de um SO?", 0);
};
k_main é o ponto de entrada de nosso kernel.Nós chamamos esta função no arquivo de kernel_start.asm.
k_clear_screen faz o que você já esperava... limpa a tela. k_printf("Oi!\nComo esta para o comeco de um SO?", 0); imprime o texto:
Oi!
Como esta para o comeco de um SO?
Começando na primeira linha da memória do vídeo (0 é a primeira linha, 1 é a
segunda, 2 é a terceira, etc). O \n
especifica uma nova linha igual na função printf do C/C++.
Em modo protegido, você não pode chamar interrupções da BIOS para limpar a tela, temos que fazer isto nós mesmos escrevendo diretamente na Memória de Vídeo.
void k_clear_screen() // limpe a tela inteira de texto
{
char *vidmem = (char *) 0xb8000;
unsigned int i=0;
while(i < (80*25*2))
{
vidmem[i]=' ';
i++;
vidmem[i]=WHITE_TXT;
i++;
};
};
Na função anterior (k_clear_screen), o ponteiro vidmem aponta para 0xb8000 que é o começo de memória de vídeo em modo protegido. Nós declaramos o ponteiro como um do tipo char, assim nós podemos escrever um byte de cada vez na memória de vídeo. O modo de texto em um x86 é 80 x 25 caracteres. Cada caractere precisa de 2 bytes. O primeiro byte é o caractere, o segundo byte é o byte de atributo que controla cor e fundo. Assim, nós levamos 80*25(a quantia de caracteres que pode ser exibido na tela) e multiplicamos por 2 desde que tenhamos acesso memória de vídeo um byte de cada vez. O loop quase é a própria explicação. O vidmem[i] = ' '; escreve um espaço na memória (vidmem aponta à marca exata). Nós somamos 1 a i, i++, para ir ao próximo byte da memória (o byte de atributo do vídeo) e botamos 0x07 lá. 0x07 especifica um fundo preto com branco, texto sem piscar.
Agora para a função k_printf!
unsigned int k_printf(char *message, unsigned int line) // a mensagem e então a linha #
{
char *vidmem = (char *) 0xb8000;
unsigned int i=0;
i=(line*80*2);
while(*message!=0)
{
if(*message=='\n') // checa para nova linha
{
line++;
i=(line*80*2);
*message++;
} else {
vidmem[i]=*message;
*message++;
i++;
vidmem[i]=WHITE_TXT;
i++;
};
};
return(1);
};
A função k_printf trabalha como a função k_clear_screen. while(*message!=0), ele volta até que nós chegamos ao fim da cadeia de texto que é passado à função. if(*message == ' \n ') confere para ver se o próximo caractere da cadeia for uma nova linha.. Se for, nós somamos 1 para enfileirar de forma que o caracteres que vem atrás de um \n estará uma linha abaixo. Se um caractere não é um newline (\n), nós há pouco colocamos o caractere em memória de vídeo e fixamos o byte de atributo a fundo para 0x07 (preto com texto branco sem piscar).
Primeiro, baixe o código fonte do kernel. Você precisará de um assembler (NASM), um compilador de C (DJGPP ou gcc), e um linkador (LD).
Agora,
em cima perto do topo do arquivo de
linker, você verá esta linha:
.text 0x100000
O número hex precisa ser fixado onde o kernel será carregado na memória. Neste
caso que está na marca do 1MB (0x100000 em hex).
Compilaremos nosso arquivo de código assembly primeiro:
nasm -f aout kernel_start.asm -o ks.o
Isto compila kernel_start.asm para ks.o em formato aout. Agora para nosso arquivo C:
gcc -c kernel.c -o kernel.o
O próximo e último passo é unir ks.o e kernel.o em um arquivo apenas. Neste
caso, nós vamos linkar em um arquivo binário com o script link.ld. Nós
unimos os dois arquivos com este comando:
ld
-T link.ld -o kernel.bin ks.o kernel.o
É importante que ks.o é lincado primeiro ou o kernel não irá funcionar.
O kernel é chamado de kernel.bin
e está pronto para executar por um bootsector / loader que
seta o Modo Protegido e ativa o A20(Arquivo
de John Fine's bootf02 bootsector
faz isso).
Se você gosta ou quer usar o GRUB
para
carregar o kernel,
você
pode baixar uma versão do GRUB
aqui(você
compila-o e linca de qualquer modo).
Pronto! Um kernel básico. Você vai querer escrever um k_printf que funcione melhor, como o usado neste exemplo é bastante simples e não controla coisas como %s, %d, %c, etc. Ainda, com isso poderá ser o bastante para começar a fazer um melhor.
Traduzido por CrociDB e revisado por M@uro, autor ????????