Gestion mémoire dans le noyau Linux

Terminologie essentielle

  • Page physique (Page Frame) : unité élémentaire de gestion de la mémoire physique
  • Page virtuelle : unité d'adressage dans l'espace virtuel d'un processus
  • Numéro de page physique (PFN) : identifiant attribué par le système à chaque cadre de page selon son ordre d'adressage physique
  • MMU (Memory Management Unit) : composant matériel intégré au processeur chargé de la traduction d'adresses virtuelles en adresses physiques
  • TLB (Translation Lookaside Buffer) : mémoire cache dédiée au stockage des correspondances récentes entre pages virtuelles et physiques

Vue d'ensemble de la mémoire physique

La mémoire, au sens strict, désigne la RAM. Toutefois, du point de vue du processeur, toute unité de stockage connectée au bus système est considérée comme accessible. La gestion mémoire de Linux concerne donc cette mémoire au sens large.

Le système d'exploition manipule deux espaces distincts :

  1. L'espace physique, perceptible depuis le CPU
  2. L'espace virtuel, tel qu'observé par les applications

L'espace physique englobe non seulement la RAM, mais également les zones d'entrées/sorties via le mécanisme MMIO (Memory-Mapped I/O). La cartographie complète est consultable via /proc/iomem :

$ sudo cat /proc/iomem
00000000-00000fff : Reserved
00001000-0002ffff : System RAM
00030000-0004ffff : Reserved
00050000-0009efff : System RAM
...
100000000-17fffffff : System RAM
  117800000-118dfffff : Kernel code
  118e00000-119bfefff : Kernel rodata
  119c00000-11a056aff : Kernel data
  11a561000-11a9fffff : Kernel bss

Cet espace présente généralement des discontinuités (holes) entre ses différentes régions.

Correspondance RAM vers espace d'adressage

La projection de la RAM vers l'espace mémoire n'est pas contiguë. Une barrette de 4 Go n'occupe pas nécessairement les adresses 0 à 4 Go. Sur architecture x86, cette cartographie est établie par le BIOS. La gestion mémoire repose sur la maintenance de trois entités : les supports physiques (RAM et MMIO), l'espace d'adressage physique et l'espace d'adressage virtuel.

Translation d'adresses et MMU

L'unité de gestion mémoire réalise la conversion entre adresses virtuelles et physiques. Son fonctionnement repose sur une chaîne de traitement spécifique, simulable également par voie logicielle pour les architectures dépourvues de ce composant matériel.

Gestion du cadre physique

Le processeur administre la mémoire physique par unités de pages. Chaque page est représentée par une instance de struct page dans le noyau.

Structure des métadonnées de page

La définition de cette structure se trouve dans les en-têtes du noyau :

struct page {
    unsigned long drapeaux;
    
    union {
        struct {
            union {
                struct list_head liste_lru;
                struct {
                    void *remplissage;
                    unsigned int compte_mlock;
                };
                struct list_head liste_buddy;
                struct list_head liste_pcp;
            };
            struct address_space *espace_adressage;
            union {
                pgoff_t decalage;
                unsigned long partages;
            };
            unsigned long prive;
        };
        
        struct {
            unsigned long magie_pp;
            struct page_pool *pool;
            unsigned long _remplissage_mapping;
            unsigned long adresse_dma;
            union {
                unsigned long adresse_dma_haut;
                atomic_long_t compte_frag;
            };
        };
        
        struct {
            unsigned long tete_composee;
        };
        
        struct {
            struct dev_pagemap *carte_pg;
            void *donnees_peripherique;
        };
        
        struct rcu_head tete_rcu;
    };
    
    union {
        atomic_t _compte_cartes;
        unsigned int type_page;
    };
    
    atomic_t _compte_references;
    
#ifdef CONFIG_MEMCG
    unsigned long donnees_memcg;
#endif

#if defined(WANT_PAGE_VIRTUAL)
    void *adresse_virtuelle;
#endif

#ifdef CONFIG_KMSAN
    struct page *ombre_kmsan;
    struct page *origine_kmsan;
#endif

#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
    int _dernier_cpupid;
#endif
} _alignement_struct_page;

Le champ drapeaux combine des indicateurs d'état : les bits de poids faible définissent les propriétés de la page (voir include/linux/page-flags.h), tandis que les bits supérieurs conservent les identifiants de zone, de nœud NUMA et de section SPARSEMEM.

Allocation par système de buddies

Ce mécanisme constitue l'approche dominante pour l'attribution dynamique de blocs mémoire. Lors d'une demande d'allocation, le système identifie un bloc de taille appropriée. À la libération, les blocs contigus de même taille sont fusionnés pour former un bloc de niveau supérieur.

Mécanismes d'allocation par objets

Slab : Cette approche mise en place des réserves d'objets préinitialisés, divisées entre tampons locaux par processeur et tampon partagé global. Les tampons locaux éliminent la contention inter-cœurs en permettant à chaque CPU d'opérer sur sa propre réserve sans synchronisation.

Slob : Version allégée du slab, optimisée pour les systèmes à ressources limitées.

Slub : Évolution moderne du slab, simplifiant la structure de données et améliorant les performances sur les architectures contemporaines.

Mémoire virtuelle et ses structures

Chaque processus dispose d'une représentation de son espace d'adrsesage via mm_struct :

struct mm_struct {
    struct {
        atomic_t compte_mm;
    } ____cacheline_aligned_in_smp;

    struct maple_tree arbre_mm;
    
#ifdef CONFIG_MMU
    unsigned long (*obtenir_zone_libre) (struct fichier *f,
            unsigned long addr, unsigned long taille,
            unsigned long decalage, unsigned long drapeaux);
#endif

    unsigned long base_mmap;
    unsigned long base_mmap_legacy;
    
#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES
    unsigned long base_mmap_compat;
    unsigned long base_mmap_compat_legacy;
#endif

    unsigned long taille_tache;
    pgd_t * pgd;

#ifdef CONFIG_MEMBARRIER
    atomic_t etat_membarriere;
#endif

    atomic_t utilisateurs_mm;

#ifdef CONFIG_SCHED_MM_CID
    struct mm_cid __percpu *cid_pcpu;
    unsigned long prochain_scan_cid;
#endif

#ifdef CONFIG_MMU
    atomic_long_t octets_tables_pages;
#endif

    int nombre_cartes;

    spinlock_t verrou_tables_pages;
    struct rw_semaphore verrou_mmap;

    struct list_head liste_mm;

#ifdef CONFIG_PER_VMA_LOCK
    int sequence_verrou_mm;
#endif

    unsigned long pic_rss;
    unsigned long pic_vm;
    unsigned long total_vm;
    unsigned long vm_verrouillees;
    atomic64_t vm_epinglees;
    unsigned long vm_donnees;
    unsigned long vm_exec;
    unsigned long vm_pile;
    unsigned long drapeaux_defaut;

    seqcount_t sequence_protection_ecriture;

    spinlock_t verrou_arguments;

    unsigned long debut_code, fin_code, debut_donnees, fin_donnees;
    unsigned long debut_brk, brk, debut_pile;
    unsigned long debut_args, fin_args, debut_env, fin_env;

    unsigned long auxv_sauve[AT_VECTOR_SIZE];

    struct percpu_counter statistiques_rss[NR_MM_COUNTERS];

    struct linux_binfmt *format_binaire;
    mm_context_t contexte;

    unsigned long drapeaux;

#ifdef CONFIG_AIO
    spinlock_t verrou_ioctx;
    struct kioctx_table __rcu *table_ioctx;
#endif

#ifdef CONFIG_MEMCG
    struct task_struct __rcu *proprietaire;
#endif

    struct user_namespace *espace_utilisateur;
    struct fichier __rcu *fichier_exe;

#ifdef CONFIG_MMU_NOTIFIER
    struct mmu_notifier_subscriptions *abonnements_notificateur;
#endif

#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS
    pgtable_t pte_geante_pmd;
#endif

#ifdef CONFIG_NUMA_BALANCING
    unsigned long prochain_scan_numa;
    unsigned long decalage_scan_numa;
    int sequence_scan_numa;
#endif

    atomic_t invalidations_tlb_en_attente;
#ifdef CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH
    atomic_t invalidations_tlb_batch;
#endif

    struct uprobes_state etat_uprobes;
#ifdef CONFIG_PREEMPT_RT
    struct rcu_head liberation_differee;
#endif
#ifdef CONFIG_HUGETLB_PAGE
    atomic_long_t utilisation_hugetlb;
#endif
    struct work_struct travail_liberation_async;

#ifdef CONFIG_IOMMU_SVA
    u32 pasid;
#endif
#ifdef CONFIG_KSM
    unsigned long pages_fusion_ksm;
    unsigned long elements_rmap_ksm;
    atomic_long_t pages_zero_ksm;
#endif
#ifdef CONFIG_LRU_GEN
    struct {
        struct list_head liste;
        unsigned long bitmap;
#ifdef CONFIG_MEMCG
        struct mem_cgroup *memcg;
#endif
    } generation_lru;
#endif
    } __randomize_layout;

    unsigned long bitmap_cpu[];
};

Depuis la version 6.1, l'arbre_mm de type maple_tree remplace les listes chaînées et arbres rouge-noir pour l'organisation des régions mémoires virtuelles, offrant des performances supérieures pour les opérations de recherche et d'insertion.

Description des régions virtuelles

Les zones contiguës au sein de l'espace virtuel sont représentées par vm_area_struct :

struct vm_area_struct {
    union {
        struct {
            unsigned long debut_vm;
            unsigned long fin_vm;
        };
#ifdef CONFIG_PER_VMA_LOCK
        struct rcu_head rcu_vma;
#endif
    };

    struct mm_struct *mm;
    pgprot_t protection_pages;
    
    union {
        const vm_flags_t drapeaux_vm;
        vm_flags_t __private __drapeaux_vm;
    };

#ifdef CONFIG_PER_VMA_LOCK
    int sequence_verrou_vm;
    struct vma_lock *verrou_vm;
    bool detachee;
#endif

    struct {
        struct rb_node noeud_rb;
        unsigned long dernier_sous_arbre_rb;
    } partage;

    struct list_head chaine_vma_anon;
    struct anon_vma *vma_anon;

    const struct vm_operations_struct *operations;

    unsigned long decalage_pg;
    struct fichier * fichier_vm;
    void * donnees_privees_vm;

#ifdef CONFIG_ANON_VMA_NAME
    struct anon_vma_name *nom_anon;
#endif
#ifdef CONFIG_SWAP
    atomic_long_t info_prefetch_swap;
#endif
#ifndef CONFIG_MMU
    struct vm_region *region_vm;
#endif
#ifdef CONFIG_NUMA
    struct mempolicy *politique_vm;
#endif
#ifdef CONFIG_NUMA_BALANCING
    struct vma_numab_state *etat_numab;
#endif
    struct vm_userfaultfd_ctx contexte_userfaultfd;
} __randomize_layout;

Gestion des défauts de page

Les défauts de page constituent le mécanisme central d'établissement des correspondances entre mémoire virtuelle et physique. Cette procédure, fortement dépendante de l'architecture, doit traiter divers cas : pages anonymes, pages KSM, cache de pages, copie sur écriture, ainsi que les mappings privés et partagés.

Fonction centrale de traitement

La fonction do_page_fault (ou son équivalent architecturel) constitue le point d'entrée pour la résolution des défauts de page.

Catégories de défauts

Pages anonymes : se produit lors du premier accès à une zone mémoire allouée via malloc ou mmap sans fichier sous-jacent.

Mapping fichier : déclenché lors de l'accès initial à une région mappée sur un fichier, nécessitant le chargement depuis le stockage.

Copie sur écriture (COW) : activé lors d'une tentative d'écriture sur une page partagée entre plusieurs prcoessus, entraînant la duplication de la page pour l'initiateur de l'écriture.

Stratégies de récupération de pages

Algorithmes de remplacement

LRU (Least Recently Used) : les pages les moins récemment accédées sont candidates à l'éviction. Cette approche repose sur l'hypothèse de localité temporelle.

Algorithme de la seconde chance : variante du LRU où chaque page dispose d'un bit de référence. Lors du parcours, les pages référencées récemment se voient accorder un sursis, évitant l'élimination trop rapide de pages fréquemment utilisées.

Mécanisme OOM Killer

En situation d'épuisement critique des ressources, le sous-système OOM (Out-Of-Memory) identifie et termine le processus le plus approprié selon un score calculé à partir de critères tels que la consommation mémoire, la priorité et le temps d'exécution.

Outils de diagnostic

top

Cette commande affiche en temps réel l'activité des processus avec leurs métriques mémoire :

Colonne Signification
PID Identifiant du processus
VIRT Mémoire virtuelle totale (Ko)
RES Mémoire résidente physique (Ko)
SHR Mémoire partagée (Ko)
%MEM Pourcentage de mémoire physique utilisée

vmstat

Utilitaire de surveillance globale du sous-système mémoire :

$ vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 725336  94036 1956992    0    0   366   494  261  451  3  1 95  0  0

Les colonnes mémoire révèlent : swpd (mémoire swap utilisée), free (quantité libre), buff (tampons système), cache (cache de pages). Les colonnes si/so indiquent l'activité d'échange avec le disque, tandis que bi/bo mesurent les transferts sur les périphériques de blocs.

Étiquettes: linux-kernel memory-management MMU buddy-system slab-allocator

Publié le 3 septembre à 10h22