Projet système PC : 2016 - Maxime HAGENBOURGER, IJ, Rémi SAUREL

De Ensiwiki
Aller à : navigation, rechercher
Succulentlogo.jpg
Titre du projet succulent.
Cadre Ensimag
Page principale Projet_système

Équipe Maxime Hagenbourger Rémi Saurel, IJ
Encadrants Sébastien Viardot Grégory Mounié

Présentation

succulent. est un système d'exploitation développé par Maxime Hagenbourger, Rémi Saurel et IJ dans le cadre du projet système de fin de deuxième année d'apprentissage. Celui-ci permet de mettre en place les concepts développés pendant le cours de SEPC de deuxième année.

Prise en main rapide

  • Changer de TTY: F1/F2/F3
  • Liste des commandes: help
  • Lancer la démo sans son (qemu): demo
  • Lancer la démo avec son (virtualbox): demo s
  • Si vous utilisez des commandes qui manipulent des fichiers, les noms doivent être donnés en majuscules (exemple: bassdrop SPACEDEB.MOD)

Origines

Tous trois férus d'anglais, il arrive que nous recherchions la prononciation de divers mots grâce à Merriam-Webster tels que process, assert... Ce jour-là, la prononciation du mot "succulent" était mise en exergue. C'était alors une évidence : nous devions nommer notre OS succulent. ! Attention, la prononciation se fait en anglais et non en français. Pour connaître la bonne prononciation, rendez-vous ici !

Cahier des charges

Le cahier des charges initial est présent sur cette page. Sur ce wiki, notamment en-dessous, vous pouvez trouver les extensions et les fonctionnalités supplémentaires de succulent.

Objectifs

Nos objectifs principaux étaient les suivants :

1. Finir toutes les phases en faisant passer l'ensemble de nos tests et ceux fournis que ce soit en mode kernel ou en mode user.

2. Proposer une démonstration impressionnante.

3. Développer le plus d'extensions possibles avec comme priorité le système de fichiers.

Nous estimons les deux premiers objectifs remplis et le troisième bien entamé. Nous avons développé quelques extensions intéressantes (détails plus loin) dont le système de fichiers FAT16. Aussi, certaines fonctionnalités qui nous tenaient à coeur sont présentes, comme le son et le lecteur de GIFs.

Gestion de projet

Pour conduire succulent. de la meilleure des façons, nous avons utilisé quelques outils pour augmenter notre productivité.

Outils

Mattermost

Mattermost est un clone libre de Slack. Il permet de nous synchroniser en partageant sur un chat accessible sur toutes les plate-formes ou aussi partager des fichiers si nécessaires, liens, images... Comme l'ensemble du travail n'était pas effectué à l'école, cela a pu permettre aux membres de l'équipe de s'y retrouver après la fin de journée à l'Ensimag.

Git

Imposé par le cahier des charges mais aussi très utile, nous avons utilisé un dépôt Git mis à disposition par l'école. Il nous a permis de travailler en parallèle sur plusieurs parties du projet et pouvoir revenir sur certaines modifications qui se sont prouvées néfastes par la suite.

Planning

Planning prévisionnel

A la naissance de succulent., nous avions prévu d'allouer un maximum de temps pour la phase 5, étant la phase la plus complexe du projet. Nous n'incluons pas non plus les week-ends dans le diagramme, afin de pouvoir en cas de majeur obstacle, allouer du temps de travail supplémentaire pendant les jours normalement non travaillés. La majeure difficulté dans ce planning est de ne pas avoir de bugs trop problématiques avant de pouvoir entamer la phase 5. Ce planning est ambitieux mais faisable.

Voici le planning prévisionnel du projet :

Ganttpre.jpg

En réalité, le planning a été majoritairement respecté. La phase 3 a pris légèrement plus de temps que prévu, mais la 4, comme la 5, a été bouclée rapidement. Plus de détails ci-dessous.

Planning effectif

Voici le planning effectif du projet :

Finalganttsucculent.png

Les deux plannings diffèrent plutôt : à la fin de la phase 5, nous avons pu paralléliser la démonstration et le début des extensions avec les phases 6 et 7, celles-ci ne demandant pas trop de travail. Ceci nous a permis de passer le plus de temps possible pour peaufiner succulent. mais aussi y intégrer un maximum de fonctionnalités non prévues dans le cahier des charges.

Concernant le reste du projet, à part la phase 3 qui a pris légèrement plus de temps, la 4 et 5 un peu moins, les prévisions ont été respectées. Aucune phase nous a ralenti particulièrement demandant une refonte totale du planning.

Détails du projet

Comme l'indique la conduite du projet, le développement de succulent. est divisé en 7 phases. La rubrique présente va décrire le déroulement de chacune des phases.

Phase 1

Durée prévue : 1 jour. Durée effective : 0,5 jour.

Objectifs :

  • Affichage écran
  • Import du code de première année

Cette phase consiste à importer le code développé en première année en cours de logiciel de base pour assurer les opérations basiques d'affichage à l'écran : affichage de texte, changement de couleur de la police affichée, changement de couleur du fond d'écran... Cette partie du projet ne comporte aucune difficulté majeure et a été réalisée entièrement dans la première matinée du projet.

Phase 2

Durée prévue : 1 jour. Durée effective : 1 jour.

Objectifs :

  • Changement de contexte
  • Gestion de l'horloge
  • Traitement des interruptions

Cette étape du projet comporte plusieurs fonctions importantes comme décrites ci-dessus. Elle comporte peu de difficultés mais est cruciale pour la suite du projet. Un bon partage des tâches nous a permis de réaliser cette phase en une seule journée.

Cette dernière n'a pas posé de problème significatif. Cela dit, il était important de bien mettre ces fonctionnalités en place pour éviter des bugs plus dérangeants par la suite.

Phase 3

Durée prévue : 2 jours. Durée effective : 3 jours.

Tâches:

  • Ordonnanceur
  • Gestion terminaison
  • Création dynamique des processus
  • Gestion filiation

La phase 3 est consacrée à l'implémentation de l'ordonnanceur, la gestion de la terminaison des processus, leur création dynamique ainsi que la gestion des liens de parenté entre ceux-ci. L'ordonnanceur est la pièce maîtresse de succulent. Elle doit être développée sans le moindre bug et être testée le plus possible.

A l'origine du retard sur cette phase, une incompréhension sur l'utilisation des appels système sti et cli qui causaient de nombreux problèmes lors de l'exécution des tests, notamment des interruptions au mauvais moment et de façon irrégulière. Cela nous a fait perdre une journée entière.

Phase 4

Durée prévue : 2 jours. Durée effective : 1 jour.

Objectifs :

  • Files de messages

Nous avons pu rattraper le retard engendré par la phase 3 en implémentant la phase 4 plus rapidement que prévu. Le développement des files de messages s'est déroulé sans grands encombres. Cela dit, quelques bugs sont apparus au fur et à mesure de l'évolution de la phase suivante et de l'exécution des tests fournis.

Phase 5

Durée prévue : 4 jours. Durée effective : 3 jours.

Objectifs :

  • Séparation kernel/user
  • Appels système

Cette phase est le noeud du projet. Il faut séparer le mode kernel et le mode user. Grâce à une bonne présentation des différentes difficultés et des pistes pour les surmonter, nous avons pris moins de temps que prévu pour finir cette partie. Il était important de comprendre comment séparer les piles par processus, les registres à empiler, comment le faire et gérer la terminaison des programmes en mode user.

Nous sommes très satisfaits d'avoir pu démêler ce noeud si rapidement. Nous sentions que c'était la phase critique qui pouvait nous empêcher de progresser. Après cela, nous avons pu séparer les tâches et laisser quelqu'un travailler entièrement sur les extensions et la démonstration.

Phase 6

Durée prévue : 1 jour. Durée effective : 1 jour.

Objectifs :

  • Gestion entrées/sorties clavier

Cette phase consiste à l'intégration du clavier dans notre système d'exploitation. Cette partie n'étant pas très complexe, elle a pris, comme prévu, qu'une seule journée. Cette partie est intéressante pour bien saisir les entrées/sorties dans succulent.

Phase 7

Durée prévue : 1 jour. Durée effective : 2 jours.

Objectifs :

  • Implémentation du shell demandé
  • Implémentation de commandes supplémentaires pour notre utilité

Le shell a été implémenté en se basant sur le code source du TP de SEPC. Le fichier readcmd.c a en particulier été adapté, afin de simplifier l'analyse des commandes. Cette adaptation a nécessité des modifications dans le fichier malloc.c.h, notamment pour implémenter la fonction mem_realloc. Parmi les fonctionnalités du shell on peut noter le support des processus en tâche de fond.

Diverses commandes ont été développées (dont la liste est accessible via la commande 'help'). Certaines permettent par exemple l'analyse du fonctionnement du système d'exploitation ('ps' pour les processus, 'mem' pour l'allocation dynamique de mémoire, 'queues' pour les files de messages).

Extensions

Système de fichier FAT16 en mémoire

Un support basique du système de fichier FAT16 a été implémenté. Les fonctionnalités disponibles sont la lecture de fichiers dans le répertoire racine de l'image disque, ainsi que leur énumération. 3 appels systèmes ont été implémentés (fsize, fread, scandir), et les commandes 'ls' et 'cat' permettent d'interagir avec les fichiers. D'autres fonctionnalités plus avancés tirent également parti du système de fichier, tels que le lecteur d'images 'gif' ou le lecteur musical 'bassdrop'.

L'implémentation du FAT16 est relativement simple. Une des principales difficultés au début est de bien comprendre la différence entre les notions de secteur, de bloc et d'unité d'allocation. Voici quelques ressources qui nous ont été utiles lors de l'implémentation :

Terminaux multiples

3 terminaux sont disponibles à l'utilisateur, aisément accessibles à l'aide des touches F1/F2/F3 du clavier. Ainsi, il est par exemple très facile de lancer son lecteur musical préféré dans l'un d'entre eux, tout en continuant son travail à côté. À noter que le premier terminal (tty0) est également redirigé vers le port série de l'ordinateur.

SoundBlaster 16

succulent. comprend un driver basique pour SoundBlaster 16 en mode auto-init DMA, c'est-à-dire où on doit continuellement répondre à une interruption venant de la carte son pour lui donner des données à lire. En d'autres termes, on streame du son.

Nous avons utilisé le mécanisme de file de messages pour permettre d'écrire le streamer en user. À l'initialisation de la carte son, qui est obligatoirement dans le kernel car on touche directement au matériel, on crée une file de messages. Dans le traitant d'interruption, on fait preset sur cette file, ce qui réveille le(s) processus user se chargeant de streamer, leur indiquant ainsi que la carte est prête à recevoir des nouvelles données. On a choisi de faire preset plutôt que psend pour éviter des blocages sur un processus qui n'a rien à voir avec le son.

Note : l'émulation SB16 de qemu est buggée. Nous vous conseillons vivement d'utiliser VirtualBox pour tester le son.

Démonstration

Beaucoup de temps a été investi pour promouvoir le mieux possible notre système d'exploitation. Une démonstration avec comme égérie notre puits d'inspiration Cristina Cordula a été développée. Elle permet de mettre en évidence le multithreading, le changement de contexte, les files de messages, en bref, toutes les fonctions critiques de succulent. Aussi, elle utilise le système de fichiers FAT16, l'implémentation du driver SoundBlaster 16 (le son est streamé en user mode et non dans le kernel) mais aussi un lecteur de GIFs (les GIFs étant présents sur le système de fichiers). La résolution de la présentation est 320*200*8.

Spec des syscalls supplémentaires

Graphisme

gmode

int gmode(int mode)

Bascule entre le mode graphique et le mode texte. Si on passe en mode texte, la palette est sauvegardée et sera restaurée quand on repassera en mode graphique.

Retourne zéro si OK, non-zéro si le mode demandé est inconnu.

mode = GMODE_TEXT_80_25 ou GMODE_320_200_8 (défini dans shared/const.h)

gframe

void gframe(char *framebuffer)

Copie un framebuffer de 320x200 octets dans la mémoire vidéo.

gcolor

void gcolor(int i, int r, int g, int b) 

Modifie l'entrée i (0-255) de la palette. Les composantes de la couleur (rouge vert bleu) sont données entre 0 et 63 compris.

Infos système

kmeminfo

void kmeminfo(struct mallinfo *kmem, int *ksize, struct mallinfo *umem, int *usize)

Remplit des informations sur la mémoire gérée par le kernel.

  • kmem: tas réservé au kernel (principalement pour les piles kernel)
  • ksize: taille totale du tas kernel
  • umem: tas réservé aux piles utilisateur
  • usize: taille totale du tas des piles utilisateur

Pour obtenir des infos sur le tas utilisateur il suffit d'appeler mem_allinfo (mallinfo existait déjà dans malloc.c.h, on l'a simplement rendu accessible)

getprocessinfo

int getprocessinfo(int pid, struct procinfo* buf)

Si pid référence un processus existant, retourne le nombre de processus existants (c'est à dire tous sauf ceux marqués comme recyclables), sinon retourne -1. Si buf n'est pas nul, la structure est remplie avec des informations sur le processus pid.

Ces informations sont le PID du processus, son nom, son état, le numéro du terminal auquel il est rattaché, sa priorité, ainsi que le PID de son père.

getmqinfo

int getmqinfo(int qid, struct mqinfo* buf)

Si qid référence une file de message existante, retourne le nombre de files de messages allouées, sinon retourne -1. Si buf n'est pas nul, la structure est remplie avec des informations sur la file de messages identifiée qid.

Ces informations sont la capacité de la file de messages, le nombre de messages actuellement dans la file, ainsi que le nombre processus bloqués en réception et en émission.

FAT16

fsize

int fsize(const char *filename);

Retourne la taille du fichier filename (en octets) s'il existe, -1 sinon.

fread

int fread(const char *filename, void *buffer, int size);

Si le pointeur buffer n'est pas nul, copie (au maximum) les size premiers octets du fichier filename dans buffer, et retourne le nombre d'octets effectivement copiés. Sinon, si le pointeur buffer est nul, ou si le fichier n'existe pas, retourne -1.

scandir

int scandir(struct fileinfo *file_list, int file_list_len);

Retourne le nombre de fichiers présents dans le répertoire racine. Si le pointeur file_list n'est pas nul, le tableau de structures pointé est rempli avec le nom et la taille des file_list_len premiers fichiers.

SoundBlaster 16

sb16_init

int sb16_init(int rate, int bits);

Initialise la soundblaster pour jouer du son en auto-init DMA à la fréquence rate, en hertz (typiquement 11025, 22050 ou 44100), encodé sur bits bits (8 ou 16).

Si échec, renvoie un nombre négatif.

Si succès, renvoie l'ID de la file de message servant à remplir le buffer de la soundblaster.

La SB envoie une interruption à chaque fois qu'elle a lu 8Ko de son; le traitant, dans le kernel, appelle preset sur la file sus-citée. Le processus user s'occupant de générer le son doit preceive sur cette file ; à chaque fois que preceive renvoie MQ_INTERRUPTED, le processus doit générer 8Ko de son et appeler sb16_feed.

Lorsque preceive renvoie MQ_INVALID, la file a été supprimée et le processus de stream peut s'arrêter.

Le traitant d'interruption n'envoie jamais de valeur dans cette file de message (pour ne pas bloquer un processus qui n'a rien à voir avec le son), donc on peut passer NULL comme pointeur à preceive.

sb16_feed

void sb16_feed(int chunk, char *buf);

Copie 8 Ko de son depuis buf vers la mémoire de la SoundBlaster.

chunk est le numéro de "morceau" du buffer vers lequel on veut copier ces 8 Ko, en sachant que le buffer fait 32 Ko au total. Il y a donc 4 chunk indexés de 0 à 3. La SB commence à lire le chunk 0, et elle envoie une interruption à chaque fois qu'elle a lu un chunk. Quand elle a fini le chunk 3 elle revient au chunk 0.

Ainsi, le streamer doit toujours garder une avance de 4 chunks (32 Ko) sur la lecture pour que le son soit fluide.

sb16_stop

void sb16_stop(void);

Arrête la lecture et détruit la file de message associée à la SB16.


Tests

Afin de vérifier l'ensemble des fonctionnalités de succulent., nous avons écrit un jeu de tests comme utilisé les nombreux tests fournis.

Tests fournis

Dans le shell de succulent., tapez test_proc pour lancer les tests fournis. Indiquez ensuite sur le prompt si vous souhaitez un test en particulier, indiquez son numéro ou tapez 'auto' pour lancer l'ensemble des tests.

Tests développés

Pour lancer l'ensemble de nos tests, tapez dans le shell succulent. test_ours.

Voici l'ensemble des tests développés :

  • test_baby_deaths : créé beaucoup de processus (en profite pour tester la limite) et vérifie leur terminaison
  • test_baby_flushed_in_pipe, test_feed_baby, test_baby_stuck_in_pipe : testent différents scénarios relatifs aux files de messages
  • test_start_higher_prio : test de processus avec changements de priorité
  • test_wait_clock : test de synchronisation avec wait_clock
  • test_waitpid_any : teste la récupération avec waitpid(-1,NULL) dans le bon ordre
  • test_waitpid_any_zombie : teste la récupération d'un zombie avec waitpid(-1,NULL)
  • test_waitpid : teste waitpid et la terminaison d'un orphelin avec kill

Code de tierces parties

Nous avons utilisé quelques librairies externes pour certaines fonctionnalités "de luxe", mais il va sans dire que tout le code du kernel tel qu'il est demandé dans la spec est de nous.

  • Lecteur de MOD ProTracker : Micromod, (c) 2015 Martin Cameron.
  • Décodeur de GIF : adapté de stb_image.h. Sean Barrett. Domaine public. Modifié pour les besoins de notre application (retiré récursion et auto-dépalettisation)
  • Registres VGA pour passer en mode 13h en mode protégé : Source. Chris Giese. Domaine public. (Il existe aussi cette version)
  • Trigonométrie : adapté de cet article (si on avait eu plus de temps on aurait investigué fsincos du x86)
  • PRNG : on a recyclé celui qui était dans les tests

ProTips pour les promotions suivantes

Le projet système est semé d'embûches. Comme on est vraiment sympas, voici quelques soucis "périphériques" qu'on a eus (c'est-à-dire que c'est pas lié directement au système, mais ça nous a mis des bâtons dans les roues quand même) et des conseils pour les résoudre si vous tombez dessus.

"unknown opcode" sur division par 4 (entre autres)

Si vous ne travaillez pas sur les machines de l'ensimag et que votre version de GCC est sortie lors de cette décennie (gcc 6.1.1 sur arch pour nous), il est possible le compilo utilise des opcodes pas supportés par le Pentium. Notamment, le programme "int a = 10; int b = a/4;" plantera. Pour remédier à ça, passez "-march=pentium" à gcc dans vos makefiles.

Booter le kernel dans VirtualBox à partir d'une image disque FAT32

VirtualBox est moins pratique que qemu pour débugger le projet système, mais son émulation SoundBlaster 16 fonctionne mieux et la framerate est généralement meilleure. C'est donc un meilleur environnement pour faire tourner votre démo pour la soutenance si vous avez du graphisme et/ou du son.

Le hic, c'est que VirtualBox a son propre format d'image disque : pas pratique pour mettre à jour le kernel régulièrement. Il est néanmoins possible de dire à VirtualBox d'utiliser un loop device lié à une image disque maison. La recette :

1. Préparez un grub.cfg contenant :

menuentry "monsuperkernel"
{
    multiboot (hd0)/kernel.bin
    boot
}

2. Préparez de votre image disque :

dd if=/dev/zero of=coucou.img bs=1M count=100
losetup /dev/loop0 coucou.img
mkfs.vfat /dev/loop0
mount /dev/loop0 /mnt
grub-install --target=i386-pc --debug /dev/loop0 --boot-directory=/mnt/boot --force
cp kernel.bin /mnt/kernel.bin
cp grub.cfg /mnt/boot/grub/grub.cfg
umount /mnt
VBoxManage internalcommands createrawvmdk -filename yolo.vmdk -rawdisk /dev/loop0

3. Créez une machine virtuelle VirtualBox avec yolo.vmdk comme image disque. Vous devriez pouvoir booter sur votre kernel. Il faudra peut-être rajouter votre utilisateur au groupe disk pour que vbox puisse accéder à /dev/loop0.

Quand vous voulez mettre à jour votre kernel pour le retester dans VirtualBox il suffit de monter /dev/loop0 et d'y copier kernel.bin.

Pour détacher le loop : losetup -d /dev/loop0

Programmation de la Sound Blaster 16

L'émulation Sound Blaster 16 de qemu, en date de la version 2.6.0, semble être buggée. De une, le son grésille sur la sortie PulseAudio, alors qu'avec le driver WAVE le son est nickel. De deux, quand vous avez lancé le playback en mode autoinit DMA, la fenêtre graphique de qemu freeze jusqu'à ce que vous arrêtiez le DSP (typiquement en lui envoyant 0xD0), alors que votre interrupt handler (qui s'occupe de streamer) continue bien de tourner dans le fond.

Donc je vous conseille dans un premier temps mettre au point votre routine de streaming avec qemu sans trop s'inquiéter si le son est un peu cracra et que la fenêtre freeze. Quand vous pensez qu'elle est suffisante, écoutez le résultat final avec VirtualBox, dont l'émulation SB16 est correcte.

Quand vous programmez votre vecteur d'interruptions n'oubliez pas que les entrées 0-31 sont réservées pour les exceptions, même si votre SB occupe l'IRQ 5.

Quelques petites pages sympa:

mem_alloc n'est pas thread safe

De rien.


Inspirations

Cette démo textmode ultra stylée (bawlz - demosaurus) nous avait inspirés pour faire une démo en mode texte au début, mais finalement on est parti sur du mode 13h.

Conclusion

Nous sommes plutôt satisfaits de l'avancée de succulent. Nous sommes particulièrement heureux d'avoir pu développer et présenter une démonstration que nous pensons divertissante et drôle. En plus de cela, nous sommes contents d'avoir pu intégrer des extensions supplémentaires et très intéressantes.