[Original] Análise do princípio do Linux RCU (1)

Antecedentes

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

Descrição:

  1. Versão do kernel: 4.14
  2. Processador ARM64, Contex-A53, núcleo duplo
  3. Use ferramentas: Source Insight 3.5, Visio

1. Visão geral

RCU, Read-Copy-Update, É um mecanismo de sincronização no kernel do Linux.
RCUÉ frequentemente descrito como um substituto para os bloqueios de leitura e gravação, cuja característica é que o leitor não precisa sincronizar diretamente com o gravador, e também pode executar simultaneamente. RCUO objetivo é minimizar a sobrecarga no lado do leitor, por isso também é comumente usado em cenários que exigem alto desempenho do leitor.

  • Vantagens:

    1. O lado do leitor possui muito pouca sobrecarga, não é necessário adquirir nenhum bloqueio, não é necessário executar instruções atômicas ou barreiras de memória;
    2. Nenhum problema de impasse;
    3. Não há problema de inversão de prioridade;
    4. Não há perigo de vazamento de memória;
    5. Muito bom atraso em tempo real;
  • Desvantagens:

    1. A sobrecarga de sincronização do gravador é relativamente grande e os gravadores precisam ser mutuamente exclusivos.
    2. É mais complicado de usar do que outros mecanismos de sincronização;

Vamos tirar uma foto para descrever a operação geral:

  • Vários leitores podem acessar recursos críticos simultaneamente e usá- rcu_read_lock/rcu_read_unlocklos simultaneamente para calibrar seções críticas;
  • O writer ( updater) copia uma cópia como base para modificação ao atualizar recursos críticos.Quando todos os leitores saem da seção crítica, apontam o ponteiro para o recurso crítico antigo para a cópia atualizada e reciclam o recurso antigo;
  • Apenas um escritor é mostrado na figura.Quando existem vários autores, é necessária exclusão mútua entre escritores;

A descrição acima é relativamente simples e a implementação da RCU é muito complicada. Este artigo primeiro fornece uma primeira impressão do RCU e analisa o exemplo com a interface.Os artigos subsequentes serão aprofundados no princípio de implementação subjacente, camada por camada. Vamos lá!

2. Noções básicas de RCU

2.1 Elementos básicos da RCU

RCUA idéia básica é dividir a Updateoperação de atualização em duas partes: 1) Removalremover; 2) Reclamationreciclar.
Para ser franco, o recurso crítico é lido por vários leitores.Quando o gravador atualiza a cópia após a modificação, a primeira etapa precisa remover os dados antigos dos recursos críticos (o ponteiro da modificação aponta) e a segunda etapa requer Recicle dados antigos (por exemplo kfree).

Portanto, é funcionalmente dividido nos três seguintes elementos básicos: Reader/Updater/ReclaimerA interação entre os três é a seguinte:

  1. Leitor

    • Use rcu_read_locke rcu_read_unlockpara definir a área crítica do leitor Ao acessar os RCUdados protegidos, você sempre deve acessá-los na área crítica;
    • Antes de acessar os dados protegidos, você precisa usar rcu_dereferencepara obter o RCU-protectedponteiro;
    • Ao usar o não-preemptivo, RCUvocê rcu_read_lock/rcu_read_unlocknão pode usar o código que pode dormir;
  2. Atualizador

    • Quando vários Atualizadores atualizam dados, eles precisam usar um mecanismo de exclusão mútua para proteção;
    • Updater é usado rcu_assign_pointerpara remover o ponteiro antigo para apontar para os recursos críticos atualizados;
    • O Updater usa synchronize_rcuou, call_rcupara iniciar Reclaimer, reciclar os recursos críticos antigos, o que synchronize_rcusignifica aguardar a reciclagem de forma síncrona, o que call_rcusignifica reciclagem assíncrona;
  3. Reclaimer

    • Recuperador recicla antigos recursos críticos;
    • Para garantir que nenhum leitor esteja acessando os recursos críticos a serem recuperados, o Reclaimer precisa aguardar que todos os leitores saiam da seção crítica.Este tempo de espera é chamado de período de cortesia ( Grace Period);

2.2 Três mecanismos básicos da RCU

Usado para fornecer as funções descritas acima, com RCUbase em três mecanismos.

2.2.1 Publish-Subscribe Mechanism

Qual é o conceito do mecanismo de assinatura, vamos à figura:

  • UpdaterE o Readergosto Publishere Subsriberas relações;
  • UpdaterApós atualizar o conteúdo, chame a interface para publicar e Readerchame a interface para ler o conteúdo publicado;

Então, o que precisa ser feito para garantir esse mecanismo de assinatura? Vejamos um pseudo-có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 }

À primeira vista, parece que o problema não é muito grande: o Updater executa a atribuição e a atualização, e o Reader executa a leitura e outros processamentos. No entanto, devido aos problemas de compilação e execução fora de ordem, a ordem de execução do código acima pode não ser necessariamente a ordem do código.Por exemplo, em algumas arquiteturas ( DEC Alpha), a parte de operação do leitor pode ser operada antes da atribuição de p do_something_with().

Para resolver este problema, o Linux oferece rcu_assign_pointer/rcu_dereferencemacro para garantir a ordem de execução, kernel do Linux também é baseado em rcu_assign_pointer/rcu_dereferencemacros de pacotes de nível mais alto, como list, hlistportanto, há três protegida do kernel cena RCU: 1) ponteiro; 2) Lista da lista ; 3) lista vinculada de hash hlist.

Para esses três cenários, a Publish-Subscribeinterface é a seguinte:

2.2.2 Wait For Pre-Existing RCU Readers to Complete

O Reclaimer precisa reciclar os recursos críticos antigos; assim, surge a pergunta: quando isso acontecerá? Portanto, é RCUnecessário fornecer um mecanismo para garantir que todos os leitores RCU anteriores tenham sido concluídos, ou seja rcu_read_lock/rcu_read_unlock, eles só podem ser reciclados depois de sair da seção crítica calibrada.

  • Os leitores e o atualizador na figura são executados simultaneamente;
  • Quando o Atualizador executa a Removaloperação, ele é chamado synchronize_rcu, marcando o final da atualização e começando a entrar na fase de recuperação;
  • Após a synchronize_rcuchamada, pode haver novos leitores para ler recursos críticos (conteúdo atualizado) no momento, mas os leitores que estão Grace Periodapenas aguardando Pre-Existingestão na figura Reader-4, Reader-5. Desde que esses leitores da RCU que existiam antes tenham saído da seção crítica, isso significa o final do período de carência, portanto o processo de reciclagem é realizado;
  • synchronize_rcuNão é que o último Pre-Existingleitor de RCU saia imediatamente após sair da seção crítica; pode haver um atraso no agendamento;

2.2.3 Maintain Multiple Versions of Recently Updated Objects

Desde 2.2.2节que pode ser visto, depois de atualizar o Updater, antes Recuperadora para reciclagem, haverá duas versões do velho e do novo recurso crítico, e somente synchronize_rcuapós o retorno, RECLAIMER recursos críticos do antigo recuperado, deixando uma versão final. Obviamente, quando houver vários Atualizadores, haverá versões de recursos mais críticas.

Vamos tirar uma foto, usando ponteiros e listas vinculadas como exemplos:

  • O synchronize_rcuinício da chamada é um ponto crítico, mantendo diferentes versões de recursos críticos;
  • Após o Reclaimer recuperar a versão antiga dos recursos, ele finalmente é unificado;

3. Análise de exemplo de RCU

Está na hora de uma onda fucking sample code.

  • A lógica geral do código:
    1. Construa quatro threads do kernel, dois threads do kernel para testar a operação de proteção da RCU do ponteiro e dois threads do kernel para testar a operação de proteção da RCU da lista vinculada;
    2. No momento da reciclagem, foram utilizados dois mecanismos de synchronize_rcureciclagem síncrona e call_rcureciclagem assíncrona;
    3. Para simplificar o código, o julgamento básico da tolerância a falhas foi omitido;
    4. O mecanismo de vários Atualizadores não é considerado, portanto, a operação mutuamente exclusiva entre Atualizadores também é omitida;
#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 provar que não há engano, o log de saída em execução na placa de desenvolvimento é publicado, como mostrado abaixo:

4. Introdução à API

4.1 API principal

As seguintes interfaces não podem ser mais 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 Outras APIs relacionadas

Com base na API principal, outras APIs relacionadas foram estendidas, da seguinte forma, sem mais detalhes:

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

Ok, listar essas APIs é um pouco confuso.

O misterioso véu da RCU é inicialmente revelado, e então será um pouco difícil pegar as roupas por dentro, afinal, o mecanismo de implementação por trás da RCU é realmente difícil. Então, a pergunta está chegando: você quer ser um homem que vê o rei? Por favor, preste atenção.

Referência

Documentação / RCU
O que é RCU, fundamentalmente?
O que é RCU? Parte 2: Uso da
RCU parte 3: a API da RCU
Introdução à RCU

Bem-vindo a prestar atenção ao número público e continuar compartilhando os principais artigos do mecanismo em forma gráfica

Acho que você gosta

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