• Aucun résultat trouvé

Programmation de modules sous Linux

N/A
N/A
Protected

Academic year: 2022

Partager "Programmation de modules sous Linux"

Copied!
6
0
0

Texte intégral

(1)

Programmation de modules sous Linux

Le TP est noté, chaque point à faire valider sera évalué par l'enseignant durant la séance. L'intégralité du TP sera évaluée dans la séance. Le TP pouvant donner suite à un TP, un TD, etc., il est conseillé de garder tous les fichiers produits lors de cette séance.

1 Environnement de développement

L’objectif du TP est de développer un module qui sera chargé dans le noyau du système d’exploitation. Ce module permettra de créer un nouveau périphérique

caractère qui sera utilisable à partir du shell et d’un programme C par le biais d’appels systèmes. Pour cela, vous utiliserez une version de knoppix live CD qui est disponible sous forme d’ISO à la racine de la machine. Démarrer une machine virtuelle sans disque dur et charger le live CD au démarrage de la machine virtuelle. Pour changer la langue du clavier, vous cliquerez sur l’icône représentant un drapeau. Un problème dû à la version de VirtualPC ne permet pas d’utiliser les touches #, {, } et |. Pour cela, passer en clavier américain et utiliser respectivement les touches 3, ^ , $ et *. Vous ferez vos sauvegardes sur le serveur tx (<ftp://serv-tx>) à partir de konqueror.

Utiliser le compte root, pour cela utiliser la commande su.

2 Les périphériques

Dans le monde Linux, les périphériques sont implémentés sous la forme de modules.

Ils permettent de fournir les fonctionnalités de périphérique depuis l’espace

utilisateur. Un point d’entrée de l’espace utilisateur vers un périphérique est fourni par un fichier dans le répertoire /dev (devices). La plupart des éléments dans un système Linux sont représentés par un fichier, pour les périphériques se sont des fichiers spéciaux.

2.1 Liste des périphériques

A l’aide du programme « ls -l », vous listerez les périphériques dans le répertoire pour connaître leur type (périphérique caractère ou bloc) ainsi que leurs numéro mineur et majeur. Le type de périphérique indique la manière dont les données sont écrites sur un périphérique. Pour un périphérique caractère, on parle d’écriture en série, octet par octet par exemple le shell (tty). Pour un périphérique bloc comme un disque dur, l’écriture s’effectue sous forme de blocs d’octet. Le numéro majeur représente le pilote qui gère le fichier spécial. Le nombre mineur est réservé à l’utilisation du pilote. Son utilité consiste, dans le cas où un même pilote gère

plusieurs fichiers spéciaux, à identifier de façon unique le périphérique. Leur création se fait par la commande « mknod ».

Pour avoir la liste des numéros majeurs (type de périphérique) : $cat /proc/devices Pour avoir la liste des numéros mineurs (liste des périphérique) : $ls -l /dev ( une partie des fichiers spéciaux se trouve dans le répertoire /KNOPPIX/dev )

2.2 Création d’un périphérique caractère

/dev/vcs0 est un périphérique caractère de numéro majeur 7 et de numéro mineur 0, avec les permissions 0644. Il correspond à la mémoire d'affichage de la console

(2)

virtuelle en cours d'utilisation. A l’aide de la commande « mknod » vous allez créer un nouveau périphérique caractère et le tester.

Passer sur la console virtuelle 1 à l’aide du raccourci ctrl+alt+F1. Puis, créer un nouveau périphérique pour la mémoire d’affichage de la console virtuelle 1.

$mknod -m 0644 ./myVCS c 7 0

Pour manipuler le périphérique caractère, vous utiliserez les commandes suivantes.

Ecriture dans le périphérique de type caractère

$echo bonjour > ./myVCS

Lecture de 10000 caractères de la sortie standard d’un périphérique de type caractère

$dd bs=1 count=10000 < ./myVCS Ou lister tout le fichier spécial

$cat ./myVCS Faites vérifier

3 Les modules chargeables

Cette partie a pour objectif la création d’un module « HelloWorld », ainsi que son chargement dans le noyau.

3.1 Code de base

Le code suivant est le code minimum pour créer un module. Les fonctions

init_module() et cleanup_module() sont les méthodes respectivement exécutées lors du chargement du module en mémoire et lors du déchargement. Les macros

MODULE et __KERNEL__ sont nécessaires pour définir un module. __KERNEL__

est relatif à un certain nombre de paramètres spécifiques à la version du noyau utilisée. MODULE indiquera que le code qui suit sera destiné à concevoir un module du noyau.

#define MODULE

#define __KERNEL__

#include <linux/module.h>

int init_module () {

printk ("Hello World\n");

return (0);

}

void cleanup_module () {

printk ("Bye !\n");

}

Lorsqu’on regarde la fonction utilisée pour l’affichage du message nous ne voyons pas la fonction classique printf mais la fonction printk (print kernel). Les fonctions des bibliothèques standards libc comme la fonction printf n’est pas utilisables, car elle se base elle-même sur le noyau. Toutefois, il existe un ensemble de fonctions utilisables à partir du noyau qui sont sensiblement identiques.

(3)

3.2 Compilation

La compilation du module est simple. Il se compile comme tout programme c mais avec l’option -c (cf. $man gcc).

$gcc -c hello.c -Wall

3.3 Manipulation des modules

Les modules sont manipulables à l’aide des programmes lsmod, insmod, rmmod et modprobe qui se situent dans /sbin.

• lsmod : permets de voir quels modules sont chargés à l'heure actuelle. Cette commande affiche la liste des modules chargés, les dépendances entre les modules chargés, et dit si les modules sont utilisés ou non.

• Insmod module: permets de charger un module en mémoire. Si « module » est spécifié sans extension (en général .o), insmod cherchera le module dans des répertoires par défaut, en général /lib/module/version_du_noyau. Sinon, il faut donner le chemin où trouver le module. (Par exemple : insmod -f ./module.o ; le -f permet de forcer le chargement d'un module qui a été compilé avec une version du noyau différente de celle du noyau qui tourne actuellement.)

• rmmod module : permets de décharger un module, où « module » est le nom donné par lsmod.

• modprobe : exécute les commandes insmod et rmmod à votre place. modprobe le_module charge le_module en mémoire ainsi que tous les modules dont il dépend (en lisant le fichier modules.dep). modprobe -r le_module enleve le_module de la mémoire, ainsi que tous les modules dont il dépend, sauf s'ils sont utilisés par un autre module bien sûr.

dmesg est utilisé pour examiner ou contrôler le tampon des messages du noyau. Par conséquent, tous les messages générés par la fonction printk peuvent être examinés par ce programme.

3.4 Gestion de la portée des symboles

Quand un module est enregistré, toutes ses fonctions sont exportées. Cela signifie que tous les autres modules ont accès à ces fonctions, car elles sont globales. Il est

possible d’accéder à la table des symboles et vérifier si un symbole existe déjà. La table des symboles est contenue dans le fichier /proc/ksyms.

$cat /proc/ksyms | hello

Ajouter une variable et une fonction qui initialise la variable ajouter.

Compiler et installer le module, puis faites vérifier

4 Création d’un périphérique caractère

Cette partie à pour objectif la création d’un module permettant de définir un périphérique caractère manipulable à partir du shell ou d’un programme c.

4.1 Paramétrage d’un module

Quelquefois, il est plus simple de configurer dynamiquement un module. Dans la

(4)

programmation c conventionnelle, nous utilisons *argv[] pour passer des paramètres au programme. Pour les modules, nous avons besoin de macros pour accéder aux paramètres.

MODULE_PARAM (variable, type) Types:

s - string i - integer b - byte h - 2 bytes l - 4 bytes

#define MODULE

#define __KERNEL__

#include <linux/module.h>

char *test_str = "Bonjour";

MODULE_PARAM(test_str,"s");

int init_module() {

printk ("%s\n" , test_str);

return (0);

}

void cleanup_module (){}

Quand aucun paramètre n’est spécifié, la valeur par défaut est utilisée. Ex :

$insmod param.o Bonjour

$rmmod param

$insmod param.o test_str="TEST"

TEST

faites vérifier

4.2 Enregistrement du nombre majeur

Les nombres majeurs doivent être enregistrés auprès du noyau pour que celui-ci fasse la liaison entre les fichiers spéciaux et le pilote. Nous ne pouvons pas choisir celui qui nous plaît, nous risquerions d’avoir des problèmes de conflit. Il y a donc des plages réservées qui sont définies dans le fichier « /usr/src/linux/Documentation/devices.txt

». Il existe une méthode pour obtenir dynamiquement fonction de ceux déjà utilisés.

Une fois l’enregistrement terminé, le module possède une entrée dans le fichier

« /proc/devices » et peut utiliser les fichiers spéciaux y faisant référence. Une fois que nous avons terminé l’utilisation de notre module, il faut libérer le « nombre majeur » obtenu qui pourra alors être utilisé à son tour par un autre pilote.

Voici les fonctions permettant de gérer les enregistrements du pilote auprès du noyau.

int register_chrdev(unsigned int nb_majeur, const char *nom_dev, struct file_operation *fops)

int unregister_chrdev(unsigned int nb_majeur, const char*nom_dev)

#define MODULE

#define __KERNEL__

#include <linux/module.h>

(5)

#include “mon_peripherique.h”

int init_module () {

printk ("Hello World\n");

if(register_chrdev(222,"mon_dev",&mon_fops)){

// Le numéro majeur 222 est un numéro libre printk("<1>déclaration impossible");

}

return (0);

}

void cleanup_module () {

printk ("Bye !\n");

unregister_chrdev(222,"mon_dev");

}

Pour disposer de l’allocation dynamique de “nombre_majeur”, il suffit de mettre à 0 la variable nb_majeur et récupérer le retour de la fonction. La variable « nom_dev » est le nom qui va être affiché dans « /proc/devices ». Lorsqu’on conçoit un pilote, nous devons prendre garde de respecter ce qu’il peut fournir comme possibilités en termes d’accès (ouverture, déplacement, lecture, écriture …). Ceci ce fait par le biais de la structure de pointeur sur fonction « struct file_operations ». Vous trouverez des informations sur cette structure dans le fichier d’entête

« /usr/src/linux/include/linux/fs.h ».

4.3 Périphérique caractère

/*

* fichier d'en-tête mon_peripherique */

#ifndef _MON_PERIPHERIQUE_H

#define _MON_PERIPHERIQUE_H

#include <linux/fs.h>

#include <linux/sched.h>

#include <linux/errno.h>

#include <asm/current.h>

#include <asm/segment.h>

#include <asm/uaccess.h>

char mon_data[80] = "Bonjour du noyau";

int mon_open(struct inode *inode,struct file *filep);

int mon_release(struct inode *inode,struct file *filep);

ssize_t mon_read(struct file *filep,char *buff,size_t count,loff_t

*offp );

ssize_t mon_write(struct file *filep,const char *buff,size_t count,loff_t *offp );

struct file_operations mon_fops = { open: mon_open,

read: mon_read, write: mon_write, release:mon_release, };

int mon_open(struct inode *inode,struct file *filep){ return 0;}

int mon_release(struct inode *inode,struct file *filep){return 0;}

(6)

ssize_t mon_read(struct file *filep,char *buff,size_t count,loff_t

*offp ) {

/* fonction pour copier le tampon depuis l'espace du noyau vers l'espace utilisateur*/

if ( copy_to_user(buff, mon_data, strlen(mon_data)) != 0 ) printk( "Kernel -> userspace copy failed!\n" );

return strlen(mon_data);

}

ssize_t mon_write(struct file *filep,const char *buff,size_t count,loff_t *offp )

{

/* fonction pour copier le tampon depuis l'espace utilisateur vers l'espace du noyau*/

if ( copy_from_user(mon_data, buff,count) != 0 )

printk( "Userspace -> kernel copy failed!\n" );

return 0;

}

#endif

Recopier et compiler le module. Une fois le module chargé dans le noyau, tester les entrées/sorties à partir du shell. Puis, vous coderez un programme qui lit et écrit dans le périphérique.

int fd = open(“/dev/mon_peripherique”, O_RDWR);

read(fd, buff, 10);

write(fd, buff, 10);

Faites vérifier la manipulation à partir du shell et d’un programme c

4.4 Périphérique caractère personnalisé

Modifier le module de manière à paramétrer la taille et la valeur par défaut de la variable mon_data. Modifier le module pour gérer le périphérique comme une pile de tableaux de caractères. L’appel de la méthode « write » ajoute un tableau de caractère au début de la liste. Un appel à la méthode « read » supprime le sommet de la pile.

Faites vérifier

Références

Documents relatifs

marge brute – remise – prix d’achat net – prix de vente hors taxe – coût d’achat prix de vente toute taxe comprise – prix d’achat net – frais d’achat – prix

En traction, torsion ou flexion il est possible de résoudre un système qui est hyperstatique et d’en déterminer sa déformation, ou la contrainte. Pour cela la même méthode pour

 A chaque type et chaque degré est affecté un nombre de points La méthode permet de calculer le poids du projet en points de

L'induit d’un moteur est alimenté par une tension continue V = 275V, par l'intermédiaire d'un hacheur série, selon le schéma de principe connu. A l'aide d'un oscilloscope bi-courbe,

Les réactifs sont les ions Ag + et le cuivre métallique car les courbes correspondantes ont un coefficient directeur négatif.. Les produits sont le métal Ag et les ions Cu 2+ car

* Détermination de la graduation 100 : on plonge le réservoir du thermomètre dans de l’eau en ébullition sous la pression atmosphérique normale.. Le liquide dans le capillaire

Un régulateur est dit à action proportionnelle (P) lorsque la valeur de sa tension de sortie est proportionnelle à l’erreur détectée .Quand l’erreur a été corrigée,

Le chauffage à reflux permet d’augmenter la température du milieu réactionnel et donc la vitesse de réaction.. Et ceci sans perte de matière, puisque les vapeurs sont condensées