[Original] Analyse du principe du Linux RCU (1)

Contexte

  • Read the fucking source code! --Par Lu Xun
  • A picture is worth a thousand words. --Par Gorky

Description:

  1. Version du noyau: 4.14
  2. Processeur ARM64, Contex-A53, double cœur
  3. Utiliser des outils: Source Insight 3.5, Visio

1. Présentation

RCU, Read-Copy-Update, Est un mécanisme de synchronisation dans le noyau Linux.
RCUIl est souvent décrit comme remplaçant les verrous en lecture-écriture. Sa caractéristique est que le lecteur n'a pas besoin de se synchroniser directement avec le graveur, et le lecteur et le graveur peuvent également s'exécuter simultanément. RCUL'objectif est de minimiser les frais généraux du côté du lecteur, de sorte qu'il est également couramment utilisé dans les scénarios qui nécessitent des performances élevées du lecteur.

  • Avantages:

    1. Le côté lecteur a très peu de surcharge, pas besoin d'acquérir de verrous, pas besoin d'exécuter des instructions atomiques ou des barrières mémoire;
    2. Aucun problème de blocage;
    3. Pas de problème d'inversion de priorité;
    4. Aucun danger de fuite de mémoire;
    5. Très bon délai en temps réel;
  • Inconvénients:

    1. Les frais généraux de synchronisation de l'auteur sont relativement importants et les auteurs doivent s'exclure mutuellement.
    2. Il est plus compliqué à utiliser que les autres mécanismes de synchronisation;

Prenons une photo pour décrire le fonctionnement général:

  • Plusieurs lecteurs peuvent accéder simultanément aux ressources critiques et les utiliser simultanément rcu_read_lock/rcu_read_unlockpour calibrer les sections critiques;
  • L'auteur ( updater) copie une copie comme base de modification lors de la mise à jour des ressources critiques. Lorsque tous les lecteurs quittent la section critique, ils pointent le pointeur vers l'ancienne ressource critique vers la copie mise à jour et recyclent l'ancienne ressource;
  • Un seul écrivain est indiqué sur la figure. Lorsqu'il y a plusieurs écrivains, l'exclusion mutuelle est requise entre les écrivains;

La description ci-dessus est relativement simple et la mise en œuvre de RCU est très compliquée. Cet article donne d'abord une première impression de RCU et analyse l'exemple avec l'interface.Les articles suivants approfondiront le principe d'implémentation sous-jacent couche par couche. C'est parti!

2. Bases RCU

2.1 Éléments de base du RCU

RCUL'idée de base est de diviser l' Updateopération de mise à jour en deux parties: 1) Removalsupprimer; 2) Reclamationrecycler.
Pour le dire franchement, la ressource critique est lue par plusieurs lecteurs. Lorsque le rédacteur met à jour la copie après modification, la première étape doit supprimer les anciennes données de ressource critique (les points du pointeur de modification), et la deuxième étape Recyclez les anciennes données (par exemple kfree).

Par conséquent, il est fonctionnellement divisé en trois éléments de base suivants: Reader/Updater/ReclaimerL'interaction entre les trois est la suivante:

  1. Lecteur

    • Utilisez rcu_read_locket rcu_read_unlockpour définir la zone critique du lecteur Lorsque RCUvous accédez aux données protégées, vous devez toujours y accéder dans la zone critique;
    • Avant d'accéder aux données protégées, vous devez utiliser rcu_dereferencepour obtenir le RCU-protectedpointeur;
    • Lorsque vous utilisez non préemptif, RCUvous rcu_read_lock/rcu_read_unlockne pouvez pas utiliser le code qui peut dormir;
  2. Updater

    • Lorsque plusieurs Updaters mettent à jour les données, ils doivent utiliser un mécanisme d'exclusion mutuelle pour la protection;
    • Updater est utilisé rcu_assign_pointerpour supprimer l'ancien pointeur pour pointer vers les ressources critiques mises à jour;
    • Updater utilise synchronize_rcuou call_rcupour démarrer Reclaimer, pour recycler les anciennes ressources critiques, ce qui synchronize_rcusignifie attendre de manière synchrone le recyclage, ce qui call_rcusignifie le recyclage asynchrone;
  3. Reclaimer

    • Reclaimer recycle les anciennes ressources critiques;
    • Afin de garantir qu'aucun lecteur n'accède aux ressources critiques à récupérer, Reclaimer doit attendre que tous les lecteurs quittent la section critique. Ce temps d'attente est appelé période de grâce ( Grace Period);

2.2 Trois mécanismes de base de RCU

Utilisé pour fournir les fonctions décrites ci-dessus, RCUbasées sur trois mécanismes.

2.2.1 Publish-Subscribe Mechanism

Quel est le concept du mécanisme d'abonnement, venez à l'image:

  • UpdaterEt Readercomme Publisheret les Subsriberrelations;
  • UpdaterAprès avoir mis à jour le contenu, appelez l'interface pour publier et Readerappelez l'interface pour lire le contenu publié;

Alors, que faut-il faire pour assurer ce mécanisme d'abonnement? Regardons un pseudo code:

 /* Definiton of global structure */
 1 struct foo {
  2   int a;
  3   int b;
  4   int c;
  5 };
  6 struct foo *gp = NULL;
  7 
  8 /* . . . */
  9 /* =========Updater======== */ 
 10 p = kmalloc(sizeof(*p), GFP_KERNEL);
 11 p->a = 1;
 12 p->b = 2;
 13 p->c = 3;
 14 gp = p;
 15 
 16 /* =========Reader======== */
 17 p = gp;
 18 if (p != NULL) {
 19   do_something_with(p->a, p->b, p->c);
 20 }

À première vue, il semble que le problème ne soit pas trop important. Le programme de mise à jour effectue l'affectation et la mise à jour, et le lecteur effectue la lecture et d'autres traitements. Cependant, en raison des problèmes de compilation et d'exécution dans le désordre, l'ordre d'exécution du code ci-dessus n'est pas nécessairement l'ordre du code. Par exemple, dans certaines architectures ( DEC Alpha), la partie opération du lecteur peut être exploitée avant l'affectation de p do_something_with().

Pour résoudre ce problème, Linux offre rcu_assign_pointer/rcu_dereferencemacro pour assurer l'ordre d'exécution, le noyau Linux est également basée sur des rcu_assign_pointer/rcu_dereferencemacros un packages de niveau supérieur, tels que list, par hlistconséquent, il y a trois noyaux de scène RCU protégé: 1) pointeur; 2) liste la liste ; 3) liste de liens de hachage hlist.

Pour ces trois scénarios, l' Publish-Subscribeinterface est la suivante:

2.2.2 Wait For Pre-Existing RCU Readers to Complete

Reclaimer doit recycler les anciennes ressources critiques, donc la question se pose, quand cela se produira-t-il? Par conséquent, il est RCUnécessaire de fournir un mécanisme pour garantir que tous les lecteurs RCU précédents ont été terminés, c'est-à- rcu_read_lock/rcu_read_unlockdire qu'ils ne peuvent être recyclés qu'après avoir quitté la section critique étalonnée.

  • Les lecteurs et le programme de mise à jour de la figure sont exécutés simultanément;
  • Lorsque le programme de mise à jour effectue l' Removalopération, il est appelé synchronize_rcu, marquant la fin de la mise à jour et commençant à entrer dans la phase de récupération;
  • Après l' synchronize_rcuappel, il peut y avoir de nouveaux lecteurs pour lire les ressources critiques (contenu mis à jour) en ce moment, mais les lecteurs qui Grace Periodn'attendent que le Pre-Existingsont dans la figure Reader-4, Reader-5. Tant que ces lecteurs RCU qui existaient avant ont quitté la section critique, cela signifie la fin de la période de grâce, donc le processus de recyclage est effectué;
  • synchronize_rcuCe n'est pas que le dernier Pre-Existinglecteur RCU quitte immédiatement après avoir quitté la section critique, il peut y avoir un retard de programmation;

2.2.3 Maintain Multiple Versions of Recently Updated Objects

On 2.2.2节peut voir qu'après la mise à jour du programme de mise à jour, avant le recyclage du reclaimer, il y aura deux nouvelles et anciennes versions des ressources critiques. Ce n'est qu'après synchronize_rcuson retour que le reclaimer recycle les anciennes ressources critiques et la dernière version reste. Évidemment, lorsqu'il y a plusieurs mises à jour, il y aura des versions de ressources plus critiques.

Prenons une photo, en prenant des pointeurs et des listes liées comme exemples:

  • Le synchronize_rcudébut de l' appel est un point critique, maintenant différentes versions des ressources critiques;
  • Après que Reclaimer récupère l'ancienne version des ressources, il est finalement unifié;

3. Exemple d'analyse RCU

Il est temps pour une vague fucking sample code.

  • La logique de code globale:
    1. Construire quatre threads du noyau, deux threads du noyau pour tester l'opération de protection RCU du pointeur et deux threads du noyau pour tester l'opération de protection RCU de la liste liée;
    2. Au moment du recyclage, deux mécanismes de synchronize_rcurecyclage synchrone et de call_rcurecyclage asynchrone ont été utilisés ;
    3. Afin de simplifier le code, le jugement de base sur la tolérance aux pannes a été omis;
    4. Le mécanisme de plusieurs Updaters n'est pas considéré, par conséquent, l'opération mutuellement exclusive entre Updaters est également omise;
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/rcupdate.h>
#include <linux/delay.h>

struct foo {
	int a;
	int b;
	int c;
	struct rcu_head rcu;
	struct list_head list;
};

static struct foo *g_pfoo = NULL;

LIST_HEAD(g_rcu_list);

struct task_struct *rcu_reader_t;
struct task_struct *rcu_updater_t;
struct task_struct *rcu_reader_list_t;
struct task_struct *rcu_updater_list_t;

/* 指针的Reader操作 */
static int rcu_reader(void *data)
{
	struct foo *p = NULL;
	int cnt = 100;

	while (cnt--) {
		msleep(100);
		rcu_read_lock();
		p = rcu_dereference(g_pfoo);
		pr_info("%s: a = %d, b = %d, c = %d\n",
				__func__, p->a, p->b, p->c);
		rcu_read_unlock();
	}

	return 0;
}

/*  回收处理操作 */
static void rcu_reclaimer(struct rcu_head *rh)
{
	struct foo *p = container_of(rh, struct foo, rcu);
	pr_info("%s: a = %d, b = %d, c = %d\n",
			__func__, p->a, p->b, p->c);
	kfree(p);
}

/* 指针的Updater操作 */
static int rcu_updater(void *data)
{
	int value = 1;
	int cnt = 100;

	while (cnt--) {
		struct foo *old;
		struct foo *new = (struct foo *)kzalloc(sizeof(struct foo), GFP_KERNEL);

		msleep(200);

		old = g_pfoo;

		*new = *g_pfoo;
		new->a = value;
		new->b = value + 1;
		new->c = value + 2;
		rcu_assign_pointer(g_pfoo, new);

		pr_info("%s: a = %d, b = %d, c = %d\n",
				__func__, new->a, new->b, new->c);

		call_rcu(&old->rcu, rcu_reclaimer);

		value++;
	}

	return 0;
}

/* 链表的Reader操作 */
static int rcu_reader_list(void *data)
{
	struct foo *p = NULL;
	int cnt = 100;

	while (cnt--) {
		msleep(100);
		rcu_read_lock();
		list_for_each_entry_rcu(p, &g_rcu_list, list) {
			pr_info("%s: a = %d, b = %d, c = %d\n",
					__func__, p->a, p->b, p->c);
		}
		rcu_read_unlock();
	}

	return 0;
}

/* 链表的Updater操作 */
static int rcu_updater_list(void *data)
{
	int cnt = 100;
	int value = 1000;

	while (cnt--) {
		msleep(100);
		struct foo *p = list_first_or_null_rcu(&g_rcu_list, struct foo, list);
		struct foo *q = (struct foo *)kzalloc(sizeof(struct foo), GFP_KERNEL);

		*q = *p;
		q->a = value;
		q->b = value + 1;
		q->c = value + 2;

		list_replace_rcu(&p->list, &q->list);

		pr_info("%s: a = %d, b = %d, c = %d\n",
				__func__, q->a, q->b, q->c);

		synchronize_rcu();
		kfree(p);

		value++; 
	}

	return 0;
}

/* module初始化 */
static int rcu_test_init(void)
{
	struct foo *p;

	rcu_reader_t = kthread_run(rcu_reader, NULL, "rcu_reader");
	rcu_updater_t = kthread_run(rcu_updater, NULL, "rcu_updater");
	rcu_reader_list_t = kthread_run(rcu_reader_list, NULL, "rcu_reader_list");
	rcu_updater_list_t = kthread_run(rcu_updater_list, NULL, "rcu_updater_list");

	g_pfoo = (struct foo *)kzalloc(sizeof(struct foo), GFP_KERNEL);

	p = (struct foo *)kzalloc(sizeof(struct foo), GFP_KERNEL);
	list_add_rcu(&p->list, &g_rcu_list);

	return 0;
}

/* module清理工作 */
static void rcu_test_exit(void)
{
	kfree(g_pfoo);
	kfree(list_first_or_null_rcu(&g_rcu_list, struct foo, list));

	kthread_stop(rcu_reader_t);
	kthread_stop(rcu_updater_t);
	kthread_stop(rcu_reader_list_t);
	kthread_stop(rcu_updater_list_t);
}

module_init(rcu_test_init);
module_exit(rcu_test_exit);

MODULE_AUTHOR("Loyen");
MODULE_LICENSE("GPL");

Afin de prouver qu'il n'y a pas de tromperie, le journal de sortie exécuté sur la carte de développement est publié, comme indiqué ci-dessous:

4. Introduction à l'API

4.1 API principale

Les interfaces suivantes ne peuvent pas être plus centrales.

a.      rcu_read_lock()  //标记读者临界区的开始
b.      rcu_read_unlock()  //标记读者临界区的结束
c.      synchronize_rcu() / call_rcu() //等待Grace period结束后进行资源回收
d.      rcu_assign_pointer()  //Updater使用这个宏对受RCU保护的指针进行赋值
e.      rcu_dereference()  //Reader使用这个宏来获取受RCU保护的指针

4.2 Autres API associées

Sur la base de l'API principale, d'autres API connexes ont été étendues, comme suit, sans plus de détails:

RCU list traversal::

        list_entry_rcu
        list_entry_lockless
        list_first_entry_rcu
        list_next_rcu
        list_for_each_entry_rcu
        list_for_each_entry_continue_rcu
        list_for_each_entry_from_rcu
        list_first_or_null_rcu
        list_next_or_null_rcu
        hlist_first_rcu
        hlist_next_rcu
        hlist_pprev_rcu
        hlist_for_each_entry_rcu
        hlist_for_each_entry_rcu_bh
        hlist_for_each_entry_from_rcu
        hlist_for_each_entry_continue_rcu
        hlist_for_each_entry_continue_rcu_bh
        hlist_nulls_first_rcu
        hlist_nulls_for_each_entry_rcu
        hlist_bl_first_rcu
        hlist_bl_for_each_entry_rcu

RCU pointer/list update::

        rcu_assign_pointer
        list_add_rcu
        list_add_tail_rcu
        list_del_rcu
        list_replace_rcu
        hlist_add_behind_rcu
        hlist_add_before_rcu
        hlist_add_head_rcu
        hlist_add_tail_rcu
        hlist_del_rcu
        hlist_del_init_rcu
        hlist_replace_rcu
        list_splice_init_rcu
        list_splice_tail_init_rcu
        hlist_nulls_del_init_rcu
        hlist_nulls_del_rcu
        hlist_nulls_add_head_rcu
        hlist_bl_add_head_rcu
        hlist_bl_del_init_rcu
        hlist_bl_del_rcu
        hlist_bl_set_first_rcu

RCU::

        Critical sections       Grace period            Barrier

        rcu_read_lock           synchronize_net         rcu_barrier
        rcu_read_unlock         synchronize_rcu
        rcu_dereference         synchronize_rcu_expedited
        rcu_read_lock_held      call_rcu
        rcu_dereference_check   kfree_rcu
        rcu_dereference_protected

bh::

        Critical sections       Grace period            Barrier

        rcu_read_lock_bh        call_rcu                rcu_barrier
        rcu_read_unlock_bh      synchronize_rcu
        [local_bh_disable]      synchronize_rcu_expedited
        [and friends]
        rcu_dereference_bh
        rcu_dereference_bh_check
        rcu_dereference_bh_protected
        rcu_read_lock_bh_held

sched::

        Critical sections       Grace period            Barrier

        rcu_read_lock_sched     call_rcu                rcu_barrier
        rcu_read_unlock_sched   synchronize_rcu
        [preempt_disable]       synchronize_rcu_expedited
        [and friends]
        rcu_read_lock_sched_notrace
        rcu_read_unlock_sched_notrace
        rcu_dereference_sched
        rcu_dereference_sched_check
        rcu_dereference_sched_protected
        rcu_read_lock_sched_held


SRCU::

        Critical sections       Grace period            Barrier

        srcu_read_lock          call_srcu               srcu_barrier
        srcu_read_unlock        synchronize_srcu
        srcu_dereference        synchronize_srcu_expedited
        srcu_dereference_check
        srcu_read_lock_held

SRCU: Initialization/cleanup::

        DEFINE_SRCU
        DEFINE_STATIC_SRCU
        init_srcu_struct
        cleanup_srcu_struct

All: lockdep-checked RCU-protected pointer access::

        rcu_access_pointer
        rcu_dereference_raw
        RCU_LOCKDEP_WARN
        rcu_sleep_check
        RCU_NONIDLE

D'accord, répertorier ces API est un peu déroutant.

Le voile mystérieux de RCU est d'abord dévoilé, puis il sera un peu difficile de ramasser des vêtements à l'intérieur. Après tout, le mécanisme de mise en œuvre derrière RCU est vraiment difficile. Donc, la question vient, voulez-vous être un homme qui voit le roi? Faites attention.

Référence

Documentation / RCU
Qu'est-ce que RCU, fondamentalement?
Qu'est-ce que RCU? Partie 2: Utilisation
RCU partie 3: l'API RCU
Introduction à RCU

Bienvenue à prêter attention au numéro public et à continuer de partager les articles du mécanisme de base sous forme graphique

Je suppose que tu aimes

Origine www.cnblogs.com/LoyenWang/p/12681494.html
conseillé
Classement