Copyright © 2004 Pramode C.E.
Copyright © 2004 Joëlle Cornavin
Copyright © 2004 Emmanuel Araman
Article paru dans le n°106 de la Gazette Linux de septembre 2004.
Traduction française par Joëlle Cornavin
<jcornavi CHEZ club TIRET internet POINT fr>
.
Relecture de la traduction française par Emmanuel Araman
<Emmanuel CHEZ araman POINT org>
.
Article publié sous Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.
Un étudiant est venu me dire que son système Linux ne s'arrêtait pas automatiquement quand il appuyait sur le bouton d'alimentation. Il s'agissait d'un problème d'ACPI (Advanced Configuration and Power Interface), qui n'était pas configuré correctement sur sa machine. C'était une occasion d'en savoir un peu plus sur la gestion de l'alimentation et son implémentation sous Linux. Ce thème ne me passionnait pas particulièrement, mais la lecture de quelques fichiers dans drivers/acpi m'a convaincu que je pourrais le transformer en une démonstration intéressante sur la façon d'appréhender la lecture et le déchiffrement du code du noyau.
La gestion de l'alimentation est un problème épineux dans les systèmes modernes — le système d'exploitation devrait être capable de décider des aspects comme le niveau de charge de la batterie, la température du processeur, etc. et de prendre des mesures intelligentes. L'ACPI est conçue dans le but de donner au système d'exploitation autant de souplesse que possible, en traitant les problèmes de gestion de l'alimentation. J'ai pu le comprendre en faisant une rapide recherche sur Google. Quand un article mentionnait que l'ensemble des spécifications ACPI dépassait largement 500 pages, j'en ai conclu que ce n'était pas ma tasse de thé. Je voulais juste savoir ce que le système d'exploitation fait pour arrêter et mettre hors tension mon système proprement quand j'appuie sur le bouton d'alimentation : inutile de lire 500 pages d'un manuel pour comprendre cela.
La première chose était de découvrir si mon noyau renfermait du code ACPI compilé. Un rapide
dmesg | grep ACPI |
a résolu le problème — c'est le cas. Comme avec toutes choses complexes (USB par exemple), on peut à juste titre émettre l'hypothèse selon laquelle le noyau pourrait contenir du code de « bas niveau » (core) pour gérer tous les aspects vraiment ardus, comme communiquer avec le BIOS ACPI et déterminer tous les « événements » qui s'y sont produits (appuyer sur le commutateur d'alimentation est un événement). Le code qui « réagit » à ces événements devrait être beaucoup plus simple et serait la plupart du temps écrit sous forme de module. En gardant à l'esprit la philosophie de base d'Unix d'une politique du « pousser » autant que possible vers l'utilisateur, vous devinerez que cette « réaction » consisterait le plus souvent faire savoir à un processus utilisateur que quelque chose s'est produit. Maintenant, comment un pilote de périphérique fait-il savoir à un processus utilisateur que quelque chose s'est produit ? Deux des méthodes les plus simples consistent soit à employer un élément de fichier dans proc, soit à faire appel à un fichier de périphérique, l'élément de fichier dans proc étant la plus probable. Je suis assez sûr, en regardant dans /proc, d'avoir vu un répertoire acpi.
Le dernier maillon de la chaîne serait un programme au niveau utilisateur qui lit un fichier donné sous /proc/acpi et effectue quelques actions en fonction des informations d'événements qu'il obtient du noyau — par exemple un programme appelé acpid. Un ps ax m'a montré qu'un tel programme tournait. L'exécution d'un
strace -p <pid> |
a affiché le programme effectuant un select sur les deux descripteurs de fichier 3 et 4. Tuer acpid et le lancer à nouveau comme suit :
strace acpid |
a affiché la ligne suivante comme faisant partie de la sortie :
> open("/proc/acpi/event", O_RDONLY); |
Voilà ! Il suffit de lire /proc/acpi/event et vous saurez que tous les « événements » de gestion de l'alimentation se sont produits. J'ai tué acpid et lancé un
cat /proc/acpi/event |
Comme prévu, le processus s'est bloqué. Appuyer sur le bouton d'alimentation a provoqué l'affichage de la ligne suivante :
button/power PWRF 00000080 00000001 |
La chaîne est presque complète. Comment le code core ACPI fait-il savoir qu'on a appuyé sur le bouton ? Le noyau ne peut pas continuer à utiliser un genre de boucle active (busy loop) — les interruptions sont les seules issues. Il est presque certain qu'un cat /proc/interrupts affichera :
CPU0 CPU1 0: 11676819 11663518 IO-APIC-edge timer 1: 8998 7247 IO-APIC-edge i8042 2: 0 0 XT-PIC cascade 8: 1 0 IO-APIC-edge rtc 9: 3 1 IO-APIC-level acpi 12: 84 0 IO-APIC-edge i8042 |
Le reste a été supprimé. |
Observons l'IRQ 9 — c'est ce à quoi nous nous intéressons. Appuyer sur le bouton a provoqué l'incrémentation du compteur d'interruption.
Voici la chaîne complète :
Bouton d'alimentation | | IRQ 9 | | Code ACPI core | | Pilote du bouton | | Lecture de /proc/acpi/event via le programme utilisateur |
Un rapide coup d'½il dans /usr/src/linux-2.6.5/drivers montre un répertoire appelé acpi, contenant un fichier dénommé button.c. Il s'agit probablement du code du pilote plug-in qui a répondu aux événements bouton. Un autre fichier appelé event.c comporte des routines qui répondent aux requêtes d'ouverture/lecture dans l'espace utilisateur.
Une requête de « lecture » dans l'espace utilisateur sur le fichier /proc/acpi/events invoque acpi_system_read_event
. À son tour, cette fonction invoque acpi_bus_receive_event
qui bloque si le code du pilote du bouton n'a posté aucun événement. Les événements sont postés par une fonction appelée acpi_bus_generate_event
définie dans le fichier bus.c.
Se faire une « haute idée » du fonctionnement du code du pilote du bouton est assez simple. Quand vous essayez d'aborder le code du pilote comme celui-ci, il serait bon de supprimer quelques lignes, de façon à pouvoir mieux vous concentrer sur l'essentiel. Les fonctions acpi_button_info_seq_show
, acpi_button_info_open_fs
, acpi_button_state_seq_show
, acpi_button_state_open_fs
, acpi_button_add_fs
et acpi_button_remove_fs
peuvent sans risque être retirées. Une simple printk
au début des fonctions que nous trouvons intéressantes nous donnera une certaine idée du flot de contrôle de la commande. Voici la première version abrégée de button.c :
Voici ce que j'ai remarqué quand le module a été compilé séparément et chargé dans le noyau (2.6.5) :
acpi_button_add
a été invoquée deux fois lors du chargement du module.
Chaque fois que j'ai appuyé sur le bouton d'alimentation, acpi_button_notify
a été appelée une fois.
acpi_button_remove
a été appelée deux fois lors du déchargement du module.
Il est à nouveau temps de nous livrer à quelques conjectures. La fonction acpi_button_init
appelle acpi_bus_register_driver
avec l'adresse d'un objet de type struct acpi_driver
:
static struct acpi_driver acpi_button_driver = { .name = ACPI_BUTTON_DRIVER_NAME, .class = ACPI_BUTTON_CLASS, .ids = "ACPI_FPB,ACPI_FSB,PNP0C0D,PNP0C0C,PNP0C0E", .ops = { .add = acpi_button_add, .remove = acpi_button_remove, }, }; |
L'hypothèse est que acpi_button_add
communiquerait avec le code core de plus bas niveau et assurerait l'invocation de quelques fonctions gestionnaires lorsque des événements comme l'activation d'un bouton d'alimentation ou de veille sont détectés (donc — un bon nombre d'imprécisions ici — le dialogue entre le code source et un bouton POWER et POWERF ainsi qu'un bouton SLEEP et SLEEPF. Est-ce que quelqu'un qui connaît mieux le code pourrait m'éclairer à propos de POWERF et SLEEPF ?). Puisque des gestionnaires doivent être invoqués pour plusieurs types de boutons, la fonction acpi_bus_register_driver
devrait examiner le champ .ids de la structure et analyser un par un les noms qu'elle contient, en invoquant la fonction appelant la fonction acpi_button_add
à chaque fois qu'elle rencontre un autre nom (ce ne sont que des conjectures — je n'ai pas vraiment regardé dans scan.c qui implémente la fonction acpi_bus_register_driver
. L'ennui est que je ne comprends pas pourquoi add
n'est appelée que deux fois).
Dans la révision suivante de button.c, je ne garde qu'un nom, ACPI_FPB
dans le champ .ids et réduis acpi_button_add/acpi_button_remove
pour traiter uniquement le cas du bouton d'alimentation. La ligne la plus importante à rechercher dans cette fonction est l'invocation de acpi_install_fixed_event_handler
, qui déclare la fonction acpi_button_notify_fixed
comme gestionnaire à invoquer quand le système ACPI détecte une activité du bouton.
Le gestionnaire (acpi_button_notify_fixed
) appelle acpi_button_notify
, qui à son tour appelle acpi_bus_generate_event
. Souvenez-vous qu'une lecture sur /proc/acpi/events a été bloquée par acpi_bus_receive_event
. La lecture (et le processus au niveau utilisateur) sort du bloc quand acpi_bus_generate_event
est invoquée !
Modifions le pilote de manière à ce qu'il utilise une interface différente pour communiquer avec le « pays magique des utilisateurs » : un pilote en mode caractère simple (bien sûr, c'est une idée plutôt stupide, mais nous devons effectuer un changement !). La méthode 'lecture' du pilote va s'endormir en appelant interruptible_sleep_on
alors que la fonction acpi_button_notify_fixed
la réveille.
Supposons que le pilote soit réservé à un nombre majeur de 254. Nous créons un fichier de périphérique :
mknod button c 254 0 |
et essayons un cat button. Le processus de lecture va s'endormir. Maintenant, nous appuyons sur le commutateur Marche et il sort immédiatement du bloc !
Un script shell simple (nous l'appellerons silly_acpid) :
#!/bin/sh cat /home/pce/button poweroff |
Lancez cette commande en arrière-plan et appuyez sur le commutateur Marche — le système s'arrête et se met hors tension comme par magie !
Le numéro d'octobre 2003 de la revue ACM Queue offre un article en préambule sur l'ACPI destiné aux débutants ; sa lecture est digne d'intérêt. Le projet Linux ACPI a sa propre page d'accueil ici. Vous pouvez télécharger le code source du programme acpi daemon ici. Le processus de compilation du module pour les noyaux de la série 2.6 est différent de celui de la série 2.4. Vous trouverez plus d'informations dans /usr/src/linux/Documentation/kbuild/.
Je suis formateur et travaille pour IC Software® à Kerala (Inde). J'aurais aimé travailler dans le domaine de la chimie organique, mais je donne le meilleur de moi-même à ma seconde passion, jouer avec Linux et enseigner la programmation !