Semaine Athens Linux Device Drivers


Rappels mémoire virtuelle et physique

Sur la quasi totalité des plates-formes sur lesquelles le noyau Linux tourne, en fonctionnement normal, le processeur (et donc les applications, qu'elles tournent en espace utilisateur ou en espace noyau) manipule des adresses dites virtuelles.

Avant d'être transmises à l'extérieur du processeur pour faire un accès mémoire, ces adresses virtuelles sont traduites en adresses physiques (celles réellement utilisées par les bus et la mémoire) par un composant appelé la MMU (Memory Management Unit).

La MMU utilise une table nommée la table des pages (page table) pour effectuer une traduction entre une adresse virtuelle et une adresse physique. Cette table est mise en place par le système d'exploitation et est en général propre à chaque application.

La granularité de cette traduction est une page (dont la taille dépend de l'architecture, en général 4 Kio). L'espace d'adressage virtuel et l'espace d'adressage physique sont découpés en pages. Pour chaque page de l'espace d'adressage virtuel, la table des pages indique à la MMU la page de l'espace d'adressage physique correspondante.

D'autres informations sont présentes dans la table des pages. Pour chaque entrée, il y a notamment un drapeau de validité (y-a-t-il réellement une page physique correspondant à la page virtuelle demandée ?) et des informations sur les droits d'accès (écriture et/ou exécution, accès autorisé en mode utilisateur ou pas, etc.).

Seul du code s'exécutant en espace noyau peut modifier la table des pages utilisée par la MMU ce qui empêche une application s'exécutant en mode utilisateur de modifier la table des pages.

Lorsqu'il n'existe pas d'entrée valide dans la table des pages pour une adresse virtuelle présentée à la MMU, ou lorsque les informations de protection n'autorisent pas le type d'accès demandé, la MMU lève une exception pour permettre d'interrompre le programme et de redonner la main au système d'exploitation qui peut ainsi analyser la situation.

Ce mécanisme permet plusieurs fonctions importantes :

Espace d'adresse virtuel d'un processus

Sur un système Linux 32 bits (pour les systèmes 64 bits, voir Documentation/x86/x86_64/mm.txt pour x86_64, Documentation/arm64/memory.txt pour AArch64 (ARM 64 bits)...), sauf configuration particulière, l'espace d'adressage virtuel est organisé de la manière suivante :

Le noyau peut donc, directement, accéder à un peu moins d'un Gio de l'espace d'adressage physique. Cette contrainte peut être contournée sur les systèmes 32 bits par le mécanisme High Memory (voir Documentation/vm/highmem.rst) et n'existe pas sur les systèmes 64 bits où toute la mémoire physique peut facilement être mappée dans l'espace d'adressage virtuel.

Les application quant à elles ne peuvent disposer de plus de 3 Gio de mémoire (toujours dans le cas d'un système 32 bits).

Allocation mémoire depuis le noyau

Le noyau fournit plusieurs mécanismes pour permettre d'allouer de la mémoire pour le code s'exécutant en espace noyau (le noyau lui-même et les pilotes).

Drapeaux utilisés lors de l'allocation

La plupart des fonctions d'allocation prennent en argument des drapeaux (type gfp_t) dont voici les valeurs les plus courantes :

Fonctions d'allocation mémoire "classiques"

Dans include/linux/slab.h on trouve les fonctions suivantes permettant d'allouer de la mémoire. La zone mémoire retournée est contiguë en mémoire physique.

Fonctions devm_*

Il existe une variante des fonctions précédentes. Elles sont définies dans include/linux/device.h.

Elles prennent en premier argument une structure struct device représentant un périphérique.

Leur particularité est que la mémoire allouée est automatiquement libérée lorsque le périphérique est supprimée. Elles permettent de réduire le risque de fuites mémoires dans les pilotes de périphérique.

Ces fonctions sont donc recommandées dans le contexte d'un pilote de périphérique.

Plus d'information sur ces fonctions est disponible dans Documentation/driver-model/devres.txt.

Fonctions d'allocation d'une ou plusieurs pages

Lorsqu'il est nécessaire d'allouer une ou plusieurs pages consécutives, on peut utiliser les fonctions suivantes :

Autres fonctions d'allocation

D'autres mécanismes d'allocation sont disponibles dans le noyau :

kasprintf

Le noyau fournit également une fonction équivalente à la fonction asprintf de la glibc. Cette fonction combine snprintf (qui permet de créer une chaîne de caractères à partir d'une chaîne de format et d'arguments supplémentaires) et kmalloc.

L'inconvénient majeur de snprintf est que la chaîne produite est stockée dans un tampon passé en argument à la fonction. Si la chaîne résultante est trop longue pour le tampon, elle sera tronquée.

kasprintf calcule la taille de la chaîne produite et alloue automatiquement de la mémoire en quantité suffisante pour la stocker.

char *kasprintf(gfp_t gfp, const char *fmt, ...);

La mémoire allouée par kasprintf doit être libérée une fois qu'elle n'est plus utilisée via kfree.

Une variante devm_ existe qui permet de libérer la mémoire automatiquement lorsque le périphérique est supprimé :

char *devm_kasprintf(struct device *dev, gfp_t gfp, const char *fmt, ...);

Fuites de mémoire

Lorsque vous allouez de la mémoire, n'oubliez pas de la libérer lorsque vous n'en avez plus besoin. Dans le cas contraire, vous créez une fuite mémoire. Cela peut être très problématique car cette mémoire ne sera jamais libérée (et donc réutilisable par une autre partie du noyau), sauf lors du redémarrage du noyau.

C'est pourquoi il est nécessaire de faire très attention et il est recommandé d'utiliser les fonctions devm_ dans les pilotes de périphériques (ce qui ne vous libère pas de l'obligation de libérer explicitement la mémoire, elles constituent juste un filet de sécurité).


Retour au sommaire du cours


© Copyright 2017 Guillaume Duc. Le contenu de cette page est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International (à l'exception des exemples de code tirés du noyau Linux et qui sont distribués sous leurs licences d'origine).

Licence
Creative Commons