Aller au contenu

Accès concurrents

Ressources partagées et sections critiques

Tout système temps-réel est confronté un jour ou l’autre aux problèmes d’accès concurrents sur des ressources partagées entre plusieurs threads. Il en est de même sous Linux et plus particulièrement dans son noyau.

L’idéal est d’éviter au maximum toute variable globale ou partagée.

Si ce n’est pas possible, Linux propose 3 mécanismes principaux pour protéger ces sections critiques

  • Les mutexes
  • Les spinlocks
  • Les accès atomiques

Avec l’utilisation de mutexes ou de spinlocks, le danger de créer des deadlocks est latent.

  • Linux propose des outils de validation. Plus de détails sous Documentation/lockdep-design.txt
  • Dans la mesure de possible on peut essayer d’utiliser des algorithmes libre de loquets, tel que RCU (Read Copy Update). Plus de détails sous http://en.wikipedia.org/wiki/Read-copy-update

Les mutexes

L’interface <linux/mutex.h> propose les services reliés aux mutuxes

  • Initialisation statique d’un mutex
    DEFINE_MUTEX (name);
    
  • Initialisation dynamique d’un mutex
    void mutex_init (struct mutex* lock);
    
  • Accès à la ressource critique. Si l’accès n’est pas autorisé, le thread sera bloqué. Attention: ce service ne pas être interrompu, empêchant la destruction du thread.
    void mutex_lock (stuct mutex* lock);
    
  • Accès à la ressource critique, mais interruptible par le signal fatal (SIGKILL). Une valeur non zéro est retourné si le verrou n’a pas été tenu.
    int mutex_lock_killable (stuct mutex* lock);
    
  • Accès à la ressource critique, mais interruptible par tous les signaux
    int mutex_lock_interruptible (stuct mutex* lock);
    
  • Accès à la ressource, mais sans attente (non zéro si pas disponible)
    int mutex_trylock (struct mutex* lock);
    
  • Libération de la ressource critique
    void mutex_unlock (struct mutex* lock);
    

Les spinlocks

Les spinlocks permettent de protéger des sections critiques pour des parties de code ne pouvant pas être mis en mode sommeil (interrupt handlers).

  • L’utilisation de spinlocks demande une extrême précaution
  • Les spinlocks déclenchent le mécanisme de préemption du noyau
  • Les spinlocks restent en attente actives jusqu’à ce que l’accès soit libre

L’interface <linux/spinlock.h> propose les services reliés aux spinlocks

  • Initialisation statique d’un spinlock
    DEFINE_SPINLOCK (name);
    
  • Initialisation dynamique d’un spinlock
    void spin_lock_init (spinlock_t* lock);
    
  • Pour un verrouillage dans le contexte de threads (interruptions autorisées)
    void spin_[un]lock (spinlock_t* lock);
    
  • Pour un verrouillage entre threads et interruptions (interruptions déclenchées)
    void spin_lock_irqsave/_unlock_irqrestore(
      spinlock_t *lock, unsigned long flags);
    
  • Pour un verrouillage entre threads et interruptions software (interruptions matérielles autorisées)
    void spin_[un]lock_bh (spinlock_t* lock);
    

Les variables atomiques

Les variables atomiques peuvent être d’une grande utilité si la ressource partagée est une valeur entière. Il important de noter que l’opération n++ n’est pas atomique sur tous les processeurs (par exemple ARM).

L’interface <linux/atomic.h> propose les services reliés aux variables atomiques

  • Le type atomic_t représente un nombre entier signé (minimum 24 bits)
  • Opérations pour lire et écrire un compteur
    atomic_set (atomic_t* v, int i);
    int atomic_read (atomic_t *v);
    
  • Opérations sans valeurs de retour
    void atomic_[inc/dec] (atomic_t* v);
    void atomic_[add/sub] (int i, atomic_t* v);
    
  • Opérations retournant la nouvelle valeur
    int atomic_[inc/dec]_and_return (atomic_t* v);
    int atomic_[add/sub]_and_return (int i, atomic_t* v);
    
  • Il existe encore d’autres opérations…

Les opérations atomiques sur les bits

L’interface <linux/bitops.h> propose des opérations très efficaces pour manipuler des bits.

Sur la plupart des plateformes, ils s’appliquent sur des type unsigned long.

  • Opérations pour poser, effacer et changer bit donné
    void set_bit (int nr, unsigned long * addr);
    void clear_bit (int nr, unsigned long * addr);
    void change_bit (int nr, unsigned long * addr);
    
  • Opération pour tester un bit
    int test_bit (int nr, unsigned long *addr);
    
  • Opérations pour tester et modifier (retourne la valeur avant modification)
    int test_and_set_bit (int nr, unsigned long *addr);
    int test_and_clear_bit (int nr, unsigned long *addr);
    int test_and_change_bit (int nr, unsigned long *addr);