[Original] Análisis del principio de Linux RCU (1)

Antecedentes

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

Descripción:

  1. Versión de Kernel: 4.14
  2. Procesador ARM64, Contex-A53, doble núcleo
  3. Usar herramientas: Source Insight 3.5, Visio

1. Descripción general

RCU, Read-Copy-Update, Es un mecanismo de sincronización en el kernel de Linux.
RCUA menudo se describe como un reemplazo para los bloqueos de lectura-escritura. Su característica es que el lector no necesita sincronizarse directamente con el escritor, y el lector y el escritor también pueden ejecutar simultáneamente. RCUEl objetivo es minimizar la sobrecarga en el lado del lector, por lo que también se usa comúnmente en escenarios que requieren un alto rendimiento del lector.

  • Ventajas:

    1. El lado del lector tiene muy poca sobrecarga, no es necesario adquirir ningún bloqueo, no es necesario ejecutar instrucciones atómicas o barreras de memoria;
    2. No hay problema de punto muerto;
    3. No hay problema de inversión prioritaria;
    4. No hay peligro de pérdida de memoria;
    5. Muy buen retraso en tiempo real;
  • Desventajas

    1. La sobrecarga de sincronización del escritor es relativamente grande, y los escritores deben ser mutuamente excluyentes.
    2. Es más complicado de usar que otros mecanismos de sincronización;

Tomemos una foto para describir la operación general:

  • Múltiples lectores pueden acceder a recursos críticos al mismo tiempo y usarlos simultáneamente rcu_read_lock/rcu_read_unlockpara calibrar secciones críticas;
  • El escritor ( updater) copia una copia como base para la modificación al actualizar recursos críticos. Cuando todos los lectores abandonan la sección crítica, señalan el puntero al recurso crítico antiguo a la copia actualizada y reciclan el recurso antiguo;
  • Solo se muestra un escritor en la figura. Cuando hay varios escritores, se requiere exclusión mutua entre escritores;

La descripción anterior es relativamente simple, y la implementación de RCU es muy complicada. Este artículo primero da una primera impresión de RCU y analiza el ejemplo con la interfaz. Los artículos siguientes profundizarán en el principio de implementación subyacente capa por capa. Vamos!

2. Conceptos básicos de RCU

2.1 Elementos básicos de la UCR

RCULa idea básica es dividir la Updateoperación de actualización en dos partes: 1) Removaleliminar; 2) Reclamationreciclar.
Para decirlo sin rodeos, el recurso crítico es leído por varios lectores. Cuando el escritor actualiza la copia después de la modificación, el primer paso debe eliminar los datos de recursos críticos antiguos (los puntos de puntero de modificación), y el segundo paso requiere Reciclar datos antiguos (por ejemplo kfree).

Por lo tanto, se divide funcionalmente en los siguientes tres elementos básicos: Reader/Updater/ReclaimerLa interacción entre los tres es la siguiente:

  1. Lector

    • Utilice rcu_read_locky rcu_read_unlockpara definir el área crítica del lector. Al acceder a los RCUdatos protegidos, siempre debe acceder a ellos en el área crítica;
    • Antes de acceder a los datos protegidos, debe usar rcu_dereferencepara obtener el RCU-protectedpuntero;
    • Cuando se usa no preventivo, RCUno rcu_read_lock/rcu_read_unlockpuede usar el código que puede dormir;
  2. Updater

    • Cuando varios Actualizadores actualizan datos, necesitan usar un mecanismo de exclusión mutua para protección;
    • Updater se utiliza rcu_assign_pointerpara eliminar el puntero antiguo para apuntar a los recursos críticos actualizados;
    • Updater usa synchronize_rcuo call_rcupara comenzar Reclaimer, para reciclar los viejos recursos críticos, lo que synchronize_rcusignifica esperar sincrónicamente el reciclaje, lo que call_rcusignifica reciclaje asincrónico;
  3. Reclamador

    • Reclaimer recicla viejos recursos críticos;
    • Para garantizar que ningún lector acceda a los recursos críticos que se recuperarán, Reclaimer debe esperar a que todos los lectores salgan de la sección crítica. Este tiempo de espera se llama período de gracia ( Grace Period);

2.2 Tres mecanismos básicos de UCR

Se utiliza para proporcionar las funciones descritas anteriormente, RCUbasadas en tres mecanismos.

2.2.1 Publish-Subscribe Mechanism

¿Cuál es el concepto del mecanismo de suscripción? Ven a la imagen:

  • UpdaterY el Readercomo Publishery Subsriberlas relaciones;
  • UpdaterDespués de actualizar el contenido, llame a la interfaz para publicar y Readerllame a la interfaz para leer el contenido publicado;

Entonces, ¿qué se debe hacer para garantizar este mecanismo de suscripción? Veamos un pseudocódigo:

 /* 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 }

A primera vista, parece que el problema no es demasiado grande: el actualizador realiza la asignación y la actualización, y el lector realiza la lectura y otros procesos. Sin embargo, debido a los problemas de compilación y ejecución fuera de orden, el orden de ejecución del código anterior puede no ser necesariamente el orden del código. Por ejemplo, en algunas arquitecturas ( DEC Alpha), la parte de operación del lector puede operarse antes de asignar p do_something_with().

Para resolver este problema, Linux ofrece rcu_assign_pointer/rcu_dereferencemacro para asegurar el orden de ejecución, núcleo de Linux se basa en rcu_assign_pointer/rcu_dereferencelas macros a los paquetes de más alto nivel, como por ejemplo list, hlistpor lo tanto, hay tres protegida kernel escena UCR: 1) apuntador; 2) la lista de la lista ; 3) hlist lista enlazada hash.

Para estos tres escenarios, la Publish-Subscribeinterfaz es la siguiente:

2.2.2 Wait For Pre-Existing RCU Readers to Complete

Reclaimer necesita reciclar los viejos recursos críticos, entonces surge la pregunta, ¿cuándo sucederá? Por lo tanto, es RCUnecesario proporcionar un mecanismo para garantizar que se hayan completado todos los lectores de RCU anteriores, es decir rcu_read_lock/rcu_read_unlock, que solo se puedan reciclar después de salir de la sección crítica calibrada.

  • Los lectores y el actualizador de la figura se ejecutan simultáneamente;
  • Cuando el Actualizador realiza la Removaloperación, se llama synchronize_rcu, marca el final de la actualización y comienza a entrar en la fase de recuperación;
  • Después de la synchronize_rcullamada, puede haber nuevos lectores para leer recursos críticos (contenido actualizado) en este momento, pero los lectores que Grace Periodsolo Pre-Existingestán esperando están en la figura Reader-4, Reader-5. Mientras estos lectores de la UCR que existían antes salieran de la sección crítica, significa el final del período de gracia, por lo que el proceso de reciclaje se lleva a cabo;
  • synchronize_rcuNo es que el último Pre-Existinglector de RCU se vaya inmediatamente después de abandonar la sección crítica, puede tener un retraso de programación;

2.2.3 Maintain Multiple Versions of Recently Updated Objects

Se 2.2.2节puede ver que después de las actualizaciones del Actualizador, antes de que el Reclamador se recicle, habrá dos versiones nuevas y antiguas de los recursos críticos. Solo después de synchronize_rcuregresar, el Reclamador recicla los viejos recursos críticos y queda la última versión. Obviamente, cuando hay múltiples Actualizadores, habrá versiones de recursos más críticas.

Tomemos una foto, tomando punteros y listas vinculadas como ejemplos:

  • El synchronize_rcuinicio de la llamada es un punto crítico, que mantiene diferentes versiones de recursos críticos;
  • Después de que Reclaimer reclama la versión anterior de los recursos, finalmente se unifica;

3. Análisis de ejemplo de RCU

Es hora de una ola fucking sample code.

  • La lógica general del código:
    1. Construya cuatro subprocesos del núcleo, dos subprocesos del núcleo para probar las operaciones de protección de la RCU del puntero y dos subprocesos del núcleo para probar las operaciones de protección de la RCU de la lista vinculada;
    2. En el momento del reciclaje, se utilizaron dos mecanismos de synchronize_rcureciclaje sincrónico y call_rcureciclado asincrónico;
    3. Para simplificar el código, se ha omitido el juicio básico de tolerancia a fallas;
    4. El mecanismo de múltiples Actualizadores no se considera, por lo tanto, también se omite la operación mutuamente excluyente entre Actualizadores;
#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");

Para demostrar que no hay engaño, se publica el registro de salida que se ejecuta en la placa de desarrollo, como se muestra a continuación:

4. Introducción a la API

4.1 API principal

Las siguientes interfaces no pueden ser más básicas.

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 Otras API relacionadas

Según la API principal, se han ampliado otras API relacionadas, de la siguiente manera, sin más detalles:

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

Bien, enumerar estas API es un poco confuso.

El velo misterioso de RCU se revela inicialmente, y luego será un poco difícil recoger la ropa dentro. Después de todo, el mecanismo de implementación detrás de RCU es realmente difícil. Entonces, la pregunta viene, ¿quieres ser un hombre que ve al rey? Presta atención.

Referencia

Documentación / RCU
¿Qué es RCU, fundamentalmente?
¿Qué es la UCR? Parte 2: Uso de
RCU parte 3: Introducción a API
de RCU para RCU

Bienvenido a prestar atención al número público y seguir compartiendo los artículos del mecanismo central en forma gráfica

Supongo que te gusta

Origin www.cnblogs.com/LoyenWang/p/12681494.html
Recomendado
Clasificación