Verrou
Le verrou est utilisé pour qu'un même code soit exécuté par un thread à la fois.
On peut, par exemple, utiliser un verrou pour un driver ATA, afin qu'il n'y ait plusieurs écritures en même temps. On utilise alors un verrou au début de l'opération que l'on débloque à la fin.
Un équivalent en code serait:
struct Lock lock;
void ata_read(/* ... */)
{
acquire(&lock);
/* ... */
release(&lock);
};
Prérequis
Même si le verrou utilise l'instruction lock
il peut être utilisé même si la machine ne possède qu'un seul processeur.
Pour comprendre le verrou il faut avoir un minimum de base en assembleur.
L'instruction LOCK
l'instruction lock
est utilisée juste avant une autre instruction qui accède / écrit dans la mémoire.
Elle permet d'obtenir la possession exclusive de la partie du cache concernée le temps que l'instruction s'exécute. Un seul CPU à la fois peut exécuter l'instruction.
Exemple de code utilisant le lock :
lock bts dword [rdi], 0
Verrouillage & Déverrouillage
Code assembleur
pour verrouiller on doit implémenter une fonction qui vérifie le vérrou, si il est à 1, alors le verrou est bloqué, on doit attendre. si il est à 0, alors le verrou est débloqué, c'est notre tour.
pour le déverrouiller on doit juste mettre le vérou à 0.
pour le verrouillage le code pourrait ressembler à ceci :
locker:
lock bts dword [rdi], 0
jc spin
ret
spin:
pause ; pour éviter au processeur de surchauffer
test dword [rdi], 0
jnz spin
jmp locker
Ce code test le bit 0 de l'addresse contenu dans le registre rdi
(registre utilisé pour les arguments de fonctions en 64bit)
lock bts dword [rdi], 0
jc spin
si le bit est à 0 il le met à 1 et CF à 0 si le bit est à 1 il met CF à 1
jc spin jump à spin seulement si CF == 1
pour le déverrouillage le code pourrait ressembler à ceci :
unlock:
lock btr dword [rdi], 0
ret
il réinitialise juste le bit contenu dans rdi
Maintenant on doit rajouter un temps mort
parfois si un CPU a crash ou a oublié de déverrouiller un verrou il peut arriver que les autres CPU soient bloqués? Il est donc recommandé de rajouter un temps mort pour signaler l'erreur.
locker:
mov rax, 0
lock bts dword [rdi], 0
jc spin
ret
spin:
inc rax
cmp rax, 0xfffffff
je timed_out
pause ; pour gagner des performances
test dword [rdi], 0
jnz spin
jmp locker
timed_out:
; code du time out
Le temps pris ici est stocké dans le registre rax
.
Il incrémente chaque fois et si il est égal à 0xfffffff
alors il saute à timed_out
On peut utiliser une fonction C/C++ dans timed_out
Code C
Dans le code C on peut se permettre de rajouter des informations au verrou. On peut rajouter le fichier, la ligne, le cpu etc... cela permet de mieux débugger si il y a une erreur dans le code
Les fonction en c doivent être utilisées comme ceci :
void lock(volatile uint32_t* lock);
void unlock(volatile uint32_t* lock);
Si on veut rajouter plus d'informations au lock on doit faire une structure contenant un membre 32bit
struct verrou
{
uint32_t data; // ne doit pas être changé
const char* fichier;
uint64_t line;
uint64_t cpu;
} __attribute__(packed);
Vous devez maintenant rajouter des fonction verrouiller et déverrouiller qui appelleront respectivement lock et unlock
Note : si vous voulez avoir la ligne/le fichier, vous devez utiliser des #define et non des fonction
void verrouiller(verrou* v)
{
// code pour remplir les données du vérrou
lock(&(v->data));
}
void deverrouiller(verrou* v)
{
unlock(&(v->data));
}
Maintenant vous devez implementer la fonction qui serra appelé dans timed_out
void crocheter_le_verrou(verrou* v)
{
// vous pouvez log des informations importantes ici
}
maintenant vous pouvez choisir entre 2 possibilité :
-
dans la fonction crocheter_le_verrou vous continuez en attandant jusqu'à ce que le verrou soit déverrouillé
-
dans la fonction crocheter_le_verrou vous devez mettre le membre
data
du vérou v à 0, ce qui forcera le verrou à être déverrouiller
Utilisation
Maintenant, pour utiliser votre verrou, vous pouvez juste faire
struct Lock lock;
void ata_read(/* ... */)
{
acquire(&lock);
/* ... */
release(&lock);
}
Le code sera désormais exécuté seulement sur 1 cpu à la fois !
Il est important d'utiliser les verrou quand il le faut, dans un allocateur de frame, le changement de contexte, l'utilisation d'appareils...