Présentation de ByteAtomic
Le développement de programmes multithreads implique souvent la lecture et la modification de données partagées de manière concurrente. Si ces opérations ne sont pas atomiques, cela peut entraîner des courses de données et un comportement indéfini dans des langages comme Rust et C++.
Pour prévenir cela, les mécanismes de verrous peuvent être utilisés pour protéger les données pendant leur lecture. Cependant, les verrous traditionnels présentent le risque de dédeadlocks, ce qui est inacceptable dans les systèmes critiques et à haute fiabilité.
Contexte technique
Une approche courante pour atténuer les courses de données sans utiliser des verrous bloquants consiste à utiliser un verrou de séquence. Ce verrou contient les données partagées et un compteur atomique qui a une valeur impaire lorsque les données sont mises à jour.
struct SequenceLock<T: Copy + Send> {
counter: AtomicUsize,
data: UnsafeCell<T>,
}
Le problème est que même si le lecteur détecte que les données ont été modifiées et supprime la copie avant de l'utiliser, l'acte de copie des données non atomiques déclenche toujours un comportement indéfini.
Implémentation de ByteAtomic
Pour cibler les systèmes critiques et à haute fiabilité, iceoryx2 fournit une bibliothèque de constructions sans verrous basées sur des mécanismes similaires à un verrou de séquence. Pour rendre ces constructions sûres et correctes, il est nécessaire de réaliser des copies de mémoire atomiques au niveau du byte, en garantissant ainsi l'absence de courses de données.
C'est pourquoi nous avons implémenté l'enveloppe atomique au niveau du byte, ByteAtomic, qui fournit des opérations de lecture et d'écriture atomiques au niveau du byte sur son type interne.
#[repr(C)]
pub struct FixedSizeByteAtomic<T: AtomicCopy, const SIZE: usize> {
data: [AtomicU8; SIZE],
_inner_type: PhantomData<T>,
}
L'implémentation de ByteAtomic a subi des affinements pour résoudre les complexités de la sécurité de la mémoire. La version initiale de notre enveloppe ByteAtomic ressemblait à ceci :
Analyse scientifique
La mise en œuvre de ByteAtomic nécessite que le type interne T implémente le trait AtomicCopy d'iceoryx2 pour les types qui peuvent être copiés de manière atomique. Cela fournit for_each_field(), un accesseur par champ pour la copie au niveau du byte.
Cette méthode applique la fonction de rappel fournie à chaque paire de décalage-taille de chaque champ dans T. Avec cela, new() copie uniquement les octets initialisés de la valeur dans le champ de données, en évitant efficacement les octets de remplissage potentiels.