Projet système PC : 2018 - ALMEIDA DA SILVEIRA Leonardo, BENAVIDES GARZON Carlos Eduardo, BINOTTO BRAGA Amanda, SOARES RIBOLLI Ricardo, TOMASI RIBEIRO Eduardo, VICTORIA Miguel Alfredo, YU Miaoyun

De Ensiwiki
Aller à : navigation, rechercher
RemolachOS.png
Titre du projet Projet de Conception de Système d'Exploitation - 2018
Cadre PCSEA-2018

Équipe ALMEIDA DA SILVEIRA Leonardo
BENAVIDES Carlos
BINOTTO Amanda
SOARES Ricardo
TOMASI Eduardo
VICTORIA Miguel
YU Miaoyun

Présentation

RemolachOS est le nouveau système d'exploitation qui a été créé afin de satisfaire les exigences des plus ambitieux utilisateurs Unix. Sa performance et sa politique d'ordonnancement de tâches permettent à l'utilisateur de se régaler pendant que l'OS fait tout le travail.

Avancement du projet

Phase 1: Prise en main de l'environnement

100 %

Phase 2: Création et lancement de processus de niveau noyau

100 %

Timer

  • C'est l'implementation des fonction clock_settings, current_clock et wait_clock. Au pas que les deux premières sont triviales, dans la fonction wait_clock il faut changer le status de processus en actif quand le processus est initialement dans l'état endormi et aussi son temps d’endormissement est atteint ou dépassé. Après cela, il faut trouver un processus pour être actif dans son lieu.

Phase 3: Ordonnancement, création dynamique et terminaison de processus de niveau noyau

100 %

Cette étape consiste à gérer tout ce qui concerne la création des processus, leur endormissement, leur terminaison, leur ordonnancement et bien sûr leur filiation. Pour réussir à le faire, les primitives principales ont été implémentés.

Quelques choix importantes d’implémentation ont été pris pendant cette phase. En particulier, nous avons décidé d’avoir une liste chaînée de processus en train de finir, pour éviter d’avoir des problèmes à l’heure de faire le changement de contexte après avoir fini un processus. De cette manière, la libération de la mémoire associée a ce processus est faite pendant l’appel à l’ordonnanceur.

Pour pouvoir implémenter le waitpid, une liste de fils a été associée à chaque processus et chaque processus possède un pointeur à son père, de cette façon lors d’une terminaison, le fils peut débloquer son père s’il est en train de l’attendre. Quand le père reprend la main du processeur, il peut prendre la valeur de retour du fils qui l’a débloqué et il peut continuer avec son exécution.

Dans ce point, les testes ont été vraiment importantes parce qu’ils ont permis d’éclaircir quelques fonctionnalités qui nous n’avions pas bien compris pendant la lecture des specs, par exemple, l’action à prendre pendant la création d’un processus fils avec une priorité plus grande, ou si les processus peuvent se suicider (faire kill sur eux-même). Pour pouvoir les utiliser, il a été nécessaire de les copier sur la partie kernel et faire quelques modifications.

Phase 4: Gestion des communications et synchronisation de processus de niveau noyau

100 %

Pour la file de messages, on a commencé avec un file global de queues où on va créer dynamiquement les files selon besoin. On a créé ainsi une structure de données où on sauvegarde, pour chaque file: sa capacité, la quantité de messages qu'elle a, la liste de processus bloqués qui veulent envoyer, la liste de processus bloqués qui veulent recevoir, la file de messages et deux pointeurs *read et *write pour savoir où il faut commencer à lire et à écrire.

Dans la suite, nous relevons ici les points importants que nous avons bien fait attention au moment de l'implémentation.

Pour la fonction psend, il faut traiter le cas de queue vide (débloquer processus pour envoyer messages) et le cas de queue pleine (bloquer processus).

Pour la fonction preceive, il faut traiter les cas de queue vide.

Pour la fonction pcreate, il faut faire attention au cas où la file global de queues est pleine (on ne peut pas créer une autre file dans ce cas).

Pour la fonction pdelete, il faut débloquer les processus qui sont bloqués dans le files de la structure avant de les supprimer.

Phase 5 : Séparation des espaces mémoire noyau et utilisateur : gestion de processus utilisateur

95 %
  1. Protection Mémoire
    1. Cette partie était un peu obscure et nous a pris beaucoup de temps pour comprendre. Les spécifications sur ensiwiki ne sont pas suffisantes et il faut trouver certaines informations ailleurs. On a beaucoup utilisé, par exemple, les pages des groupes des autres années (surtout cette page ) et OSDev.
  2. Appels Système
    1. Fournir à l'espace utilisateur la possibilité d'appeler le noyau (à l'aide d'interruptions logicielles): Dans le fichier tests/lib/sysappels.h, il y a la liste des fonctions qu'il faut implémenter. C'est important de faire attention si le travail a été fait avec files de messages ou semaphores (il faut implémenter soit un soit autre). Dans un fichier assembly, il faut avoir un traitant pour chaque fonction à} être implémenté côté kernel. Chaque assembly doit, d'abbord, sauvegarder les registres du processus (on veut retourner au même point après l'appel système). Après, il faut donner un numéro pour identifier chaque appel système (kernel va utiliser ce numéro pour savoir quel est l'appel) et mettre dans les registres les paramètres de la fonction. Je conseille de ne pas utiliser eax pour mettre de paramètres/número de l'appel car il va être utilisé pour mettre le retour de l'appel. Faire attention à la position des paramètres dans la pile. Dans context switch, on peut voir où les paramètres d'un appel généralement sont. Après, il ne faut pas oublier que plusieurs pushes ont été fait pour sauvegarder des registres dans le traitant.
    2. Permettre au noyau de recevoir les interruptions des applications pour traiter leur requêtes: Pour cela, on a dû créer un traitant de l'interruption 49 et bien l'insérer au vecteur d'interruptions au démarrage du système. Ce traitant prépare les registres ds, es, fs et gs pour le changement de mode, push les autres registres (sauf eax puisqu'il sera utilisé pour la valeur de retour) et appelle une fonction C. Cette fonction a pour but de récupérer le numéro de l'appel système (de la pile), ses paramètres (de même), et ensuite appeler la bonne fonction (qui est implémentée côté kernel). S'il y a une valeur de retour de la fonction appelée, elle sera dans eax.
  3. Protection d'Execution

Phase 6 : Gestion du clavier et implémentation d'un pilote de console

100 %

Cette étape consiste à implémenter un pilote de clavier en utilisant les interruptions de ce périphérique afin qu'il soit capable de gérer les entrées sorties de la console de la phase 7. Nous avons réussi à réutiliser les codes des phases antérieures en les adaptant aux besoins que nous avions dans cette étape. Par exemple, nous avons pris la structure de données file de message de la phase 4 afin d’implémenter le tampon du clavier.

Nous avons aussi développé la fonction kbd_leds afin que l’affichage des leds du clavier soit cohérent avec l’OS, pourtant nous n’avons pas encore testé cette fonctionnalité.

Pendant cette phase de développement nous avons beaucoup testé étant donné qu’elle fait l’intégration des étapes précédentes avec la partie utilisateur. En conséquence, nous avons beaucoup itéré entre la phase de teste et codage afin de rendre plus agréable l’expérience utilisateur. Par exemple, un petit détail qui peut influencer positivement c’est que l’utilisateur ne puisse pas effacer au-delà de la ligne de console dans laquelle il est en train d’écrire.

Phase 7 : Implémentation d'un interprète de commandes

100 %
Commandes OS réalisées
Commande Effect Appel système?

help

Afficher les commandes disponibles

non

echo

Afficher un string sans guilletmet ou entre guilletmet

non

color

Changer de couleur de texte

non

exit

Sortir de la console

oui

ps

Afficher les processus

oui

test

Lancer tous les tests

oui

history

Afficher les commandes récemment tapées

non

Dans cette phase, nous devons réaliser une console qui peut interpréter les commandes que l'utilisateur tape au clavier. Afin de la réaliser, à l'aide de la phase 6, nous avons dû pouvoir récupérer les commandes et ensuite les analyse dans cette phase, donc l’interprétation des commandes est la partie la plus importante dans cette phase. Nous avons séparé ce travail en quelques sous-parties:

  • Première phase
Nous avons réalisé une fonction qui sépare les tokens dans la ligne de commande: char **split_cmd_line(char* line, unsigned long* param_length)
  • Deuxième phase
Nous avons réalisé une fonction qui extrait des tokens la commande et une fonction qui extrait des tokens les paramètres présentes dans la ligne de commande: char* extraire_cmd(char **tokens) et char** extraire_param(char **tokens, unsigned long* length)
  • Troisième phase
Nous avons fait un bulletin des commandes disponibles: static int builtin_command(char* cmd, char** param, unsigned long* param_length)
  • Quantième phase
Nous avons réalisé une écran d'accueil et le prompt de console: static void welcomeScreen() et static void type_prompt()
  • Cinquième phase
Nous avons fait une boucle infinie qui regroupe tous les fonctions précédentes afin de lire et interprète les commandes l'une après l'autre: int miniShell()

Journal de bord

  • Première semaine
    • Implémentés les primitives kill, getprio et chprio. Ajout de nouveaux champs à la structure du processus pour supporter la filiation et commencé à coder la primitive waitpid.
    • Nous avons implémenté la primitive waitpid. :)
    • Nous avons fini la création de structure de File de messages, et les fonctions pcreate et pdelete.
  • Deuxième semaine
    • Adaptés les premiers fichiers de test pour les utiliser dans le mode noyau.
    • Nous avons implémenté tous les fonctions de la Phase 4
    • Déboggage de la phase 3 avec l'aide des fichiers de test.
  • Troisième semaine
    • Réglé des autres aspects de la gestion de processus qui ne suivaient pas les spécs.
    • Fait le merge de la Phase 3 et la Phase 4 pour permettre de débogguer la dernière.
    • Commencé la Phase 5.
    • Changé la façon dans laquelle les processus sont tués et ultérieurement nettoyés, et finalement fini la Phase 3.
    • Nous avons commencé l'implémentation d'un fichier pour tester la Phase 4.
    • Déboggage des premières fonctions de la Phase 5 pour allouer un espace de mémoire.
  • Quatrième semaine
    • Commencé à lire la documentation pour implémenter le mapping de la mémoire virtuelle.
    • Fini l'allocation et libération de mémoire (y compris le merge de blocs libres).
    • File de message est opérationnelle.
  • Cinquième semaine
    • Première version de Malloc.
    • Testé la phase 4.
    • Mapping de la mémoire commencé
  • Sixième semaine
    • L'implémentation du directoire de pages commencée.
    • Deboggage et test de la Phase 5.
  • Septième semaine
    • Réglé l'alignement mémoire.
    • Directoire de pages a été ajouté.
    • Implémentation de la phase 6 commencée.
    • Implémentation des appels systèmes commencée.
    • Interruptions du clavier sont opérationnelles.
  • Huitième semaine
    • Commencé le mode utilisateur.
    • La Phase 6 était en train d'être testée.
    • Commencé la Phase 7 du interpréteur de commandes.
  • Neuvième semaine
    • Réglés les valeurs de retour de la Phase5.
    • Finir les Phase 6 et Phase 7

Difficultés rencontrées

  • Phase 4:
    • Pendant cette phase on avait déjà la file de messages opérationnelle, mais pendant la période de test nous nous sommes rendu compte qu'il y avait des problèmes quand on essayait d'effacer et redémarrer plusieurs fois la structure. Le problème était qu'on gérait mal la allocation dynamique, en conséquence les fuites de mémoire étaient de telle magnitude qu'on ne pouvait plus allouer de la mémoire.
  • Phase 5:
    • C'était la phase où nous avons eu le plus de problèmes. C'était aussi la phase la plus difficile de comprendre et celle que nous a pris le plus de temps pour faire. Un problème que nous a bloqué pendant beaucoup de temps c'était l'alignement de la mémoire, toutes les adresses qu'il faut utiliser doivent être alignées sur 4K.
    • Pendant la séparation du mode user et kernel on a aussi trouvé une difficulté avec la méthode appelée pour finaliser un processus, parce qu'il fallait la mettre dans la pile du processus sur le mode kernel avant son initialisation. Mais cette méthode devrait être appelée sur le mode user, ce qu'il fallait donc un appel système pour la finaliser. On a résolu ce problème on créant, dans la pile du processus, une partie où était le code de l'appel système de finalisation.
    • Nous avons eu aussi un problème avec la modification du registre cr3, parce qu'il ne faut pas modifier le fichier ctx_sw.S, donc on a crée un nouveau fichier pour la faire.

Sources externes

  • Linux Kernel Linked List Explained : Ce lien va vous rediriger vers un exemple d'utilisation du fichier list.h du kernel Linux. Cet exemple nous a beaucoup aidé pour comprendre l'utilisation du fichier fourni queue.h.