Maison >Tutoriel système >Linux >Explication détaillée du mécanisme RCU dans le noyau Linux

Explication détaillée du mécanisme RCU dans le noyau Linux

WBOY
WBOYavant
2024-02-10 21:09:111358parcourir

Le noyau Linux est un système complexe qui doit gérer divers problèmes de concurrence, tels que la planification des processus, la gestion de la mémoire, les pilotes de périphériques, les protocoles réseau, etc. Afin de garantir la cohérence et l'exactitude des données, le noyau Linux fournit une variété de mécanismes de synchronisation, tels que des verrous tournants, des sémaphores, des verrous en lecture-écriture, etc. Cependant, ces mécanismes de synchronisation présentent quelques défauts, tels que :

Explication détaillée du mécanisme RCU dans le noyau Linux

  • Les verrous de rotation feront perdre du temps au processeur en attente et ne peuvent pas être utilisés dans les noyaux préemptifs
  •  ;
  • Le sémaphore entraînera la mise en veille et le réveil du processus, augmentant ainsi la surcharge de changement de contexte
  •  ;
  • Les verrous en lecture-écriture entraîneront une famine des écrivains ou des lecteurs, et lorsqu'il y a plus de lecteurs que d'écrivains, l'écrivain devra également payer les frais généraux liés à l'acquisition du verrou.

Alors, existe-t-il un meilleur mécanisme de synchronisation ? La réponse est oui, c'est RCU (Read Copy Update). RCU est un mécanisme de synchronisation basé sur le modèle de publication-abonnement, qui peut réaliser des opérations de lecture efficaces et des opérations de mise à jour à faible latence. L'idée de base de RCU est la suivante :

  • L'opération de lecture n'a pas besoin d'acquérir de verrous, utilisez simplement rcu_read_lock() et rcu_read_unlock() pour marquer la zone critique
  •  ;
  • L'opération de mise à jour nécessite d'abord de créer une copie des données, d'apporter des modifications sur la copie, puis d'utiliser rcu_assign_pointer() pour publier les nouvelles données, et d'utiliser call_rcu() ou synchroniser_rcu() pour attendre que toutes les opérations de lecture soient terminées avant de recycler les données. anciennes données.

Quels sont les avantages du RCU ? Il présente les fonctionnalités suivantes :

  • RCU peut réduire la concurrence de verrouillage et le changement de contexte, et améliorer les performances de concurrence et la réactivité du système
  •  ;
  • RCU peut éviter des problèmes tels que les blocages et l'inversion des priorités, simplifiant ainsi le modèle de programmation
  •  ;
  • RCU peut s'adapter à différents scénarios, tels que les systèmes temps réel, les systèmes NUMA, les systèmes SMP, etc. ; RCU peut être utilisé conjointement avec d'autres mécanismes de synchronisation, tels que les verrous de rotation, les opérations atomiques, etc.
  • Alors, comment fonctionne le RCU ? De quels scénarios d'application dispose-t-il ? Cet article présentera le mécanisme de synchronisation efficace de RCU sous deux aspects : le principe et l'application. L'idée de conception de RCU est relativement claire et la protection du partage sans verrouillage est obtenue en remplaçant les anciens et les nouveaux pointeurs. Mais en ce qui concerne le niveau du code, cela reste un peu difficile à comprendre. Dans le chapitre 4 de "Mécanisme approfondi du noyau du pilote de périphérique Linux", les règles suivies derrière RCU ont été très clairement décrites. Ces règles sont vues d'un point de vue relativement élevé, car je pense que trop d'analyse de code est facile. Laissez le lecteur se perdre. dans les détails. Après avoir reçu le livre récemment, j'ai relu attentivement le texte de la partie RCU et j'ai pensé que je devrais ajouter un peu plus de contenu, car certaines choses peuvent ne pas convenir à être écrites dans le livre.

Le signe que le côté lecture RCU entre dans la section critique est d'appeler rcu_read_lock. Le code de cette fonction est :

.
1. 
2. static inline void rcu_read_lock(void)
3. {
4. ​    __rcu_read_lock();
5. ​    __acquire(RCU);
6. ​    rcu_read_acquire();
7. }

Il semble y avoir trois appels de fonction dans cette implémentation, mais le travail réel est effectué par la première fonction __rcu_read_lock(). __rcu_read_lock() désactive la préemption du noyau en appelant preempt_disable(). Mais les interruptions sont autorisées. Supposons que le lecteur se trouve dans la section critique rcu et qu'il vient de lire un pointeur p dans la zone de données partagées (mais n'a pas encore accédé aux données membres de p). Une interruption se produit et l'exemple de gestion des interruptions est activé. Il se trouve que l'ISR doit modifier la zone de données pointée par p. Selon les principes de conception de RCU, l'ISR allouera une nouvelle zone de données new_p de la même taille, puis copiera les données de l'ancienne zone de données p dans la nouvelle. zone de données, puis dans new_p Effectuez essentiellement le travail de modification des données (car elles sont modifiées dans l'espace new_p, il n'y a pas d'accès simultané à p, donc RCU est un mécanisme sans verrouillage, et c'est la raison), après l'ISR termine le travail de mise à jour des données, attribue new_p à p (p=new_p), et enfin il enregistrera une fonction de rappel pour libérer l'ancien pointeur p au moment approprié. Par conséquent, tant que toutes les références à l’ancien pointeur p sont terminées, il n’y a aucun problème à libérer p. Lorsque la routine de traitement des interruptions revient après avoir terminé ces tâches, le processus interrompu accédera toujours aux données dans l'espace p, c'est-à-dire aux anciennes données. Ce résultat est autorisé par le mécanisme RCU.

Les règles RCU autorisent des incohérences temporaires dans l'affichage des ressources causées par le basculement du pointeur entre les lecteurs et les rédacteurs

.

La prochaine question intéressante à propos de RCU est : quand l'ancien pointeur peut-il être libéré. La réponse à cette question que j'ai vue dans de nombreux livres est la suivante : lorsqu'un changement de processus se produit sur tous les processeurs du système. Cette réponse stylisée déroute souvent les lecteurs qui découvrent le mécanisme RCU. Pourquoi devons-nous attendre qu'un changement de processus se produise sur tous les processeurs avant d'appeler la fonction de rappel pour libérer l'ancien pointeur ? Ceci est en fait déterminé par les règles de conception de RCU : Toutes les références aux anciens pointeurs ne peuvent apparaître que dans la section critique incluse dans rcu_read_lock et rcu_read_unlock, et la commutation de processus n'est pas possible dans cette section critique , Une fois la section critique ne devrait plus n'ont plus aucune forme de référence à l'ancien pointeur p. Évidemment, cette règle exige que le lecteur ne puisse pas changer de processus dans la section critique, car une fois qu'il y a un changement de processus, la fonction de rappel qui libère l'ancien pointeur peut être appelée, provoquant la libération de l'ancien pointeur lors du changement de processus. est replanifié, il peut faire référence à un espace mémoire libéré.

Nous voyons maintenant pourquoi rcu_read_lock doit uniquement désactiver la préemption du noyau, car cela rend impossible la commutation et la suppression du processus actuel même si une interruption se produit dans la section critique. Les développeurs de noyau, ou plutôt les concepteurs de RCU, ne peuvent pas faire grand-chose. L'étape suivante relève de la responsabilité de l'utilisateur. Si une fonction est appelée dans la section critique de RCU, la fonction peut se mettre en veille, alors les règles de conception de RCU seront violées et le système entrera dans un état instable.

Cela montre une fois de plus que si vous voulez utiliser quelque chose, vous devez comprendre son mécanisme interne. Comme dans l'exemple mentionné ci-dessus, même s'il n'y a actuellement aucun problème avec le programme, les dangers cachés laissés dans le système sont comme une bombe à retardement. , ce qui peut arriver à tout moment. Une détonation, surtout s'il faut beaucoup de temps avant que le problème n'éclate soudainement. Dans la plupart des cas, le temps nécessaire pour trouver le problème peut être beaucoup plus long que pour se calmer et comprendre soigneusement les principes de la RCU.

Les lecteurs de RCU ont un degré de liberté plus élevé que les lecteurs de rwlock. Étant donné que le lecteur RCU n'a pas besoin de prendre en compte les sentiments de l'écrivain lorsqu'il accède à une ressource partagée, cela est différent du lecteur rwlock. Le lecteur rwlock doit s'assurer qu'aucun écrivain n'exploite la ressource lors de la lecture de la ressource partagée. La différence entre les deux vient du fait que RCU sépare les ressources partagées entre lecteurs et écrivains, tandis que les lecteurs et écrivains rwlock n'utilisent qu'une seule copie des ressources partagées du début à la fin. Cela signifie également que les rédacteurs de RCU doivent assumer davantage de responsabilités et qu'une sorte de mécanisme d'exclusion mutuelle doit être introduit entre plusieurs rédacteurs qui mettent à jour la même ressource partagée. RCU est donc un « mécanisme sans verrouillage ». La déclaration est limitée aux lecteurs et écrivains. Nous voyons donc que le mécanisme RCU doit être utilisé dans des situations où il y a un grand nombre d'opérations de lecture et relativement peu d'opérations de mise à jour. À l'heure actuelle, RCU peut grandement améliorer les performances du système, car l'opération de lecture de RCU n'entraîne pratiquement aucune surcharge de verrouillage par rapport à certains autres mécanismes de verrouillage.

En utilisation réelle, les ressources partagées existent souvent sous la forme de listes chaînées.Le noyau implémente plusieurs fonctions d'interface pour les opérations de liste chaînée en mode RCU, telles que list_add_tail_rcu, list_add_rcu, hlist_replace_rcu, etc. pour une utilisation spécifique, veuillez vous référer à certaines informations sur la programmation du noyau ou sur le pilote de périphérique.

En ce qui concerne la publication d'anciens pointeurs, le noyau Linux propose deux méthodes aux utilisateurs, l'une consiste à appeler call_rcu et l'autre à appeler synchroniser_rcu. La première est une méthode asynchrone. call_rcu placera la fonction de rappel qui libère l'ancien pointeur dans un nœud, puis ajoutera le nœud à la liste chaînée locale du processeur exécutant actuellement call_rcu dans la partie softirq de l'interruption d'horloge (RCU_SOFTIRQ ). , la fonction de traitement des interruptions logicielles rcu rcu_process_callbacks vérifiera si le processeur actuel a connu une période de veille (quiescente, qui implique la planification des processus du noyau et d'autres aspects. L'implémentation du code noyau de rcu détermine que tous les processeurs du système ont connu une période de veille). période (ce qui signifie qu'un changement de processus s'est produit sur tous les processeurs, donc l'ancien pointeur peut être libéré en toute sécurité à ce moment), la fonction de rappel fournie par call_rcu sera appelée.

L'implémentation de synchroniser_rcu utilise la file d'attente. Lors de son implémentation, elle ajoute également un nœud à la liste chaînée locale du processeur actuel comme call_rcu. La différence avec call_rcu est que la fonction de rappel dans ce nœud est wakeme_after_rcu, puis synchronise_rcu. dormira dans une file d'attente jusqu'à ce qu'un changement de processus se produise sur tous les processeurs du système, donc wakeme_after_rcu est appelé par rcu_process_callbacks pour réveiller le synchronisme_rcu endormi. Après avoir été réveillé, synchroniser_rcu sait qu'il peut maintenant libérer l'ancien pointeur.

Nous voyons donc que la fonction de rappel enregistrée n'a peut-être pas été appelée après le retour de call_rcu, ce qui signifie que l'ancien pointeur n'a pas été libéré, et l'ancien pointeur doit avoir été libéré après le retour de synchroniser_rcu. Par conséquent, l'appel de call_rcu ou de synchroniser_rcu dépend des besoins spécifiques et du contexte actuel. Par exemple, la fonction synchroniser_rcu ne peut pas être utilisée dans le contexte du traitement des interruptions.

Cet article présente RCU, un mécanisme de synchronisation efficace dans le noyau Linux. Il s'agit d'un mécanisme de synchronisation basé sur le modèle de publication-abonnement. Nous avons analysé les idées de base, les interfaces clés et les détails de mise en œuvre de RCU sous l'aspect principal, et avons donné des exemples de code correspondants. Nous avons également introduit l'utilisation de RCU dans les opérations de liste chaînée, la gestion des minuteries, le traitement des interruptions logicielles et d'autres scénarios du point de vue de l'application, et avons donné des exemples de code correspondants. En étudiant cet article, nous pouvons maîtriser l'utilisation de base de RCU et être en mesure d'utiliser RCU de manière flexible dans le développement réel pour répondre aux exigences de synchronisation efficaces. J'espère que cet article vous aidera !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer