Outils pour utilisateurs

Outils du site


issue93:labo_linux_1

Table des matières

1

In the last installment of our series, we introduced some small changes into the Linux kernel, altering the code that produces information on our processors in the /proc virtual file-system. In this last episode, we will try to produce some entirely new code, insert it into the kernel source, compile and execute the lot. To do so, instead of adding features to the kernel proper, it seems easier to simply create a new module. This is more convenient in not having to worry about breaking the complete kernel and making your system unresponsive - remember to do this on a non-production computer! - and the new module can simply be inserted into and removed from memory any number of times during testing. Compiling a single module also takes much less time than a complete kernel. In this episode, we will also stay within the /proc file-system, and see how it can be used to communicate not only from the kernel to the user, but also the other way around, to input information or commands into the kernel. To illustrate this, I will be writing a simple module that creates a new entry /proc/hostname. When printed, this virtual file will give us our current system hostname. But since the /proc system is bi-directional, this same file can also be written to. When this is done, the text should be received by the kernel and the hostname re-set accordingly.

Dans le dernier épisode de notre série, nous avons introduit quelques petits changements dans le noyau Linux, en modifiant le code qui produit de l'information sur nos processeurs dans le système de fichiers virtuel /proc. Dans cet épisode final, nous allons essayer de produire du code entièrement nouveau, l'insérer dans le code source du noyau, le compiler et exécuter le tout.

Pour faire cela, au lieu d'ajouter des fonctionnalités au noyau proprement dit, il semble plus facile de créer un nouveau module. C'est plus commode de ne pas avoir à vous soucier de casser le noyau complet et planter votre système - n'oubliez pas de le faire sur un ordinateur qui n'est pas en production ! - et le nouveau module peut simplement être chargé en mémoire puis retiré de nombreuses fois pendant le test. Compiler un seul module prend aussi beaucoup moins de temps qu'un noyau complet.

Dans cet épisode, nous allons aussi rester dans le système de fichiers /proc et voir comment il peut être utilisé pour communiquer non seulement du noyau vers l'utilisateur, mais aussi dans l'autre sens, pour entrer des informations ou des commandes dans le noyau. Pour illustrer cela, je vais écrire un module simple qui crée une nouvelle entrée /proc/hostname. Lors de l'affichage, ce fichier virtuel nous donnera le nom d'hôte du système actuel. Mais puisque le système /proc est bidirectionnel, on pourra également écrire dans ce même fichier. Lorsqu'on fera cela, le texte devra être réceptionné par le noyau et le nom d'hôte modifié en conséquence.

2

This is actually not a very original application of the /proc file-system, since there already exists an entry that does just that: /proc/sys/kernel/hostname. However, it will serve as an example of module construction, and we will also be granting write permission to any user, and not serving hostname changing to root as is standard behavior for the existing entry (perhaps for a good reason). The source file or files for the new module can be placed wherever we wish inside the kernel source code tree, or could even be placed in a completely separate directory if we so wished. Since we will be working within the /proc framework, I wrote a new file within the fs/proc directory that I called hostname.c

En fait, ce n'est pas une application très originale du système de fichiers /proc, car il existe déjà une entrée qui fait justement cela : /proc/sys/kernel/hostname. Cependant, elle servira comme exemple de la construction de module, et elle donnera également l'autorisation d'écriture à tout utilisateur et pas seulement à root comme c'est le cas standard pour l'entrée existante (c'est peut-être bien ainsi).

Le ou les fichiers sources pour le nouveau module peuvent être placés où nous le souhaitons dans l'arborescence du code source du noyau ou pourraient même être placés dans un répertoire séparé si nous le voulions. Puisque nous allons travailler dans le cadre de /proc, j'ai écrit un nouveau fichier dans le répertoire fs/proc que j'ai appelé hostname.c

3

CONSTRUCTING A NEW MODULE The basic points required to build a kernel module are as follow: • We need to provide code that will initialize any data structures and /proc entries we create whenever the module is loaded. • We also need to provide more code to clean up properly when the module is unloaded from memory. This is a bit different from other operating systems, in that Linux kernel modules can very well be removed during system execution if no longer needed. • Finally, we should write any callback functions that are to be invoked when operations are executed on the /proc file.

Construction d'un nouveau module

Les étapes de base nécessaires pour construire un module du noyau sont les suivantes :

• Nous devons fournir le code qui va initialiser toutes les structures de données et les entrées que nous créons dans /proc chaque fois que le module est chargé.

• Nous avons également besoin de fournir du code supplémentaire pour faire le ménage correctement lorsque le module est déchargé de la mémoire. C'est un peu différent des autres systèmes d'exploitation, car les modules du noyau Linux peuvent très bien être enlevés pendant l'exécution du système s'ils ne sont plus nécessaires.

• Enfin, nous devons écrire les fonctions de rappel qui seront invoquées lorsque des opérations seront exécutées sur le fichier /proc.

4

The concept of the callback function is rather common to operating system construction. When we are part of the system that is waiting for something to happen - for example, for a keyboard event - we have a choice of strategies we can follow. The first would be to poll the keyboard periodically, checking if a key has been pressed and new keystrokes are available to be processed. But this is not very efficient, since a lot of processing can be going on even if nobody is actually at the keyboard. The second way of responding to events is by using the interrupt mechanism. In essence, this delegates the waiting to the hardware itself. What happens is that the driver, on initialization, will prepare a certain function to process keystrokes. The function is not yet executed, but remains dormant within memory. Its address (a pointer) is passed to the interrupt management system. When a keyboard interruption is detected - when somebody pressed a key - this function is invoked. This callback function can also be used whenever we are waiting for other types of events that come from outside the kernel. They may be physical events such as a mouse-click or a packet arriving at a network interface, or logical (software) events such as, in our case, a user reads or writes a file.

Le concept de la fonction de rappel est assez courant dans la construction d'un système d'exploitation. Lorsque nous sommes une partie du système et attendons que quelque chose arrive - par exemple un événement au clavier - nous avons un choix de stratégies possibles. La première serait d'interroger le clavier périodiquement, vérifier si l'on a appuyé sur une touche et si de nouveaux caractères sont disponibles et doivent être traités. Mais ce n'est pas très efficace, puisque ça peut générer beaucoup de traitement même si personne n'est au clavier.

La seconde manière de répondre à des événements consiste à utiliser le mécanisme d'interruption. En substance, cela délègue l'attente au matériel lui-même. En fait, le pilote, à l'initialisation, préparera une certaine fonction pour traiter les saisies. La fonction n'est pas encore exécutée, mais reste en sommeil dans la mémoire. Son adresse (pointeur) est transmise au système de gestion d'interruption. Si une interruption clavier est détectée - quand quelqu'un appuie sur une touche - cette fonction est invoquée.

Cette fonction de rappel peut également être utilisée à chaque fois que nous attendons d'autres types d'événements qui viennent de l'extérieur du noyau. Cela peut être des événements physiques tels qu'un clic de la souris ou un paquet qui arrive sur une interface réseau, ou des événements logiques (applicatifs) tels que, dans notre cas, un utilisateur qui lit ou écrit un fichier.

5

INITIALIZING AND REMOVING THE MODULE The first thing we will do is insert the last two lines of the modules. These are: module_init(hostname_proc_init); module_exit(hostname_proc_exit); “module_init” indicates the function to be invoked when the module is loaded into memory, in this case “hostname_proc_init”, while “module_exit” does the same for the function to be invoked to clean up when the module is removed. Please note we are sticking to a naming convention often used within the kernel source code: all our functions will start with our module name “hostname”, followed by “proc” to indicate this code is working inside the /proc file-system, and finally we give each function a distinctive name that indicates use.

Initialisation et retrait du module

La première chose que nous allons faire, c'est insérer les deux dernières lignes des modules. À savoir :

module_init(hostname_proc_init);

module_exit(hostname_proc_exit);

« module_init » indique la fonction à invoquer lorsque le module est chargé en mémoire, dans ce cas « hostname_proc_init », tandis que « module_exit » fait de même pour la fonction à invoquer pour faire le ménage lorsque le module est retiré. Veuillez noter que nous utilisons une convention de nommage courante dans le code source du noyau : toutes nos fonctions commenceront par le nom de notre module « hostname », suivi de « proc » pour indiquer que ce code fonctionne à l'intérieur du système de fichiers /proc et, enfin, nous donnons à chaque fonction un nom distinctif qui en indique l'usage.

6

We will follow the same convention when creating a handle to reference this module itself, that can come in handy to check that we were correctly installed: static struct proc_dir_entry *hostname_entry = NULL; Now, we need to write the code (shown above) for module initialization. The function interface is standard and must be adhered to. I will be creating the new /proc entry, with access mode 0666 to give read and write access to all users (owner, group and other users). I will also be outputting quite a lot of information to the kernel log, so the “dmesg” command can be used when debugging. Note the presence of addresses to two tables: “hostname_proc_fops” and “hostname_proc_iops”. These contain references to further callback functions, that will be used to check user access permissions “hostname_proc_permission”, to provide text when our /proc entry is read in “hostname_proc_open”, and to read and parse user data when the /proc entry is written to in “hostname_proc_write” (below).

Nous allons suivre la même convention lorsque nous créerons un mécanisme pour faire référence à ce module lui-même, qui peut être utile pour vérifier qu'il a été correctement installé :

static struct proc_dir_entry *hostname_entry = NULL;

Maintenant, il faut écrire le code (ci-dessus) pour l'initialisation du module. L'interface de la fonction est standard et doit être respectée. Je vais créer la nouvelle entrée dans /proc, avec le mode d'accès 0666 pour permettre la lecture et l'écriture à tous les utilisateurs (propriétaire, groupe et autres utilisateurs). Je vais aussi émettre beaucoup d'informations vers le journal du noyau, pour que la commande « dmesg » puisse être utilisée lors du débogage.

À noter la présence des adresses de deux tables : « hostname_proc_fops » et « hostname_proc_iops ». Celles-ci contiennent des références à d'autres fonctions de rappel, qui seront utilisées pour vérifier les autorisations d'accès de l'utilisateur « hostname_proc_permission », pour fournir un texte quand notre entrée dans /proc est lue dans « hostname_proc_open », et pour lire et analyser les données de l'utilisateur lorsqu'il écrit dans l'entrée dans /proc dans « hostname_proc_write » (ci-dessous).

7

Other operations are left with their default handler functions, “seq_read”, etc. When the module is removed from memory (shown top right), in our case very little housekeeping is needed, just removing the /proc entry with the “proc_remove” function. As before, I tend to provide much logging information for ease of debugging. Most of this should be removed if this module were to enter production status. RESPONDING TO READING AND WRITING THE /proc ENTRY As in the previous part of this series, responding to a user reading the /proc entry is split up into two functions. Function “hostname_proc_open” is the actual callback routine provided in “hostname_proc_fops”. But this has access to only the /proc entry file and inode pointers. For ease of access, it is nice to use the “single_open” function to provide a sequential file access pointer “m” that can then be used with the “printf” to write formatted output to the file. Code is shown bottom right.

D'autres opérations sont laissées avec leurs fonctions de gestion par défaut, « seq_read », etc.

Lorsque le module est supprimé de la mémoire (voir en haut à droite), dans notre cas, très peu de ménage est nécessaire : il suffit de retirer l'entrée de /proc avec la fonction « proc_remove ». Comme auparavant, j'ai tendance à fournir beaucoup d'informations de journalisation pour faciliter le débogage. La plupart de ces informations devraient être retirées si ce module passait en production.

Réponses aux lectures et écritures dans l'entrée de /proc

Comme dans la partie précédente de cette série, la réponse quand un utilisateur lit l'entrée dans /proc est divisée en deux fonctions. La fonction « hostname_proc_open » est la routine de rappel fournie dans « hostname_proc_fops ». Mais elle n'a accès qu'au fichier d'entrées et aux pointeurs d'inodes de /proc. Pour faciliter l'accès, c'est mieux d'utiliser la fonction « single_open » qui fournit un pointeur « m » d'accès séquentiel aux fichiers qui peut ensuite être utilisé avec le « printf » pour écrire une sortie formatée dans le fichier. Le code est en bas à droite.

8

Note the use of the “utsname” mechanism to retrieve our system's current hostname. As set out in the uname manual page, there is actually no requirement for hostname and nodename to be the same for POSIX compliance. However, this does seem to be the case for Linux. With this in place, the user will be able to read from our /proc entry: cat /proc/hostname system hostname is currently: alan-vaio write new name to this file to change hostname Responding to writing to our entry is a bit more complex. Data written by the user process will be accessible by the “user_data” pointer, and the number of characters available to use will be in “len”. But this data is within a user-space data structure, that must be copied into an equivalent kernel-space table before working on it.

Notez l'utilisation du mécanisme « utsname » pour récupérer le nom d'hôte actuel de notre système. Comme indiqué dans la page de manuel uname, il n'est en fait pas nécessaire que hostname et nodename soient identiques pour la conformité POSIX. Toutefois, cela semble être le cas pour Linux.

Avec cela en place, l'utilisateur sera en mesure de lire notre entrée dans /proc :

cat /proc/hostname

le nom d'hôte du système est actuellement : alan-vaio

écrivez un nouveau nom dans ce fichier pour modifier le nom d'hôte.

Répondre à l'écriture dans notre entrée est un peu plus complexe. Les données écrites par le processus utilisateur seront accessibles par le pointeur « user_data », et le nombre de caractères disponibles sera dans « len ». Mais ces données sont dans une structure de données de l'espace utilisateur, qui doit être recopiée dans une table équivalente de l'espace noyau avant de travailler dessus.

9

We should also be very careful to check user input for consistency. Any actions we take from within kernel code based on incorrect user entry can affect the working of the complete system, so better be extra cautious here. In our case, it will be a simple case of copying over the first characters supplied by the user, until a non-printable character (i.e. anything with code less than a space) comes up. If this number of characters is less than admissible hostname length (check! check! check it over again!), copy the new hostname over to the corresponding utsname table. Finally, we should return the number of characters we have effectively read from user input before closing this function. This is taken into account by the system; if less than the available number of characters have actually been treated, the system will invoke this function once more - or as many times as necessary - to handle all input. So we will simply return the same number of characters “len” (given as input length) to indicate we have handled them all and no longer need to be invoked. Code is shown right.

Nous devons également nous assurer de bien vérifier la cohérence de l'entrée utilisateur. Les actions que nous ferons de l'intérieur du code du noyau basées sur une entrée utilisateur incorrecte peuvent affecter le fonctionnement du système complet, donc mieux vaut être très prudent ici.

Dans notre cas, il s'agit simplement de copier les premiers caractères fournis par l'utilisateur, jusqu'à ce qu'un caractère non imprimable (c'est-à-dire dont le code est avant celui de l'espace) arrive. Si ce nombre de caractères est inférieur à la longueur autorisée du nom d'hôte (vérifiez ! vérifiez ! vérifiez encore une fois !), recopiez le nouveau nom d'hôte dans la table utsname correspondante.

Enfin, nous devrions renvoyer le nombre de caractères que nous avons effectivement lus dans la saisie de l'utilisateur avant de fermer cette fonction. Ceci est pris en compte par le système ; si on a traité moins que le nombre de caractères disponibles, le système appellera cette fonction une fois de plus - ou autant de fois que nécessaire - pour gérer toutes les entrées. Donc, nous allons tout simplement retourner le même nombre de caractères « len » (donné comme longueur d'entrée) pour indiquer que nous les avons tous traités et qu'ils n'ont plus besoin d'être invoqués. Le code est visible à droite.

10

Once this is in place, any user will be able to write text to our entry to provoke a change of hostname: echo “our-new-hostname” > /proc/hostname hostname our-new-hostname THE FINAL MODULE SOURCE CODE Our module will not compile yet, since I left out all the include sentences that will be needed to indicate function prototypes. These need to be inserted at the top of our file. It may also be a nice idea to pass the compiler some data on what this module is, its author, and the license under which it is distributed: MODULE_AUTHOR(“Alan Ward”); MODULE_LICENSE(“GPL v2”); MODULE_DESCRIPTION(“hostname module for Full Circle Magazine”);

Une fois ceci mis en place, n'importe quel utilisateur pourra écrire un texte dans notre entrée et provoquer un changement de nom d'hôte :

echo “notre-nouveau-nom-d-hôte”> /proc/hostname

hostname

notre-nouveau-nom-d-hôte

Le code source final du module

Notre module ne compilera pas encore, car j'ai laissé de côté toutes les instructions include qui sont nécessaires pour indiquer les prototypes de fonction. Elles doivent être insérées au début de notre fichier. C'est aussi une bonne idée de passer au compilateur des indications sur ce qu'est ce module, son auteur, et la licence sous laquelle il est distribué :

MODULE_AUTHOR(“Alan Ward”); MODULE_LICENSE(“GPL v2”); MODULE_DESCRIPTION(“hostname module for Full Circle Magazine”);

11

This information will be inserted within the module itself, and may be consulted with the “module_info” utility command: $ modinfo hostname.ko filename: /home/alan/Escriptori/linux/fs/proc/hostname.ko description: hostname module for Full Circle Magazine license: GPL v2 author: Alan Ward srcversion: 431F7E34A05708273893D24 depends: vermagic: 3.13.0-24-generic SMP mod_unload modversions Once we have all the bits and pieces, the final module code can be assembled as follows: http://pastebin.com/5d6KxCRZ

Cette information sera insérée dans le module lui-même et peut être consultée avec la commande utilitaire « module_info » :

$ modinfo hostname.ko filename: /home/alan/Escriptori/linux/fs/proc/hostname.ko description: hostname module for Full Circle Magazine license: GPL v2 author: Alan Ward srcversion: 431F7E34A05708273893D24 depends: vermagic: 3.13.0-24-generic SMP mod_unload modversions

Une fois que nous avons tous les morceaux, le code final du module peut être assemblé comme ceci : http://pastebin.com/5d6KxCRZ

12

COMPILING AND INSTALLING THE NEW MODULE When our new module is compiled, it will need to be linked against existing kernel data structures and functions. The easiest way to do this is to integrate the new module within the existing source code directory make structure. Edit the Makefile in the same directory, and add: obj-m := hostname.o We can now proceed to compile all modules, including our new one, by going up to the kernel source code root directory and executing make with the commands: cd ../.. make modules

Compiler et installer le nouveau module

Lorsque notre nouveau module est compilé, il aura besoin d'être lié à des structures et des fonctions de données du noyau existant. La meilleure façon de le faire est d'intégrer le nouveau module dans la structure make existante du code source. Éditez le Makefile dans le même répertoire, et ajoutez :

obj-m := hostname.o

Nous pouvons maintenant procéder à la compilation de tous les modules, y compris le nôtre, en remontant dans le répertoire racine du code source du noyau, et en exécutant make, avec les commandes :

cd ../..

make modules

13

Alternatively, it may be practical to compile just the modules in this directory from within the same directory with the command: make -C /lib/modules/`uname -r`/build M=$PWD modules When this is finished, we can install the module within the running kernel with: sudo insmod hostname.ko and use it as indicated. The module may be unloaded if desired with: sudo rmmod hostname Reading the system kernel logs is advisable to check everything is working correctly: dmesg | tail

Comme alternative, il peut être pratique de compiler seulement les modules dans ce répertoire depuis ce même répertoire avec la commande :

make -C /lib/modules/`uname -r`/build M=$PWD modules

Lorsque c'est terminé, nous pouvons installer le module dans le noyau en cours d'exécution avec :

sudo insmod hostname.ko

et l'utiliser comme indiqué. Le module peut être déchargé le cas échéant avec :

sudo rmmod hostname

Il est conseillé de lire les journaux système du noyau pour vérifier que tout fonctionne correctement :

dmesg | tail

14

[ 7501.047170] hostname loading [ 7501.047178] hostname /proc entry created [ 8095.253713] hostname_proc_write, len=17 [ 8095.253722] wrote 17 bytes [ 8095.253726] hostname=our-new-hostname [ 8381.501772] hostname unloading [ 8381.501784] hostname /proc entry removed

[ 7501.047170] hostname loading

[ 7501.047178] hostname /proc entry created

[ 8095.253713] hostname_proc_write, len=17

[ 8095.253722] wrote 17 bytes

[ 8095.253726] hostname=notre-nouveau-nom-d-hôte

[ 8381.501772] hostname unloading

[ 8381.501784] hostname /proc entry removed

15

We are now at the end of our six-part series on compiling the Linux kernel. Altering, compiling and installing a kernel is perhaps one of the more demanding activities we can do on our favorite operating system, but it is perfectly achievable with a little patience and going about it in a systematic way. OK, so a little stubbornness may also help when things do not go as expected the first few times. In any case, the main point that I would like to make is that this can be done – not by computer wizards and professional hackers, but by “normal” people… simply should they choose to go down this path. Doing so will certainly enrich your experience, as you may end up learning things about your computer you may never have suspected. I certainly did when preparing this series. This, in turn, means that an open-source kernel running an open-source operating system has some real possibilities of getting checked by many eyes, and having improvements proposed and implemented when needed. Although the reader may choose not to investigate the kernel in person, we all benefit from the very existence of the possibility with a more stable and up-to-date computing platform. Kudos to everybody who has participated in developing the Linux kernel for giving it to the world; it is not said often enough.

Nous sommes maintenant à la fin de notre série en six parties sur la compilation du noyau Linux. Modifier, compiler et installer un noyau est peut-être l'une des activités les plus exigeantes que nous pouvons faire sur notre système d'exploitation favori, mais c'est parfaitement réalisable avec un peu de patience et en le faisant d'une manière systématique. Bon, un peu d'entêtement peut également aider lorsque les choses ne se passent pas comme prévu les premières fois.

En tout cas, le point principal que je voudrais souligner, c'est que cela peut être fait - non pas par des experts en informatique et des pirates professionnels, mais par des gens « normaux »… à condition qu'ils choisissent de tenter le coup. Cela va certainement enrichir votre expérience, car vous pourriez finir par apprendre des choses sur votre ordinateur que vous n'aviez jamais soupçonnées. C'est ce qui m'est arrivé en préparant cette série.

Cela signifie aussi qu'un noyau Open Source exécutant un système d'exploitation Open Source a de réelles chances d'être vérifié par de nombreux yeux, et que des améliorations soient proposées et mises en œuvre au besoin. Bien que le lecteur puisse choisir de ne pas fouiller le noyau en personne, grâce à l'existence même de cette possibilité, nous bénéficions tous d'une plateforme informatique plus stable et à jour.

Bravo à tous ceux qui ont participé à l'élaboration du noyau Linux pour l'offrir au monde ; on ne le dit pas assez souvent.

issue93/labo_linux_1.txt · Dernière modification : 2015/03/16 15:13 de andre_domenech