Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici

Bibliothèques et fonctions

Introduction

Les bibliothèques sont un objet particulier des systèmes d’exploitation qui permet de factoriser le code. Cela signifie que certaines fonctionnalités peuvent ainsi être d’une part partagées par les programmes, et d’autre part, que les programmes peuvent profiter des mêmes fonctionnalités sans pour autant les implémenter. Les bibliothèques peuvent généralement être présentes sous deux formes sur les systèmes modernes : statique ou dynamique. Dans le cas des bibliothèques dynamiques, le système charge la bibliothèque à la demande du premier programme qui en a besoin, et tout autre programme peut ensuite profiter immédiatement des mêmes fonctionnalités. Dans le cas des bibliothèques statiques, les exécutables contiennent une forme binaire de celle-ci avec le programme, ce qui est généralement utilisé lorsque la bibliothèque n’est pas installée au préalable et que vous ne comptez pas le faire.

Les bibliothèques permettent donc de ne pas réinventer la roue, et souvent, d’utiliser du code plus optimisé que vous auriez pu ne l’écrire vous-même (par manque de connaissance, d’expérience ou tout simplement de temps). De plus, une bibliothèque est souvent utilisée par plusieurs programmes...

Créer et utiliser une bibliothèque avec les outils GNU

Problème

Vos fonctions sont utilisées dans plusieurs de vos programmes et vous voulez factoriser votre code en le plaçant dans une bibliothèque que vos programmes utiliseront.

Solution

Compilez vos fichiers source avec l’option -fPIC du compilateur gcc. Donnez à l’éditeur de liens les options suivantes : -shared -Wl,-soname,nom.so.majeur

Discussion

Supposons que nous voulions créer une bibliothèque libtest.so.1.2 à partir des fichiers source1.c et source2.c.

Ces fichiers source sont semblables à des fichiers source de programmes normaux. L’absence de fonction main() propre aux programmes constitue la seule exception notable.

Nous compilons d’abord les fichiers objets avec :


gcc -Wall -c -fPIC source1.c -o source1.o 
gcc -Wall -c -fPIC source2.c -o source2.o
 

À l’édition des liens, vous exécutez :


gcc -shared -Wl,-soname,libtest.so.1 source1.o source2.o 
-o libtest.so.1.2
 

Attention ! Remarquez l’absence d’espace après les virgules pour la syntaxe de l’option -Wl.

Si la création d’une bibliothèque partagée peut sembler simple, il faut néanmoins avoir en tête certains points. Commençons par aborder le principe de nommage. En effet, le nom d’une bibliothèque n’est pas simplement...

Charger une bibliothèque de manière dynamique

Problème

Vous souhaitez charger une bibliothèque de manière dynamique, c’est-à-dire seulement au moment choisi et non pas automatiquement au lancement du programme.

Solution

Utilisez dlopen().

Discussion

La fonction dlopen() s’utilise comme d’autres fonctions habituelles d’ouverture de fichiers. Son premier argument est en effet le nom du fichier contenant la bibliothèque dynamique à ouvrir et le deuxième argument comprend les modes et options d’ouverture. Le choix est assez restreint pour le mode. La seule différence entre RTLD_LAZY et RTLD_NOW réside dans l’indication que les symboles contenus dans la bibliothèque sont résolus. Ils le sont au besoin pour le premier, ce qui est préférable pour des raisons de performances, ou dès l’ouverture de la bibliothèque pour le second, ce qui permet de découvrir tous les symboles indéfinis dès l’ouverture. Deux options supplémentaires sont disponibles. RTLD_GLOBAL permet à d’autres objets d’utiliser les symboles de la bibliothèque pour résoudre des références indéfinies dans ces objets, alors que RTLD_LOCAL l’en empêche.

Lors de l’ouverture d’une bibliothèque partagée, le système recherche une fonction...

Lancer une fonction d’une bibliothèque dynamique

Problème

Vous souhaitez, après avoir chargé une bibliothèque avec dlopen(), que votre programme exécute une fonction de celle-ci.

Solution

Utilisez dlsym() pour obtenir une référence sur la fonction et l’attribuer à un pointeur. Puis exécutez-la en utilisant ce pointeur sur fonction.

Discussion

Supposons qu’une bibliothèque, chargée avec dlopen(), contienne la fonction module_test(), voici le code pour exécuter cette fonction :


/* Prototype de la fonction module_test() */ 
int module_test (int arg1, char *arg2); 
 
/* Chargement de la bibliothèque libtest.so 
 * et exécution de la fonction. 
 */ 
void *dh; 
int (*m_test) (int arg1, char *arg2); 
int resultat; 
 
if (NULL == (dh = dlopen ("libtest.so", RTLD_LAZY))) 
  { 
    fprintf (stderr, "Impossible d'ouvrir libtest.so\n"); 
    exit (EXIT_FAILURE); 
  } 
 
if (NULL == (m_test = dlsym (dh, "module_test"))) 
  { 
    fprintf (stderr, "Impossible d'obtenir module_test\n"); 
    exit (EXIT_FAILURE); 
  } 
 
resultat = m_test (1, "un"); 
printf ("Résultat = %d\n", resultat);
 

Prototypes


#include <dlfcn.h> 
 
void *dlsym (void *restrict handle, const char *restrict...

Écrire un greffon

Problème

Vous souhaitez profiter de dlopen() pour écrire un greffon.

Solution

Écrivez l’API que chaque greffon devra respecter. Créez le chargeur de greffons (appel à dlopen(), puis exécution d’une fonction d’initialisation du greffon). Créez son déchargeur si nécessaire (exécution d’une fonction de destruction du greffon, suivie d’un appel à dlclose()). Enfin terminez par l’écriture du greffon lui-même.

Discussion

L’utilisation de dlopen() et dlsym() ouvre la voie vers la programmation de greffons (plug-in) car il est très facile de charger un greffon (une bibliothèque dynamique) avec dlopen() puis d’exécuter le code contenu dans ce greffon en lançant ses fonctions, dont vous obtenez les références avec dlsym(). Il est aussi très facile de programmer des greffons qui arrêteront le programme de manière inattendue si nous ne prenons pas au préalable quelques précautions.

Lors de la réalisation d’un système de greffons, il faut d’abord écrire en une API l’interface à laquelle le greffon doit se plier. Toutes les fonctions que le programme est susceptible d’appeler et que le greffon doit implémenter doivent être référencées. Dans ce référencement se trouve non seulement le nom des fonctions, mais aussi leur prototype, qui doit être immuable à moins d’implémenter un système de gestion de versions des greffons.

Lorsque cette API est réalisée, nous réalisons la partie la plus délicate : le chargeur de greffons. Celui-ci effectue un appel à dlopen() pour charger le greffon, suivi d’un appel à dlsym() pour obtenir le nom d’une fonction générique à appeler pour initialiser le greffon. Deux implémentations sont ensuite possibles pour le chargeur et le programme.

La première implémentation consiste à stocker l’identifiant de bibliothèque retourné par dlopen(), et à s’en servir via dlsym() à chaque fois que vous souhaitez appeler une fonction d’un greffon. Cette méthode, qui fonctionne très bien, présente néanmoins...

Lancer une fonction du programme depuis la bibliothèque dynamique

Problème

Vous souhaitez proposer la possibilité à la bibliothèque dynamique d’exécuter une fonction de votre programme, telle qu’une fonction de notification d’erreur. 

Solution

Disposez d’un pointeur sur la fonction du programme à exécuter dans la bibliothèque dynamique. Après son chargement avec dlopen(), exécutez une fonction de la bibliothèque dynamique dont l’argument est un pointeur sur la fonction du programme à exécuter. Cette fonction de la bibliothèque dynamique stocke cet argument dans le pointeur réservé à cet effet.

Discussion

Supposons que la fonction du programme à exécuter depuis la bibliothèque dynamique s’appelle prog_log(), de prototype void prog_log(const char*). Voici le code à placer dans la bibliothèque dynamique :


void (*prog_log) (const char *); 
 
void 
set_prog_log (void (*f) (const char *)) 
{ 
  prog_log = f; 
}
 

Après le chargement de la bibliothèque dynamique avec dlopen(), cette fonction pourra être exécutée dans le programme :


void *handle; 
void (*set_prog_log) (const char *); 
if (NULL == (handle = dlopen ("plugin.so", RTLD_LAZY))) 
  { 
    fprintf (stderr, "Problème...

Créer une bibliothèque dynamique liée statiquement aux bibliothèques dont elle dépend

Problème

Vous générez une bibliothèque dynamique, liée à d’autres bibliothèques, mais vous souhaitez lier statiquement ces autres bibliothèques à la vôtre.

Solution

Indiquez la forme statique des bibliothèques à lier à votre bibliothèque dynamique.

Discussion

Lorsque vous déployez un programme ou une bibliothèque dynamique, vous pouvez avoir besoin de lier statiquement les bibliothèques dont la vôtre dépend. Cela offre l’avantage de ne pas dépendre de ces bibliothèques dans l’environnement où votre bibliothèque va être utilisée. Pour cela, il suffit de disposer des bibliothèques statiques dont dépend la vôtre et de les indiquer ainsi à l’éditeur de liens :


$ gcc -shared -Wl,-soname,libtest.so.1 source1.o source2.o 
/usr/lib/libglib-2.0.a -o libtest.so.1.0.0 

Cet exemple montre comment générer une bibliothèque dynamique liée statiquement à la bibliothèque glib.

Cette manière de faire présente néanmoins un danger lors d’une séance de débogage. En effet, une modification effectuée dans une bibliothèque dépendante n’est...