Fazendo um kernel Simples em  C com as funções Básicas printf e clearscreen

 

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).

Compilando o Kernel

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).

Conclusão

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 ????????