La conception modulaire du serveur HTTP Apache permet à l'administrateur
de choisir les fonctionnalités à inclure dans le serveur en sélectionnant
un certain nombre de modules. Les modules seront compilés en tant
qu'Objets Dynamiques Partagés (Dynamic Shared Objects ou DSOs)
qui mènent une existence séparée du fichier binaire principal
Les modules peuvent aussi être intégrés statiquement dans le
binaire
Ce document décrit l'utilisation des modules DSO ainsi que les dessous de leur fonctionnement.
Le support DSO pour le chargement de modules individuels d'Apache
httpd est
assuré par un module nommé mod_foo.so
, un
module peut être chargé en mémoire au
démarrage ou redémarrage du serveur à l'aide de
la directive httpd.conf
.
La compilation en mode DSO peut être désactivée pour certains
modules via l'option --enable-mods-static
du script
Un utilitaire permet de simplifier la création de
fichiers DSO pour les modules d'Apache httpd
(particulièrement pour les modules tiers) ; il s'agit du programme nommé
make install
du script
Afin que vous puissiez vous faire une idée des fonctionnalités DSO du serveur HTTP Apache 2.x, en voici un résumé court et concis :
Construire et installer un module Apache httpd faisant partie de la
distribution, par exemple mod_foo.c
,
en tant que module DSO mod_foo.so
:
Configure le serveur HTTP Apache avec tous les modules
activés et chargés en tant qu'objets partagés. Vous pouvez
ensuite les désactiver un à un en commentant la directive httpd.conf
.
L'argument most
de l'option
--enable-modules
indique que tous les modules
non-expérimentaux ou qui ne sont pas là à titre d'exemple seront
compilés.
mod_foo.c
, en tant que module DSO
mod_foo.so
en dehors de l'arborescence des sources
d'Apache httpd à l'aide du programme Dans tous les cas, une fois le module partagé compilé, vous devez
ajouter une directive httpd.conf
pour qu'Apache httpd active le module.
Voir la documentation sur apxs pour plus de détails.
Les clônes modernes d'UNIX proposent un mécanisme appelé édition de liens et chargement dynamiques d' Objets Dynamiques Partagés (DSO), qui permet de construire un morceau de programme dans un format spécial pour le rendre chargeable à l'exécution dans l'espace d'adressage d'un programme exécutable.
Ce chargement peut s'effectuer de deux manières : automatiquement par
un programme système appelé ld.so
quand un programme
exécutable est démarré, ou manuellement à partir du programme en cours
d'exécution via sa propre interface système vers le chargeur Unix à l'aide
des appels système dlopen()/dlsym()
.
Dans la première méthode, les DSO sont en général appelés
bibliothèques partagées ou encore bibliothèques DSO, et
possèdent des noms du style
libfoo.so
ou libfoo.so.1.2
. Ils résident dans un
répertoire système (en général /usr/lib
)
et le lien avec le programme exécutable est établi à la compilation en
ajoutant -lfoo
à la commande de l'éditeur de liens. Les
références à la bibliothèque sont ainsi codées en dur dans le fichier du
programme exécutable de façon à ce qu'au démarrage du programme, le
chargeur Unix soit capable de localiser libfoo.so
dans
/usr/lib
, dans des chemins codés en dur à l'aide d'options de
l'éditeur de liens comme -R
ou dans des chemins définis par la
variable d'environnement
LD_LIBRARY_PATH
. Le chargeur peut dès lors résoudre tous les symboles
(jusque là non encore résolus) du DSO dans le programme exécutable.
Les symboles du programme exécutable ne sont en général pas
référencés par le DSO (car c'est une bibliothèque de code à usage général
et réutilisable),
et ainsi aucune résolution supplémentaire n'est nécessaire. De son côté,
le programme exécutable ne doit accomplir aucune action particulière
pour utiliser les
symboles du DSO car toutes les résolutions sont effectuées par le chargeur
Unix. En fait, le code permettant d'invoquer
ld.so
fait partie du code de démarrage pour l'exécution qui
est lié dans tout programme exécutable non statiquement lié.
L'avantage du chargement dynamique du code d'une bibliothèque partagée est
évident : le code de la bibliothèque ne doit être stocké qu'une seule fois
dans une bibliothèque système telle que libc.so
, ce qui permet
d'économiser de l'espace disque pour les autres programmes.
Dans la seconde méthode, les DSO sont en général appelés objets
partagés ou fichiers DSO, et peuvent être nommés avec
l'extension de son choix (bien que le nom conseillé soit du style
foo.so
). Ces fichiers résident en général dans un répertoire
spécifique à un programme, et aucun lien n'est automatiquement établi avec
le programme exécutable dans lequel ils sont utilisés.
Le programme exécutable charge manuellement le DSO à l'exécution dans son
espace d'adressage à l'aide de l'appel système dlopen()
.
A ce moment, aucune résolution de symboles du DSO n'est effectuée pour le
programme exécutable. Par contre le chargeur Unix
résoud automatiquement tout symbole du DSO (non encore résolu)
faisant partie de l'ensemble de symboles exporté par le programme
exécutable et ses bibliothèques DSO déjà chargées (et en particulier tous
les symboles de la bibliothèque à tout faire libc.so
).
De cette façon, le DSO prend connaissance de l'ensemble de symboles du
programme exécutable comme s'il avait été lié statiquement avec lui
auparavant.
Finalement, pour tirer profit de l'API des DSO, le programme exécutable
doit résoudre certains symboles du DSO à l'aide de l'appel système
dlsym()
pour une utilisation ultérieure dans les tables de
distribution, etc... En d'autres termes, le programme exécutable doit
résoudre manuellement tous les symboles dont il a besoin pour pouvoir les
utiliser.
Avantage d'un tel mécanisme : les modules optionnels du programme n'ont pas
besoin d'être chargés (et ne gaspillent donc pas de ressources mémoire)
tant qu'il ne sont pas nécessaires au programme en question. Si nécessaire,
ces modules peuvent être chargés dynamiquement afin d'étendre les
fonctionnalités de base du programme.
Bien que ce mécanisme DSO paraisse évident, il comporte au moins une étape difficile : la résolution des symboles depuis le programme exécutable pour le DSO lorsqu'on utilise un DSO pour étendre les fonctionnalités d'un programme (la seconde méthode). Pourquoi ? Parce que la "résolution inverse" des symboles DSO à partir du jeu de symboles du programme exécutable dépend de la conception de la bibliothèque (la bibliothèque n'a aucune information sur le programme qui l'utilise) et n'est ni standardisée ni disponible sur toutes les plateformes. En pratique, les symboles globaux du programme exécutable ne sont en général pas réexportés et donc indisponibles pour l'utilisation dans un DSO. Trouver une méthode pour forcer l'éditeur de liens à exporter tous les symboles globaux est le principal problème que l'on doit résoudre lorsqu'on utilise un DSO pour étendre les fonctionnalités d'un programme au moment de son exécution.
L'approche des bibliothèques partagées est la plus courante, parce que c'est dans cette optique que le mécanisme DSO a été conçu ; c'est cette approche qui est ainsi utilisée par pratiquement tous les types de bibliothèques que fournit le système d'exploitation.
Les fonctionnalités ci-dessus basées sur les DSO présentent les avantages suivants :
httpd.conf
plutôt que par des options du script
apxs -i
suivie d'un apachectl restart
pour introduire une nouvelle
version de votre module fraîchement développé dans le serveur HTTP Apache
en cours d'exécution.Inconvénients des DSO :
ld -lfoo
) sur toutes les
plates-formes
(par exemple, les plates-formes basées sur a.out ne fournissent en
général pas cette fonctionnalité alors que les plates-formes basées sur
ELF le font), vous ne pouvez pas utiliser le mécanisme DSO pour tous les
types de modules. Ou en d'autres termes, les modules compilés comme
fichiers DSO sont contraints de n'utiliser que les symboles du coeur
d'Apache httpd, de la bibliothèque C
(libc
) et toutes autres bibliothèques statiques ou
dynamiques utilisées par le coeur d'Apache httpd, ou d'archives statiques
(libfoo.a
) contenant du code indépendant de la
position (PIC).
Il y a deux solutions pour utiliser un autre type de code : soit le
coeur d'Apache httpd contient déjà lui-même une référence au code, soit vous
chargez le code vous-même via dlopen()
.