Antecedentes
Read the fucking source code!
--Por Lu XunA picture is worth a thousand words.
--Por Gorky
Descrição:
- Versão do kernel: 4.14
- Processador ARM64, Contex-A53, núcleo duplo
- 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. RCU
O objetivo é minimizar a sobrecarga no lado do leitor, por isso também é comumente usado em cenários que exigem alto desempenho do leitor.
-
Vantagens:
- 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;
- Nenhum problema de impasse;
- Não há problema de inversão de prioridade;
- Não há perigo de vazamento de memória;
- Muito bom atraso em tempo real;
-
Desvantagens:
- A sobrecarga de sincronização do gravador é relativamente grande e os gravadores precisam ser mutuamente exclusivos.
- É 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_unlock
los 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
RCU
A idéia básica é dividir a Update
operação de atualização em duas partes: 1) Removal
remover; 2) Reclamation
reciclar.
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/Reclaimer
A interação entre os três é a seguinte:
-
Leitor
- Use
rcu_read_lock
ercu_read_unlock
para definir a área crítica do leitor Ao acessar osRCU
dados protegidos, você sempre deve acessá-los na área crítica; - Antes de acessar os dados protegidos, você precisa usar
rcu_dereference
para obter oRCU-protected
ponteiro; - Ao usar o não-preemptivo,
RCU
vocêrcu_read_lock/rcu_read_unlock
não pode usar o código que pode dormir;
- Use
-
Atualizador
- Quando vários Atualizadores atualizam dados, eles precisam usar um mecanismo de exclusão mútua para proteção;
- Updater é usado
rcu_assign_pointer
para remover o ponteiro antigo para apontar para os recursos críticos atualizados; - O Updater usa
synchronize_rcu
ou,call_rcu
para iniciarReclaimer
, reciclar os recursos críticos antigos, o quesynchronize_rcu
significa aguardar a reciclagem de forma síncrona, o quecall_rcu
significa reciclagem assíncrona;
-
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 RCU
base em três mecanismos.
2.2.1 Publish-Subscribe Mechanism
Qual é o conceito do mecanismo de assinatura, vamos à figura:
Updater
E oReader
gostoPublisher
eSubsriber
as relações;Updater
Após atualizar o conteúdo, chame a interface para publicar eReader
chame 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_dereference
macro para garantir a ordem de execução, kernel do Linux também é baseado em rcu_assign_pointer/rcu_dereference
macros de pacotes de nível mais alto, como list
, hlist
portanto, 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-Subscribe
interface é 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, é RCU
necessá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
Removal
operação, ele é chamadosynchronize_rcu
, marcando o final da atualização e começando a entrar na fase de recuperação; - Após a
synchronize_rcu
chamada, pode haver novos leitores para ler recursos críticos (conteúdo atualizado) no momento, mas os leitores que estãoGrace Period
apenas aguardandoPre-Existing
estão na figuraReader-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_rcu
Não é que o últimoPre-Existing
leitor 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_rcu
apó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_rcu
iní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:
- 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;
- No momento da reciclagem, foram utilizados dois mecanismos de
synchronize_rcu
reciclagem síncrona ecall_rcu
reciclagem assíncrona; - Para simplificar o código, o julgamento básico da tolerância a falhas foi omitido;
- 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