Écriture du système d'exploitation: multitâche

image
Bonjour, cher lecteur, très probablement, vous avez vu mon article précédent que vous pouvez vous-même écrire un système d'exploitation réalisable dans un laps de temps assez court. Eh bien, aujourd'hui, nous allons parler de la mise en œuvre du multitâche dans mon système d'exploitation.

Eh bien, vous ne pouvez probablement pas imaginer un système d'exploitation unique en 2018, j'ai donc décidé de parler de la mise en œuvre du multitâche dans mon système d'exploitation. Et donc, première chose - vous devez décider du type de multitâche, j'ai choisi la préemption.
Comment est-elle? Le multitâche préemptif est un système de répartition de la puissance de calcul du processeur entre les processus: chacun a sa propre tranche de temps, chacun a sa propre priorité. Et le premier problème est quel quantum choisir en longueur, comment arrêter le processus de s'exécuter après l'expiration du quantum? En fait, tout est plus facile que jamais! Nous utiliserons PIT avec la fréquence initialement fixée à 10026 avec un sou d'interruptions par seconde, là nous résolvons un problème de plus: nous arrêtons déjà le processus précédent. Et donc, commençons par PIT.

Fosse


PIT - Programmable Interval Timer - un compteur qui, lorsqu'il atteint un nombre d'incréments programmé, donne un signal. En outre, en utilisant cette minuterie, vous pouvez grincer un grincement dans l'ordinateur (la chose qui grince après avoir passé le test de l'appareil). Et donc, il compte à une fréquence de 1193182 hertz, ce qui signifie que nous devons le programmer à 119 (1193182/119 est approximativement égal à 10026). Pour ce faire, envoyez 2 octets au port du premier générateur, d'abord l'octet bas, puis le haut:

unsigned short hz = 119; outportb(0x43, 0x34); outportb(0x40, (unsigned char)hz & 0xFF); //Low outportb(0x40, (unsigned char)(hz >> 8) & 0xFF); //Hight, about 10026 times per second 


Maintenant, cela vaut la peine de commencer la programmation de l'interruption à partir de PIT, elle a un IRQ de 0, et après le remappage de PIC, ce sera 0x20m. Pour l'IRQ du premier PIC, j'ai écrit cette macro:

 //PIC#0; port 0x20 #define IRQ_HANDLER(func) char func = 0x90;\ __asm__(#func ": \npusha \n call __"#func " \n movb $0x20,\ %al \n outb %al, $0x20 \n popa \n iret \n");\ void _## func() 


Structure et processus


Et donc, comme vous le comprenez, nous devons développer une structure pour chaque processus, ainsi qu'une structure qui me permette de me souvenir de toutes mes allocations de mémoire.
Voici ce que j'ai:
 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;//4 unsigned int ebx;//8 unsigned int ecx;//12 unsigned int edx;//16 unsigned int ebp;//20 unsigned int esp;//24 unsigned int esi;//28 unsigned int edi;//32 unsigned int eflags;//36 unsigned int state;//40 void * startAddr;//44 void * currentAddr;//48 void * stack;//52 unsigned int sse[4 * 8];// unsigned int mmx[2 * 8];//244 unsigned int priority;//248 unsigned int priorityL;//252 void * elf_process;//256 char ** argv;//260 unsigned int argc;//264 unsigned int runnedFrom;//268 char * workingDir;//272 unsigned int cs;//276 - pop is 4 byte in IRET unsigned int ds;//280 } Process; 


Pour commencer, nous devons comprendre ce qui suit: nous pouvons quelque part à l'adresse globale, par exemple, à 0xDEAD mettre le numéro du processus en cours d'exécution, puis lors de l'exécution de tout code, nous pouvons être sûrs: nous avons le numéro du processus en cours d'exécution, cela signifie que lors de l'accès à malloc, nous savons à qui nous allouons de la mémoire et nous pouvons immédiatement ajouter l'adresse de la mémoire allouée à la liste des allocations.
 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; } 


Eh bien, nous avons écrit la structure du tableau avec la description des processus, que faire ensuite, comment changer de tâche?
Pour commencer, je veux noter que, par exemple, dans le gestionnaire, les variables locales sont stockées sur la pile, ce qui signifie qu'après être entré dans le gestionnaire, le compilateur nous gâte esp. Pour éviter cela, créez une variable avec une adresse absolue, et avant d'appeler le gestionnaire, nous y mettrons l'ESP. Dans le gestionnaire, nous devons envoyer l'EOI au premier PIC et trouver le processus vers lequel nous devons basculer (je ne décrirai pas le mécanisme de priorité: c'est simple, comme un embouteillage). Ensuite - nous devons sauvegarder tous les registres et drapeaux du processus en cours, donc juste avant de placer l'ESP dans une variable, nous enregistrerons tous les registres (y compris ceux du segment) sur la pile. Dans le gestionnaire lui-même, nous devons très soigneusement les supprimer de la pile, tout en préservant les indicateurs et l'adresse de retour. Je veux noter que la pile augmente (c'est-à-dire que ESP diminue), ce qui signifie que le dernier registre que vous avez enregistré sur la pile sera à ESP, l'avant-dernier sera ESP +4, etc.:
image
Il nous reste maintenant à mettre les valeurs des registres de processus dans les registres vers lesquels nous sommes passés et exécutons IRET. Profit!

Début du processus


Au démarrage du processus, il nous suffit d'allouer la pile pour le processus, puis d'y mettre argc et argv, l'adresse de la fonction qui recevra le contrôle une fois le processus terminé. Vous devez également définir les drapeaux du processeur à la valeur dont vous avez besoin, par exemple, pour mon système d'exploitation, c'est 0x216, vous pouvez lire sur le registre des drapeaux sur Wikipedia.

En fin de compte, je veux vous souhaiter beaucoup de succès, je vous écrirai bientôt sur la mémoire et d'autres articles qui vous intéressent.
Bonne chance et piratage éthique!

Source: https://habr.com/ru/post/fr426807/


All Articles