
Bom dia, caro leitor, provavelmente, você viu meu artigo anterior que você mesmo pode escrever um sistema operacional viável em um período bastante curto de tempo. Bem, hoje falaremos sobre a implementação da multitarefa no meu sistema operacional.
Bem, você provavelmente não pode imaginar um sistema operacional de tarefa única em 2018, então decidi falar sobre a implementação da multitarefa no meu sistema operacional. E assim, a primeira coisa - você precisa decidir sobre o tipo de multitarefa, eu escolhi preventivo.
Como ela é? A multitarefa preemptiva é um sistema para distribuir o poder de computação do processador entre os processos: cada um tem seu próprio quantum de tempo, cada um tem sua própria prioridade. E o primeiro problema é qual quantum escolher em tamanho, como parar o processo de execução depois que o quantum expira? De fato, tudo está mais fácil do que nunca! Usaremos o PIT com uma frequência inicialmente definida de 10026 com centavos de interrupções por segundo, e aí resolveremos mais um problema: já estamos parando o processo anterior. E então, vamos começar com o PIT.
Pit
PIT - Programmable Interval Timer - um contador que, ao atingir qualquer número programado de incrementos, emite um sinal. Além disso, usando esse timer, você pode chiar um squeaker no computador (a coisa que chia depois de passar no teste do dispositivo). E assim, ele conta com uma frequência de 1193182 hertz, o que significa que precisamos programá-lo em 119 (1193182/119 é aproximadamente igual a 10026). Para fazer isso, envie 2 bytes para a porta do primeiro gerador, primeiro o byte baixo e depois o alto:
unsigned short hz = 119; outportb(0x43, 0x34); outportb(0x40, (unsigned char)hz & 0xFF);
Agora vale a pena iniciar a programação da interrupção a partir do PIT, ele tem um IRQ de 0 e, após o remapeamento, o PIC será de 0x20m. Para o IRQ do primeiro PIC, escrevi esta macro:
Estrutura e processos
E assim, como você entende, precisamos desenvolver uma estrutura para cada processo, bem como uma estrutura que me permita lembrar de todas as minhas alocações de memória.
Aqui está o que eu tenho:
typedef struct _pralloc { void * addr; struct _pralloc * next; } processAlloc; typedef struct { void * entry; processAlloc *allocs; } ELF_Process; typedef struct __attribute__((packed)) _E { unsigned int eax;
Para começar, precisamos entender o seguinte: em algum lugar do endereço global, por exemplo, em 0xDEAD, coloque o número do processo em execução no momento; em seguida, ao executar qualquer código, podemos ter certeza: temos o número do processo em execução no momento, isso significa que ao acessar o malloc, sabemos a quem estamos alocando memória e podemos adicionar imediatamente o endereço da memória alocada à lista de alocações.
void addProcessAlloc(ELF_Process * p, void * addr) { void * z = p->allocs; p->allocs = malloc_wo_adding_to_process(sizeof(processAlloc)); p->allocs->addr = addr; p->allocs->next = z; }
Bem, escrevemos a estrutura da tabela com a descrição dos processos, o que vem a seguir, como alternar tarefas?
Para começar, quero observar que, por exemplo, no manipulador, variáveis locais são armazenadas na pilha, o que significa que, depois de inserir o manipulador, o compilador nos estraga esp. Para evitar que isso aconteça, crie uma variável com um endereço absoluto e, antes de chamar o manipulador, colocaremos o ESP lá. No manipulador, precisamos enviar o EOI para o primeiro PIC e encontrar o processo para o qual precisamos mudar (não descreverei o mecanismo de prioridade: é simples, como um engarrafamento). Em seguida - precisamos salvar todos os registradores e sinalizadores do processo atual; portanto, imediatamente antes de colocar o ESP em uma variável, salvaremos todos os registradores (incluindo os do segmento) na pilha. No próprio manipulador, precisamos removê-los com muito cuidado da pilha, preservando também os sinalizadores e o endereço de retorno. Quero observar que a pilha cresce (ou seja, o ESP diminui), o que significa que o último registro que você salvou na pilha será no ESP, o penúltimo será o ESP +4, etc .:

Agora resta colocar os valores dos registros do processo nos registros para os quais trocamos e executamos o IRET. Lucro!
Início do processo
Ao iniciar o processo, basta alocar a pilha para o processo e, em seguida, colocar argc e argv nela, o endereço da função que terá controle após a conclusão do processo. Você também precisa definir os sinalizadores do processador com o valor necessário, por exemplo, para o meu sistema operacional é 0x216, pode ler sobre o registro de sinalizadores na Wikipedia.
No final, desejo lhe desejar sucesso, em breve irei escrever sobre como trabalhar com memória e outros artigos de seu interesse.
Boa sorte e hackers éticos!