Projet système PC : 2021 - HOUDE Elina, SOLO KWAN Léa

De Ensiwiki
Aller à : navigation, rechercher
Banner - Projet Systeme.png
Titre du projet Projet système PC : 2021 - HOUDE Elina, SOLO KWAN Léa
Cadre Projet système

Équipe Elina HOUDE Léa SOLO KWAN
Encadrants Yves Denneulin , Gregory Mounie, Patrick Reignier

Présentation

Dans le cadre du projet système, vous trouverez ici le détail de la réalisation de cet OS "HypnOSe" développé par Elina HOUDE et Léa SOLO KWAN via :

  • un journal de bord
  • une description de l'organisation
  • un récapitulatif, pour chacune des phases, des étapes critiques de la phase et des points de vigilance (conseils).

Équipe

Elina HOUDE

Léa SOLO KWAN

Organisation

Toutes les phases ont été implémentées en pair-programming. Des commits ont été effectués à chaque fois qu'une fonctionnalité ou une modification majeure était réalisée.

Phases du développement

Phase 1 : Prise en main de l'environnement

100 %

Après lecture de la documentation fournie, nous avons installé l'environnement de développement et l'avons pris en main. Puis, en suivant les consignes de la séance 1 de PSE, nous avons géré l'affichage à l'écran via l'implémentation de la fonction printf.

Phase 2 : Gestion de la notion de processus, du changement de contexte entre deux processus (au niveau noyau) et du timer via les interruptions

100 %

Après lecture de la documentation fournie sur Ensiwiki, nous avons implémenté dans un premier temps la gestion des processus en suivant ce TP PSE. Ensuite, dans un second temps, nous avons implémenté le timer et la gestion des interruptions en suivant ce TP PSE. Cette seconde étape a été un peu plus compliquée car nous avions l'habitude de réfléchir avec des variables de type uint16_t et non uint32_t : cela a donc été l'occasion d'initialiser dans le makefile le déboguage et d'utiliser cette fonctionnalité. Au final, nous avons mis 2 jours pour implémenter ces fonctionnalités.


Primitives systèmes implémentées à l'issue de cette phase :

    current_clock

Phase 3 : Gestion du cycle de vie d'un processus

100 %

Durant cette troisième phase, nous avons implémenté qautre étapes clés du cycle de vie d'un processus :


1. Ordonnancement des processus par l'ordonnanceur

Cette étape vise à choisir le prochain processus élu. Dans un premier temps, nous avons décidé d'implémenter une sélection de processus selon les priorités avec choix du processus, à priorités égales, selon le principe du tourniquet. Dans un second temps, nous avons implémentés un ordonnanceur selon les priorités des processus via la création d'une file d'attente avec queue.h (à priorités égales, le processus élu est choisi selon un ordre FIFO).

⇒ Ressources : TP PSE sur l'ordonnancement


2. Création dynamique des processus

Cet étape a pour but de permettre l'allocation et la réallocation de processus via la primitive start. Lors de cette étape, il a fallu faire attention à deux points particuliers. Premièrement, nous avons décidé de désallouer la structure d'un processus dès qu'il mourrait. Cependant, on peut pas désallouer le processus courant sans générer de problèmes. Nous avons donc finalement implémenté une fonction de nettoyage appelée dans ordonnance() et chargée de désallouer les structures des processus morts (excepté celle du processus courant). Deuxièmement, nous avons mal compris les spécifications du sujet : nous pensions que le paramètre ssize passé lors de l'appel à la fonction start correspondait à la taille de la pile allouée côté kernel alors qu'il s'agissait de la taille à allouer pour la deuxième pile côté user. Nous avons donc perdu du temps sur ce point.

⇒ Ressources : Démarrage d'un nouveau processus


3. Endormissement des processus

Cette étape consiste à rendre l'ordonnancement préemptif, c'est à dire permettre à l'horloge d'interrompre un processus en cours d'exécution pour donner la main à un autre. Nous avons profité de cette étape pour implémenter la primitive wait_clock afin d'endormir un processus pendant un temps déterminé.

⇒ Ressources : TP PSE sur l'endormissement


4. Terminaison des processus

Cette étape a pour but d'implémenter les différentes manières dont un processus prend fin, c'est-à-dire via un retour de fonction (fin normale de la fonction, utilisation d'un return; ou utilisation d'un return int;), via la fonction exit(int) ou via la fonction kill(pid). Pour cela, il a été nécessaire de bien comprendre le fonctionnement de la pile afin de configurer l'adresse de la fonction de terminaison, et récupérer la valeur de retour du processus via quelques lignes de code en assembleur (placement dans la pile de la valeur de retour de la fonction disponible dans le registre %eax).

⇒ Ressources : TP PSE sur la terminaison


5. Filiation des processus

Nous avons profité de cette étape pour implémenter la primitive waitpid.


A l'issue de ces quatre étapes, nous avons corrigé et validé notre implémentation via l'exécution des tests user 1 à 9 après les avoir préalablement copier/coller dans le dossier kernel.


Conseils :

Lors de cette étape, il est important de bien lire au préalable les spécifications sur les processus. De plus, cela est une bonne idée d'adopter un développement piloté par les tests afin de s'assurer de la bonne compréhension des spécifications. Nous avons procédé de cette manière et cela nous a permis de compléter les spécifications et de compléter les cas d'utilisation que nous n'avions pas envisagés.

De plus, à l'issue de cette étape, les tests 1 à 9 doivent passer.


Primitives systèmes implémentées à l'issue de cette phase :

   1. getprio, chprio, getpid
   2. start
   3. wait_clock, clock_settings
   4. exit, kill
   5. waitpid

Phase 4 : Gestion des files de messages

100 %

Pour cette phase, nous avons commencé par implémenter les primitives systèmes pcreate et pdelete. Puis, nous avons implémenté simultanément les primites systèmes psend et preceive car leurs comportements sont étroitement liés : il est nécessaire de refléchir à l'implémentation de l'une en gardant à l'esprit l'implémentation de l'autre et vice versa.

Conseils :

- A l'issue de cette phase, les tests 11 à 17 et 20 doivent passer.

Ressources :

Spécifications sur les files de messages

Primitives systèmes implémentées à l'issue de cette phase :

    pcreate, pdelete
    psend, preceive
    pcount
    preset

Phase 5

100 %

Cette partie a été fastidieuse. Heureusement il y avait de nombreux wiki bien documentés pour nous aider à comprendre cette partie et nous guider sur les démarches à suivre.

1) Tout d'abord il faut implémenter le passage en mode utilisateur pour cela nous avons :

  • Créer la pile user et modifié la pile kernel des processus (noter que le processus idle n'a pas de pile user ).
  • Pile user : arguments, fonction de retour (à empiler dans cet ordre en commençant par l'adresse la plus haute)
  • Pile kernel : USER_SS, le sommet de la pile utilisateur, EFLAGS en mode user, USER_CS, fonction à éxecuter, fonction assembleur switch_to_user_mode (à empiler dans cet ordre en commençant par l'adresse la plus haute)
  • implémenté la fonction user_start() coté user et crée un processus appelant cette fonction dans idle ( cote kernel )
  • Créer la fonction en assembleur switch_to_user_mode : initialise les regitres %ds, %es; %fs, %gs à la valeur %ss ( 0x4b) avant de faire iret

2) Ensuite nous avons géré l'interruption 49 et les appels systèmes : Cote kernel :

  • implémentation de l'initialisation du traitant 49 ( /!\ DPL 0xEE00)
  • implémentation de la fonction IT_49 réalisant le traitant ( gestion des appels systèmes dans le kernel via un switch case sur le numéro de l'appel donné par user)
  • fonction assembleur gérant IT 49 : sauvegarde des registres importants (%edi,%esi,%edx,%ecx,%ebx, %eax ), initialisation des registres %ds, %es, %fs, %gs à la valeur %ss ( 0x18 vers kernel), appel à la fonction C réalisant le traitant ( IT_49 pour nous ), initialisation des registres %ds, %es, %fs, %gs à la valeur %ss ( 0x4b vers user), restauration des registres important %ebx ( on ne récupère pas %eax mais on le dépile quand même ),%ebx, %ecx, %edx, %esi, %edi , iret.

Côté user :

  • Gestion des appels systèmes pour les fonctions ecrites en kernel
  • fonction assembleur gérant les appels systèmes : sauvegarde des registres importants ( %ebx, %ecx, %edx, %esi, %edi ), passage de paramètres appel dans registres , éxécution de l'interruption 49; restauration des registres important ( sauf %eax ), ret.
  • gestion retour interruption : appel de exit dans crt0.S, ajout de exit aux appels systèmes, ajout dans user_stack de l'adresse de la fonction de retour hardcodé (0x1000005)

3) Gestion de la TSS : donner à la TSS l'adresse du haut de la pile du processus élu avant le ctx_sw

Nous avons finis par gérer la sécurité avec le passage du test18.


Ressources :

- Nos camarades ayant terminés cette phase avant nous et leur wiki ( et les ressources citées dans leur wiki ): Emma et Morgane

- Ce diaporama

Phase 6

0 %

Phase 7

0 %

Extensions

Journal de bord

Semaine 1

Lundi 7 juin

⇒ Phase 1

  • prise en main de l'environnement de développement
  • gestion de l'affichage à l'écran (printf)

Mardi 8 juin

⇒ Phase 2

  • Création du type processus et de la table des processus
  • Implémentation du context_switch
  • Gestion de l'ordonnancement selon le principe du tourniquet

Mercredi 9 juin

  • Lecture de la documentation sur le timer et les interruptions systèmes pour l'horloge
  • Ecriture du traitant de l'interruption 32 (affichage du temps système) via traitant.S et la fonction tic_PIT

Jeudi 10 juin

  • Initialisation du traitant 32
  • Réglage de la fréquence pour l'horloge
  • Démasquage/masquage des interruptions

⇒ FIN Phase 2

⇒ Phase 3

  • Endormissement des processus (wait_clock) et gestion du réveil
  • Implémentation de current_clock

Vendredi 11 juin

  • Implémentation d'une fonction start pour gérer la création des processus (sans la gestion de la taille dynamique de la pile et sans la récupération d'un argument à passer à la fonction appelée par le processus)
  • Gestion de l'ordonnancement selon les priorités avec choix du processus, à priorités égales, selon le principe du tourniquet


Semaine 2

Lundi 14 juin

  • Gestion de la terminaison des processus via une fonction terminaison en C (cas des fonctions de type de retour void)
  • Implémentation des liens de filiation entre processus (père et fils)
  • Gestion de la filiation dans la fonction de terminaison (mort du père avant fils, mort du fils avant le père)

Mardi 15 juin

  • Gestion de la terminaison des processus via kill et exit
  • Création de la fonction waitpid pour attendre un fils précis ou n'importe quel fils

Mercredi 16 juin

  • Modification de la gestion de l'ordonnance pour les priorités via la création d'une file d'attente avec queue.h : à priorités égales, le processus élu n'est plus choisi selon le principe du tourniquet mais selon un ordre FIFO

Note : L'élément élu n'est pas présent dans la file d'attente lors de son exécution. Seuls les processus non terminés (état différent de ZOMBIE et booléen is_alive à True) sont présents dans la file d'attente.

  • Implémentation de la possibilité de passer un argument à la fonction exécutée par un processus

⇒ FIN Phase 3

⇒ Phase de test via les tests donnés (tests 1 à 9) , de réfractor et de mise en adéquation avec la spécification

  • Test du wait_clock implémenté en phase 3
  • Configuration du projet pour exécuter les tests donnés dans user

Jeudi 17 juin

  • Implémentation du return int pour la terminaison d'un processus
  • Mise en adéquation avec la spécification :
    • numéro de pid à partir de 1 et non 0 et numéro de priorité 0 invalide
    • kill et chprio : gestion du cas ou le processus est ZOMBIE ou mort
    • Election d'un processus à sa création si sa priorité est supérieure à celle de son père
  • Implémentation de la réallocation du pid d'un processus mort
  • Implémentation de l'allocation dynamique de la pile d'un processus

⇒ Tests 1 à 6 et 9 : OK

Vendredi 18 juin

  • Implémentation de la fonction clock_setting permettant d'obtenir les informations sur l'horloge

⇒ Test 7 : OK

  • Réfractor :
    • Appel de ctx_sw seulement dans ordonnance()
    • Supression de l'allocation de la pile kernel en fonction de ssize : cela devait être le cas pour la pile user et non kernel
    • Factorisation de la désallocation des processus dans une fonction nettoyage appelée dans ordonnance()

⇒ Test 8 : OK

⇒ FIN Phase de test via les tests donnés (tests 1 à 9) , de réfractor et de mise en adéquation avec la spécification

⇒ Phase 4

  • Implémentation des fonctions de création et de suppression des files de messages (pcreate et pdelete)


Semaine 3

Lundi 21 juin

  • Implémentation des fonctions d'envoi, de réception et de remise à 0 d'une file de messages (psend, preceive et preset)
  • Implémentation de la fonction d'établissement des statistiques d'une file de messages (pcount)
  • Pour les files de messages, fusion des files d'attente waiting_queue_in et waiting_queue_out en une seule file d'attente waiting_queue
  • Ajout d'un champ message à la structure d'un processus afin de transmettre les messages immédiatement aux processus concernés dans certains cas d'utilisation des fonctions preceive et psend et modification de ces deux dernières fonctions afin de permettre la transmission immédiate de ces messages
  • Correction de la fonction clock_settings
  • Ajout de la libération immédiate du processus attendu lors du waitpid
  • Ajout de vérification pour la primitive pcreate : vérification de la taille disponible dans la pile pour l'allocation de la file de messages et vérification de l'absence de débordement pour la variable count donnée en paramètre de pcreate

⇒ Tests 1 à 17 et 20 : OK

⇒ FIN Phase 4

⇒ Phase 5

Mardi 22 juin

  • Début de la phase 5 avec implémentation du passage en mode user :
 - Modification de idle qui crée un processus qui appel la fonction user_start()
 - Création de la pile user pour un processus et modification de la pile kernel
 - Implémentation de la fonction user_start() côté user
 - Implémentation de la fonction assembleur switch_to_user_mode permettant de passer en mode user
  • Gestion de l'interruption 49 côté user et kernel
  • Gestion des appels systèmes avec arguments

Mercredi 23 juin

  • Gestion du retour pour l'interruption 49
  • Ajout de la sauvegarde du haut de la pile dans la TSS
  • Ajout d'une fonction de terminaison hardcodé et ajout de l'adresse de cette fonction cote kernel ( pile user )
  • Implémentation de la fonction cons_write ( appel de console_putbytes)
  • Corrections de bugs : désallocation de la pile user, définition de la TSS et de l'allocation de la pile kernel de idle

⇒ Tests 1 a 7, 9 à 17 et 20 : OK

Jeudi 24 juin

  • Correction du type de retour de la fonction start côté user
  • Correctif dans waitpid (double désallocation possible en fonction du coup d'horloge)
  • Ajout de la vérification de l'adresse des pointeurs donnés en argument pour les appels systèmes demandés par l'utilisateur

⇒ Tests 1 à 18 et 20 : OK