Verrou
le verrou est utilisé pour que un processeur accède à un code à la fois
par exemple on peut utiliser un verrou pour un driver ATA, pour éviter qu'il y ait plusieurs écritures en même temps alors on utilise un verrou au début et on le débloque à la fin
un équivalent serrait :
struct verrou ata_verrou = 0;
void ata_read(){
verrouiller(&ata_verrou);
// [CODE]
deverrouiller(&ata_verrou);
};
Préréquis
même si le verrou utilise l'instruction lock
il peut être utiliser même si on a 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é 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 éxecuter l'instruction.
exemple de code utilisant le lock :ou
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, c'est qu'il est verrouillé et que l'on doit attendre si il est à 0, c'est que on peut le déverrouiller
pour le deverrouiller 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 gagner des performances
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évérouillage le code pourrait ressembler à ceci :
unlock:
lock btr dword [rdi], 0
ret
il reset 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 donc il est 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
à chaque fois il l'incrémente 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é comme ceci :
void locker(volatile uint32_t* lock);
void unlock(volatile uint32_t* lock);
si on veut rajouter plus d'information 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);
maintenant vous devez rajouter des fonction verrouiller et déverrouiller qui appellerons locker 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
locker(&(v->data));
}
void deverrouiller(verrou* v){
unlocker(&(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 vérrou soit deverrouillé
-
dans la fonction crocheter_le_verrou vous mettez le membre
data
du vérou v à 0, ce qui forcera le vérrou à être dévérouiller
Utilisation
maintenant pour utiliser votre verrou vous pouvez juste faire
verrou ata_verrou = {0};
void ata_read(){
verrouiller(&ata_verrou);
// votre code ici
deverrouiller(&ata_verrou);
}
et le code serra éxécuté seulement à 1 cpu à la fois !
Il est important d'utiliser les verrou quand il le faut, dans un allocateur de frame, le changement de context, l'utilisation d'appareils...