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);