INF583 - Étude du noyau linux

Retour au cours.

Au cours de ce TD, nous allons étudier la programmation d'un module noyau Linux afin d'étudier sommairement le fonctionnement interne de ce noyau. L'ajout ou la suppression d'un module noyau modifie le noyau en cours d'exécution, ce qui peut être très dangereux en cas de bug (crash de la machine). Le nouveau module, une fois chargé, a accès à toutes les fonctionnalités du noyau. Pour déclencher le chargement d'un module, il est donc logiquement nécessaire de posséder les droits « administrateur » (« root »). La possession de ces droits par les étudiants pour les machines des salles info poserait d'évidents problèmes de maintenance et de sécurité.

Pour toutes ces raisons, ce TD sera réalisé à l'aide d'une machine virtuelle, programme réalisant une émulation du matériel d'un ordinateur typique suffisamment complète pour pouvoir faire tourner un système d'exploitation normalement destiné à une machine physique. Dans cette machine virtuelle, vous possèderez les droits administrateurs, ce qui vous permettra de charger vos propres modules. En outre, l'utilisation d'une machine virtuelle permet de tester ses propres modifications de noyau rapidement tout en limitant les dégâts potentiels de celles-ci (crash de la machine virtuelle au lieu de la machine réelle). De plus, les machines virtuelles permettent de sauvegarder des instantanés de la machine, notamment le contenu du disque dur virtuel. Cela permet de revenir à un état antérieur fonctionnel.

Nous utiliserons l'image d'une distribution linux de petite taille, nommée Tiny Core Linux. Cette image a été pré-installée sur un disque dur virtuel, lui-même inclut dans un fichier contenant aussi les caractéristiques de la machine virtuelle à émuler. Ce fichier est destiné à être utilisé par la machine virtuelle VirtualBox.

0. Lancement de VirtualBox et utilisation du disque dur virtuel

Avant de démarrer VirtualBox, il faut s'assurer que le module du noyau vbox est correctement chargé. Ce module permet à VirtualBox d'utiliser des optimisations de virtualisation proposées par les puces Intel (comme Intel-VTx) et d'accéder directement à certains périphériques.

Pour charger ce module il suffit de taper dans un terminal:

sudo /etc/init.d/vbox start

Le disque dur virtuel pouvant grossir jusqu'à occuper quelques centaines de méga-octets, il est préférable de ne pas travailler dans votre dossier personnel à cause de la présence de quotas par compte utilisateur. Nous allons donc configurer VirtualBox pour travailler dans le répertoire /tmp qui pointe vers une partition de plusieurs dizaines de giga-octets du disque dur local.

Pour configurer correctement VirtualBox:

  1. Créer un répertoire dans le dossier /tmp. Par exemple cd /tmp && mkdir INF583.
  2. Lancer VirtualBox.
  3. Dans le menu File, cliquez sur Preferences.
  4. Dans l'onglet General, modifiez la valeur de Default Hard Disk Folder et faites la pointer vers le répertoire précédemment crée: /tmp/INF583.
  5. Puis cliquez sur le bouton OK.
  6. Dans le menu File, cliquez sur Preferences.
  7. Dans l'onglet Input, changez la Host key (qui doit avoir la valeur Right Ctrl) en Right Alt.
  8. Puis cliquez sur OK.

Vous pouvez maintenant téléchargez le fichier contenant le disque dur virtuel et les inforamations de la machine virtuelle à émuler. Mais pensez à l'enregistrer dans le répertoire /tmp/INF583 précédemment créer et non dans votre dossier personnel.

Le fichier est accessible ici.

Ce fichier est une archive contenant trois fichiers: le disque dur virtuel et deux autres fichiers permettant à VirtualBox de connaître la configuration de la machine virtuelle à émuler. Vous pouvez modifier cette configuration si vous le souhaiter.

Disons que vous avez téléchargé le fichier TCL.tar et avez extrait son contenu dans /tmp/INF583/TCL/. Pour importer la machine virtuelle et son disque dur dans VirtualBox, il suffit de:

  1. Dans le menu File, cliquez sur Import Appliance.
  2. Dans la fenêtre cliquez sur le bouton Choose, puis sélectionner le fichier /tmp/INF583/TCL/TCL.ovf.
  3. Puis cliquez sur le bouton Next.
  4. Dans la fenêtre Appliance Import Settings laisser inchanger les paramètres et cliquez sur le bouton Finish.
Dans la fenêtre principale de VirtualBox apparaît maintenant une nouvelle machine virtuelle appelée TCL que vous pouvez lancer directement en appuyant sur le bouton Start.

Remarquez que dans la fenêtre pricipale, quand la machine virtuelle TCL est sélectionnée, un résumé de ses caractéristiques est affiché. Vous pouvez modifier chaque caractéristique en cliquant dessus. Par exemple, la taille de la RAM à émuler est de 256MB. La taille de la mémoire vidéo est de 12MB, nous vous conseillons de la changer à 64MB. Pour ce faire, cliquez sur Display et changer la barre Video memory à 64MB (ce changement ne peut pas se faire si la machine virtuelle est démarrée).

Si vous n'arrivez pas à ce stade:

Epic win

demander de l'aide.

Vous pouvez maintenant utiliser la machine virtuelle comme s'il s'agissait d'une machine physique. La distribution installée est Tiny Core Linux avec en plus les programmes suivants: dillo2 (navigateur web basique), gcc, make, nano (éditeur de texte en ligne de commande) pour vous permettre de faire les exercices qui suivent directement. Vous pouvez ajouter de nouveaux programmes (comme firefox ou vim par exemple) avec le gestionnaire de paquets AppBrowser de Tiny Core Linux. Les squelettes des exercices et les archives sont déjà présents sur le disque dur virtuel dans le répertoire /home/tc/src. Notez que si vous préférez un éditeur graphique, la commande editor vous permet d'accéder directement à un éditeur basique mais fonctionnel.

Durant ce TD vous allez très certainement rencontrer ce message dans le terminal de la machine virtuelle: Operation not permitted ou Permission denied et d'autres messages similaires. L'intérêt d'utiliser une machine virtuelle réside justement dans le fait qu'on puisse se donner les droits de faire ce qu'on veut, ce qui serait impossible sur les mahcines physiques des salles de TD. Pour passer temporairement root et pouvoir effectuer des tâches nécessitant des privilèges élevés il suffit d'utiliser la commande sudo. Par exemple sudo mkdir /toto permet de créer un répertoire à la racine du système de fichiers, ce qui est impossible sur une distribution normalement configurée.

Une bonne marche à suivre sur une machine Unix est de ne passer root que lorsque cela est nécessaire. L'écriture et la compilation d'un programme ou même d'une partie du noyau comme un module peut et donc doit se faire avec les droits d'un utilisateur normal.

1. Compilation du noyau linux

Nous allons désormais nous intéresser à la compilation du noyau Linux. Le noyau, comme de nombreux autres programmes, est écrit en C et se compile avec la commande make. Toutes les versions officielles (vanilla) du noyau Linux sont disponibles à l'adresse kernel.org. Il existe de nombreux « patchs » permettant de modifier le noyau Linux. Il s'agit principalement d'ajouter de nouvelles fonctionalités, d'améliorer les performances ou la sécurité de la version officielle. La plupart des distributions actuelles utilisent des noyaux très (trop) « patchés ».

Le but de l'exercice est d'apprendre à « patcher » un noyau linux, à le compiler et à remplacer le noyau courant. La distribution Tiny Core Linux courante est basée sur un noyau agrémenté de plusieurs « patch » (voir ici).

On peut résumer les informations sur la configuration et la compilation du noyau de la façon suivante :

Configurez, compilez et testez votre propre noyau en vous basant sur la configuration par défaut de votre distribution.
Notre « boot-loader » sera grub2. Il vous faudra le configurer. Nous vous conseillons de garder la possibilité de démarrer sur l'ancienne version du noyau. Vous pouvez vous aider de cette page pour installer le boot-loader correctement.

2. Compilation et exécution d'un module

Pour le reste de la séance, vous allez utiliser et modifier un module appelé « ticks_mod » fourni par vos enseignants. Ses principe et fonctionnement seront détaillés plus loin. Pour le moment, nous allons mettre en place un environnement qui vous permettra par la suite de tester vos modifications.

Pour pouvoir retrouver votre travail lors des prochaines séances de TD, penser à le sauvegarder (copier les quelques fichiers C que vous allez modifier) sur une machine physique de la salle de TD dans votre répertoire personnel. Pour ce faire, il suffit d'utiliser la commande scp. Son utilisation est très simple. Par exemple pour copier tout un répertoire de la machine virtuelle, disons le répertoire où vous avez travaillé /home/tc/src/ex2, il suffit de taper dans un terminal de la machine virtuelle scp -r /home/tc/src/ex2 moi@10.0.2.2:moi doit être remplacé par votre login des machines physiques.

La ligne scp -r /home/tc/src/ex2 moi@10.0.2.2: se traduit par copier en utilisant ssh (scp) récursivement (-r) tout le contenu du répertoire /home/tc/src/ex2 dans le répertoire personnel (« : », ne pas oublier les deux points) de l'utilisateur moi sur la machine dont l'adresse IP est 10.0.2.2 (du point de vue de la machine virtuelle, cette adresse IP désigne la machine hôte, i.e. la machine physique). Plus d'informations sont accessibles sur la page de manuel de scp (man scp). En résumé,

On rappelle que l'utilisation des quotas par compte utilisateur nous force a utiliser le dossier temporaire /tmp de la machine physique. Ce répertoire peut voir son contenu totalement effacé lors d'un redémarrage de la machine par exemple. Il est donc fortement conseillé de sauvegarder ses données avant de partir du TD.

Le squelette du module se trouve dans le répertoire /home/tc/src/ex2 sur le disque dur virtuel. Si vous avez besoin de télécharger l'archive, elle se trouve ici.

Compilez le module sans modification. Chargez-le dans le noyau à l'aide de la commande insmod.

Vérifiez que le module a bien été chargé, en utilisant la commande lsmod, puis déchargez-le à l'aide de la commande rmmod.

Le module n'étant pas inclus dans l'arborescence du noyau, il faut le charger à l'aide de la commande insmod et non de la commande habituelle modprobe, cette dernière nécessitant que les dépendances entre les modules aient été calculées au préalable (voir la commande depmod). Lorsque cela est possible, cependant, il est préférable d'utiliser modprobe.

3. Notre propre module

Le module qui vous est fourni crée un périphérique virtuel donnant accès à la valeur d'une horloge. Cette horloge est définie dans tous les noyaux Linux et sa valeur est stockée dans la variable globale jiffies. Elle correspond aux nombres d'« instants » (jiffies) écoulés depuis le démarrage du noyau, la durée d'un instant étant spécifiée dans la configuration du noyau au moment de la compilation (dans les noyaux récents et sur x86, cette durée est de 1ms). Pour qu'un programme accède à cette valeur, il devra lire dans le périphérique créé par le module. Ce périphérique doit donc permettre une opération de lecture (appels systèmes de la famille de read). C'est cette opération que cet exercice vous propose de coder.

Description du code du module

Le code du module est divisée en plusieurs parties, dont voici un rapide survol. Les fonctions ticks_init et ticks_exit contiennent respectivement le code exécuté au chargement et au déchargement du module. Les fonctions ticks_open et ticks_read sont les fonctions appelées par la partie générique du noyau lorssqu'un programme appelle open ou read sur notre périphérique.

La fonction ticks_init est abondamment commentée (ne vous inquiétez d'ailleurs pas si vous n'en comprenez pas tous les commentaires!). Les étapes d'initialisation sont :

La fonction ticks_exit contient uniquement la libération des ressources occupées par notre module dans le noyau.

Ajout du support de la lecture pour notre périphérique

Nous voulons accéder à la valeur de notre horloge en effectuant une lecture au niveau de notre périphérique virtuel. Nous devons donc ajouter le support d'une telle opération dans notre module. Comme pour toute opération de lecture, il nous faut également pouvoir ouvrir et fermer ce périphérique. Pour chaque fonction, le travail se résume de la manière suivante :

La liste des fonctions supportées est indiquée au reste du noyau à l'aide de la structure struct file_operations qui consitue un membre de notre variable cdev.

Implémentez la fonction read.

La correction est disponible en suivant ce lien.

4. Implémentation d'un appel système virtuel (vsyscall)

L'utilisation d'un appel système pour accéder à la variable jiffies se révèle relativement lent. Une technique plus intéressante est de proposer une variable en mémoire partagée qui contient la valeur de jiffies. Il nous suffit pour cela d'implémenter l'appel système mmap.

D'une manière similaire à read, implémentez la fonction mmap à l'aide de la fonction :
int remap_pfn_range(struct vm_area_struct *vma, unsigned long from, unsigned long to, unsigned long size, pgprot_t prot).

Utilisez le programme ticks_mmap.c (fournit dans le squelette de l'exercice 2 et aussi sur le disque dur virtuel dans le répertoire /home/tc/src/ex2) pour la tester.

La correction est disponible en suivant ce lien.