Consignes

Ce premier TP est avant tout un TP d'introduction. Il ne donnera pas lieu à un compte-rendu ou une note. Les TPs 2 et 3 utiliseront MINIX et tout ce qui se trouve dans ce TP sera considéré comme acquis.

Liens utiles

1. Préliminaires : MINIX et QEMU

Démarrer Linux

Comme nous allons utiliser des outils UNIX, tout le TP se fera sous Linux (système principal) et MINIX (système émulé).

Commencez donc par démarrer Linux et vous logger.

Les systèmes Unix possèdent de nombreux utilitaires en ligne de commande bien pratiques tels que wc (compte les lignes, mots, octets dans un fichier), sed (remplacement de chaînes de caractères dans un fichier), grep (recherche d'expression régulières dans un fichier), cut (sélection de colonnes dans un fichier), ... Certaines de ces commandes sont des filtres et peuvent être composées grâce à l'opérateur |. Nous vous conseillons d'apprendre à les utiliser car sous MINIX, vous aurez seulement une interface en ligne de commande. (Vous aurez par contre toujours accès à votre session graphique Linux.)

MINIX

MINIX ("Mini Unix") est à l'origine un système d'exploitation à but pédagogique. Depuis la version 3, il s'agit également d'un système d'exploitation de type UNIX (conforme à la norme POSIX) qui peut facilement être adapté et utilisé dans des systèmes embarqués. Même s'il s'agit d'un système minimaliste de type micro-noyau, il est possible d'installer toute une chaîne de développement (gcc, emacs, etc.) ainsi qu'un serveur X.

La dernière version stable est la version 3.2.0, mais nous utiliserons la version précédente (3.1.8) qui est légèrement plus légère.

QEMU

Plutot que d'installer MINIX réellement sur le disque, nous allons l'installer dans une machine virtuelle. Il y a plusieurs intérets comme :

Un des inconvénients est que la machine virtuelle est (légèrement) plus lente qu'une machine réelle.

Nous allons utiliser QEMU, qui permet d'émuler de nombreux type de processeurs (i386, x86_64, sparc, PowerPC, ...). Autrement dit, nous pouvons choisir le type de processeur utilisé par la machine virtuelle. Comme MINIX est un système 32 bit, nous allons utiliser le processeur i386.

1.1. Installation de MINIX

L'installation de MINIX est relativement aisée. Pour ne pas modifier la séquence de boot des ordinateurs de l'université, nous allons installer MINIX dans un « disque virtuel » (un fichier) et l'utiliser à travers un émulateur. Nous y perdrons en efficacité, mais y gagnerons en simplicité.

Pour ne pas saturer vos comptes personnels (l'image du CD d'installation fait environ 300MB, et il faut compter environ 300MB pour pouvoir compiler un système confortablement), nous allons installer MINIX dans un répertoire temporaire.

Ceci signifie que votre installation de MINIX sera disponible uniquement sur l'ordinateur que vous utilisez pour le TP, et seulement jusqu'au prochain redémarrage.

Une autre possibilité est de l'installer dans un sous répertoire de /local/, sur lequel vous avez les droits d'écriture. Votre système virtuel ne sera pas effacé au redémarrage, mais ne sera disponible que sur la machine initiale, et utilisable par toute personne loggée sur cette machine...

Avant d'installer MINIX à proprement parler, il faut booter une machine virtuelle en utilisant l'image du CD d'installation. Tout ceci se fait de la manière suivante :

  1. création d'un répertoire TP-OS dans /tmp/ :

    $ cd /tmp/
    $ mkdir TP-OS
    $ cd TP-OS
    

  2. vérification de l'espace libre restant sur le disque

    $ df -h
    
    La partition racine (montée sur /) doit contenir au moins 1G.

  3. vérification que QEMU est bien installé

    $ qemu-system-i386 --version
    

  4. téléchargement de l'image du CD d'installation dans le répertoire /tmp/TP-OS/, soit par un navigateur (voir les liens au début du sujet), soit avec

    $ wget http://lama.univ-savoie.fr/~hyvernat/Enseignement/1213/info524/minix_R3.1.8-r8398.iso.bz2
    

  5. décompression de l'image (environ 2 minutes)

    $ bunzip2 minix_R3.1.8-r8398.iso.bz2
    

  6. vérification de l'empreinte de l'image

    $ md5sum minix_R3.1.8-r8398.iso
    
    Vous devez obtenir 0572a10df269831cc4f361252be66833

  7. création d'un disque virtuel minix-TP.img de 300MB avec QEMU

    $ qemu-img create minix-TP.img 300M
    

  8. « boot » de l'image du CD sur le disque virtuel grâce à QEMU

    $ qemu-system-i386 -m 256 -cdrom minix_R3.1.8-r8398.iso -hda minix-TP.img -boot d
    

    Les arguments intéressants sont :

    • -m 256 pour spécifier 256MB de RAM à l'intérieur du système émulé
    • -cdrom minix_R3.1.8-r8398.iso pour dire que le fichier est l'image du CD contenu dans le lecteur de CD virtuel
    • -hda minix-TP.img pour spécifier que le premier disque dur est représenté par le fichier minix-TP.img,
    • -boot d pour forcer la machine virtuelle à booter sur le lecteur de CD.
    Comme la plupart des systèmes réels, QEMU va essayer de booter sur le CD avant de booter sur le disque dur. QEMU lancera donc le CD d'installation.

Si le boot sur le CD virtuel se passe bien, vous devriez obtenir une nouvelle fenêtre QEMU qui contient le système émulé. L'installation se fait maintenant en suivant les instructions du CD d'installation : loggez vous en tant qu'administrateur (root, sans mot de passe). Attention, il se peut que l'affichage initial ne se mette pas à jours avant le login. Il faut alors appuyer sur Entrée afin de voir le login apparaitre. Ensuite, il suffit de lancer la commande

# setup

et suivez les instructions :

Vous pouvez ensuite éteindre la machine virtuelle :

# shutdown

et quitter QEMU :

cd>off

Félicitations, vous avez installé MINIX sur une machine virtuelle. Pour démarrer cette machine virtuelle, on utilise la commande

$ qemu-system-i386 -m 256 -hda minix-TP.img

(Notez qu'on ne déclare plus d'image pour le CD.)

1.2. Test et configuration de MINIX pour ce TP

Lors du démarrage de MINIX, il est préférable de choisir le second système (2 Start Custom MINIX 3) car c'est le noyau qui est modifié par défaut. Le choix 1 reste (normalement) le système officiel.

Les premières étapes pour configurer MINIX seront :

  1. créer un mot de passe pour l'administrateur:

    # passwd
    
  2. créer un nouvel utilisateur « normal » et lui donner un mot de passe :

    Créez un utilisateur dans le groupe other et donnez lui le login etu. Son répertoire personnel sera dans /home/etu. La commande nécessaire s'appelle adduser...

    Quittez la session root et utilisez ce nouveau compte utilisateur...

Choisir un éditeur de texte

L'éditeur par défaut de MINIX, s'appelle ELLE :

$ elle nom_de_fichier

Il suffit de connaître 2 commandes :

Ceux qui préfèrent (et connaissent) peuvent également utiliser Vi. (Mais attention, il s'agit de Vi et non de ViM.)

Pour les TPs suivant, une image MINIX déjà configurée vous sera fournie. Elle contiendra entre autre l'éditeur de texte Nano et ViM afin de rendre l'édition de code source plus facile.

Commencez par éditer le fichier .ashrc dans votre répertoire personnel pour :

2. Les processus POSIX

Afin de gagner du temps dans la partie suivante, il est fortement conseillé de recompiler le noyau MINIX (sans modification) pendant que vous faites cette partie du TP. Pour cela, loggez-vous comme administrateur sur la deuxième console (accessible avec Alt-F2) et

# cd /usr/src/tools/
# make image

Vous pouvez revenir sur la première console (Alt-F1) pendant que le noyau compile...

  1. Écrivez un petit programme en C qui :
    • imprime un message,
    • se duplique (fork()) et affiche le PID du sous-processus créé
    • le processus créé affiche un message.
    Faites en sorte que vos processus ne terminent jamais, pour avoir le temps de faire la suite.

  2. Compilez et lancez votre programme en arrière plan :

    $ cc proc1.c -o proc
    $ ./proc1 &
    

  3. Vérifiez que vous avez bien créé 2 processus, et que l'un d'eux a le bon PID.

  4. Vérifiez que vous pouvez tuer le processus père ou le processus fils sans que l'autre ne disparaisse.

  5. Créez un processus zombie. (Vous pouvez vérifier qu'il existe en passant l'option l à ps.)

  6. Vérifiez qu'un processus zombie disparaît quand son père l'a « attendu ». Comment procédez-vous ?

Pour ces questions, vous pouvez facilement insérer des sleep(1); dans votre programme pour attendre 1 seconde. Cela vous permettra de regarder l'évolution des processus...

  1. Écrivez un petit programme C qui génère un fils. Le fils se termine et le père génère un nouveau fils, ad infinitum.

    Que se passe-t'il ?

  2. Écrivez un petit programme C qui génère un fils et se termine. Son fils génère lui aussi un fils et se termine, ...

    Que se passe-t'il ? Comment peut-on stopper la génération de ces processus ?

  3. Écrivez un programme C qui génère un processus fils. Le père et le fils génèrent alors chacun un processus fils, etc.

    Que se passe-t'il ? À quoi ressemble l'arborescence des processus ?

3. Recompiler son premier noyau

Avant de modifier votre premier noyau, je vous conseille de faire une copie des sources dans un répertoire :

$ su
# cd /usr/src/
# cp -r kernel/ kernel-bak
# cp -r servers/ servers-bak
# cp -r include/ include-bak

Pour recompiler un noyau, il faut :

Recompilez un noyau en changeant le message de bienvenue du début :

Copyright 2010, Vrije Universiteit, Amsterdam, The Netherlands
...

De cette manière, vous pourrez vérifier que vous exécutez bien votre nouveau noyau.

4. Modifier le « process manager »

Les exemples précédents utilisent la fonction POSIX fork(). Cette fonction est gérée par le serveur « process manager » dans /usr/src/servers/pm/.

Trouvez le fichier correspondant et modifier le pour afficher un message lors de chaque exécution de fork().

Recompilez votre noyau et testez le.

Proposez des solutions pour prévenir les « fork-bombs » de la section 2.

fork() n'est pas un appel système. Sous MINIX, l'architecture micro-noyau a pour conséquence qu'il y a très peu de vrais appels système. (Fonctions qui nécessitent le mode noyau.) En général, un appel système se décompose en :

  1. un processus a besoin d'une opération bas-niveau,
  2. il fait une demande au noyau (sendrec),
  3. qui demande au serveur correspondant d'effectuer la tâche,
  4. le serveur répond au noyau,
  5. le noyaux répond au processus utilisateur.

Seules les étapes 2 et 4 se font en mode noyau.

Vous pouvez obtenir la liste des commandes qui vont nécessiter ceci en regardant dans le répertoire /usr/src/lib/libc/syscall. (Je crois...)

5. Ajouter une « backdoor »

À la fin du processus de boot, le noyau demande au serveur init (dans /usr/src/servers/init/) d'ouvrir des consoles pour se logger. (MINIX en démarre trois par défaut, Linux en démarre six.) Ces consoles sont accessibles avec Alt et une touche Fonction. C'est /usr/src/commands/login/login.c qui gère le login, et /usr/src/commands/getty/getty.c qui appelle la commande de login dans chaque console.

En modifiant les fichiers pertinents, créez une « back-door » dans votre noyau : il démarrera une version modifiée de login sur la troisième console (/dev/ttyc2). Utiliser le login gulliver sur cette console devra vous donner accès au compte root, sans demander de mot de passe.

Pour ceci, il vous faudra modifier les fichiers login.c et getty.c dans /usr/src/commands/... afin de remplacer la procédure normale de login...

Pour commencer, modifiez uniquement le fichier login.c sans vous occuper du numéro de la console.

Il n'est pas nécessaire de recompiler le noyau pour ceci, il suffit de recompiler les commandes login et getty avec la commande make dans le répertoire contenant le fichier .c. Il faut ensuite tester le fichier exécutable, et quand il fonctionne, l'installer. Pour login :

# cd /usr/src/commands/login/
#  # ...
#  # modification de "login.c"
#  # ...
# make
# ./login
#  # test de la commande "login"
#
#  # quand "login" fonctionne
# make install

Attention ! Si vous installez une version buggée de login, vous risquez de ne pas pouvoir vous logger sur le système MINIX...