Sobre a importância de um bom nome: a transição da página do kernel do Linux para o fólio

1. Introdução

Era uma vez, Phil Karlton da Netscape disse uma vez: "Existem apenas duas coisas difíceis na Ciência da Computação: invalidação de cache e nomeação de coisas", que se tornou um ditado bem conhecido no mundo da programação. a coisa mais importante na ciência da computação. Uma das duas coisas difíceis. Nomeação, para usar o nome para descrever adequadamente o que ele deseja descrever e para refletir o princípio mais elevado do comentário de código - auto-comentário, o que na verdade não é nada fácil.

Os nomes geralmente são alterados de incomuns para populares, para que possam ser cativantes e populares entre as pessoas. Por exemplo, Chen Gangsheng mudou seu nome para Jackie Chan, Yang Niao mudou seu nome para Yang Zi e Liu Furong mudou seu nome para Andy Lau. E uma mudança do kernel de página para fólio parece estar fazendo o oposto. Parece que um número considerável de calçados infantis pode não conhecer necessariamente a palavra fólio. Kingsoft PowerWord disse uma vez que fólio significa isso:

2705cbe90330a5e873574c38257dc7b8.png

Significa aproximadamente que é um livro ou um conjunto de documentos colados na capa e na contracapa. Este nome, um pouco antiquado, visa resolver uma situação complicada enfrentada pelo kernel. Se o nome é folio, pageset, superpage ou head_page, não é tão importante, o que realmente importa por trás disso é o problema que ele resolve.

Dois, caos

Vejamos a situação do kernel do Linux antes do surgimento do fólio. Como todos sabemos, no kernel do Linux, usamos page para descrever uma página, e essa página geralmente tem 4 KB. Se todos neste mundo forem uma única página de 4KB, ela simplesmente será normalizada. No entanto, no céu claro, há uma nuvem escura flutuando, essa nuvem escura é a página composta e a página enorme derivada da página composta, nem sempre são uma única página.

No Linux, nem sempre adquirimos, mapeamos e liberamos memória em unidades de uma única página base de 4KB. Às vezes, combinamos vários 4KB para aplicação, mapeamento e liberação:

1. Transparent Huge Pages (THP) e HugeTLB Huge Pages no modo de usuário

Podemos usar PMD diretamente em vez de PTE para mapeamento e mapear uma memória física contínua de 2 MB para o modo de usuário, para que o modo de usuário possa usá-lo para reduzir significativamente a falta de TLB.

3c5368a7b09fec149144ac1ccee7e168.png

No kernel, a unidade que descrevemos como memória é a página, e essa página geralmente tem 4 KB. Mas no cenário THP/HugeTLB, todos os 2 MB são, na verdade, um conceito geral. Neste momento, temos uma necessidade:

  • Às vezes, o que nos interessa é o todo de 2 MB, mas a página está realmente descrevendo sua parte de 4 KB e parece inapropriado usar a página para descrever o todo;

  • Às vezes, eu realmente quero descrever uma certa parte de 4KB em 2MB inteiros, e a página parece ser mais adequada neste momento.

2. O estado do kernel também pode solicitar e liberar diretamente a página composta

Por exemplo, alguns drivers do kernel serão aplicados para páginas contínuas com uma ordem maior que 0 até a marca __GFP_COMP para formar a chamada página composta (como uma página composta composta por 2 páginas, 4 páginas, 8 páginas, 16 páginas, etc. ., o THP/HugeTLB anterior é, na verdade, um tipo de página composta com ordem maior, uma página composta composta por páginas de 2 MB/4 KB). Existem alguns drivers que solicitam memória do amigo por meio do sinalizador __GFP_COMP:

b0f53ddf48063ff68206de39f2117b04.png

Essas páginas compostas podem ser gerenciadas por meio do mecanismo gc unificado do kernel.Por exemplo, quando o refcount está prestes a retornar a 0, put_page (página composta) pode liberar a página composta como um todo.

Isso é obviamente o mesmo que a situação THP/HugeTLB descrita acima, e também há um problema de emaranhamento na semântica de partes e todos.

Como várias páginas formam um todo, haverá associações entre essas páginas, precisamos de uma maneira de resolver os seguintes problemas:

  1. N páginas formam um todo?

  2. Qual dessas páginas é a cabeça (página 0)?

  3. Quais destas páginas são coroas (1ª ~ N - 1ª)?

  4. Quantas dessas páginas existem?

  5. Se eu sou um rabo, quem é minha cabeça?

  6. Como liberar essas páginas como um todo? Quais ações destrutivas são necessárias ao liberar?

....

Antes do fólio aparecer, o kernel usava os seguintes métodos para resolver os problemas acima:

  • Coloque uma marca PG_head na estrutura da página 0 (page[0], ou seja, página inicial) da página composta composta por N 4KB, a lógica é a seguinte:

page->flags |= (1UL << PG_head);

Assim, se a 0ª estrutura da página for passada para a API PageHead(), como PG_head é true, esta API retorna true.

  • Defina 1 no último bit do composto_cabeçalho da estrutura da página 1st~N-1 (page[1] ~ Page[N-1], ou seja, página final) da página composta composta por N 4KB, a lógica é a seguinte:

página->compound_head |= 1UL;

Os bits diferentes de 0 apontam para a página inicial real, ou seja, page[0], portanto, logicamente, se a estrutura da página de 1 ~ N-1 for passada, as duas APIs a seguir podem, respectivamente, retirar a página inicial e determinar se o página relevante é uma página final (uma página composta diferente de page[0]):

ccd34ab6f76810b1185b8461acf421d9.png

  • No membro composite_order da estrutura page[1], coloque a ordem da página composta. Por exemplo, se for uma página composta composta por quatro KB consecutivos, então page[1].compound_order = 2. Portanto, se passarmos o head para a API composite_order, podemos obter o número do pedido da página composta:

eafe0ea07828233a630b59cf41b22fc4.png

  • No membro composite_dtor da estrutura page[1], coloque o destruidor da página composta. Este destruidor será executado quando put_page[page[0]] e o refcount estiver prestes a retornar a 0. Os destruidores de diferentes tipos de páginas compostas podem ser diferentes:

293869df912ce56d5ef6ce161f47c015.png

Todo o relacionamento da organização é o seguinte:

706a959d2d81cf06c06df55b969dd077.png

f61437ef18b66e8dadb9572c42254ec9.png

Claro, nos cenários HugeTLB e THP, page[2] tem mais funções de meio período (HugeTLB e THP não podem ter apenas 2 páginas, eles têm 2MB/4KB, então deve haver page[2]).

Por exemplo, HugeTLB pega emprestado page[2]->mapeamento de membros:

fc47a0848d6c335b54eff8663d2a195a.png

E o THP pega emprestado o deferred_list da página[2]:

e214f91f26fc8f6a184434f59a91c921.png

As N estruturas de página são conectadas por meio do relacionamento de série especial de sinalizadores, compostos_cabeçalho e membros compostos_dtor em página[0]~página[n-1]. Isso gera uma confusão, muitas vezes, o que realmente queremos operar é apenas a página composta inteira, como get_page(), put_page(), lock_page(), unlock_page(), etc. Portanto, em tal API, existem tais operações composta_head() amplamente:

68d46f01e1946e9a1e220276afdfb152.png

Tome get_page() como exemplo, a estrutura da página passada para get_page() pode, na verdade, estar em três situações:

  1. É uma página comum não composta de 4 KB. Neste momento, a API composite_head() realmente retorna essa página;

  2. A entrada é page[0] de uma página composta ( ou seja, a página inicial ).Neste momento, composite_head() retorna page[0];

  3. A entrada é page[1] ~ page[n] da página composta ( ou seja, a página final ). Neste momento, composite_head() retorna composite_head - 1, que é page[0].

Além disso, geralmente usamos page[0] para operar um grupo de páginas para operar a página composta inteira.

8b4ee5d482a886d60e2e712fe4638bb3.png

Podemos esclarecer essa semântica ambígua? Por exemplo, get_xxx(), este xxx significa que eu quero obter um inteiro? Outro exemplo é get_yyy() significa que eu quero operar uma página base yyy? Além disso, sob a semântica de get_xxx(), é impossível que o parâmetro da função seja yyy? Deixe o céu voltar ao céu, deixe o pó voltar ao pó, Ding é Ding, Mao é Mao, não é perfumado?

get_xxx(estrutura xxx *x);

get_yyy(struct aaaa *y);

em vez de

795a2b09c0273bb95f4f8fffca6c8ec4.png

Esse tipo de situação caótica pode facilmente guiar os programadores de maneira errada, porque quando os programadores escrevem código, se eles estão operando xxx ou yyy não está claro para eles. Portanto, é necessário realizar operações de distinção no corpo da função. Problemas semelhantes também existem em APIs como lock_page() e unlock_page(), como:

3197129ec4233456311d0cea3551d4e2.png

Na verdade, códigos excelentes são códigos que podem ser claramente entendidos, e as APIs prioritárias são APIs que forçam o chamador a entender claramente.

Se você observar o kernel mais recente, poderá ver dois conjuntos diferentes de APIs:

void folio_get(struct folio *folio);

void get_page(struct página *página);

void folio_lock(struct folio *folio);

void lock_page(struct página *página);

Um chamador claro, se sentir que está operando um todo, deve chamar folio_get, folio_lock Além disso, também o forçamos a entender que o parâmetro é folio em vez de page. Isso também agrada ao leitor do código, sem suposições. Porque, um dos princípios básicos da escrita de código é: Não me faça pensar! Os leitores do código não querem adivinhar se você quer fazer xxx ou yyy, apenas me diga diretamente.

Quando sabemos explicitamente que estamos operando em um todo/coleção, estamos operando em um fólio. Então, qual é a relação entre este fólio e a página? página faz parte do fólio. Mas qual é a definição da estrutura do fólio? Na versão original do patch, na verdade era:

c3a73a9ff57049c73ccb261809db7303.png

No que diz respeito à própria estrutura de dados, o fólio é essencialmente uma estrutura de página, mas foi renomeado. Folio é essencialmente um conceito de coleção, por exemplo, representa uma turma, mas sua estrutura de dados tem o mesmo tamanho de palavra que a estrutura de dados que representa cada aluno da turma. Por exemplo, seu nome é Huang Xiaoming, você é o líder de uma equipe de desenvolvimento e é um engenheiro. Sua estrutura de dados é na verdade a mesma dos engenheiros comuns. No entanto, às vezes o líder dizia que esse assunto deveria ser feito por Huang Xiaoming. O que ele realmente disse foi que o grupo de Huang Xiaoming veio para fazer isso, e Huang Xiaoming se tornou um conceito coletivo nessa época. No final, Huang Xiaoming realmente tinha a mesma estrutura de dados que outros engenheiros, mas o líder disse que seria muito mais claro pedir a Huang Xiaoming para fazer isso do que dizer "deixe um engenheiro fazer isso". A lógica é tal lógica, que reflete a limpeza da comunidade do kernel e o princípio da auto-comentário do código.

Os primeiros patches cresceram assim:

3c85e6bb8cfe7af3e9ce4d0ec1bc5511.png

Isso é um pouco inconveniente, porque para operar os sinalizadores de um fólio, LRU, private e outros membros, precisamos primeiro realizar uma operação fólio->página, como:

bc36eb12174bce2e803c0776f1d91ef5.png

Portanto, o fólio oficialmente integrado ao kernel do Linux 5.16 se parece com isso, e alguns campos comumente usados ​​na página são extraídos na união na mesma posição da página:

0d1fcf84aa27fa8b60bfc2d154b77d2d.png

Se você ainda não percebeu, talvez fique mais claro organizar fólio e página lado a lado:

1e007af2e807bda5ce3b20a8d388a84c.png

Para ser franco, é um jogo matemático simples em que os membros com o mesmo nome estão na mesma posição de deslocamento. Desta forma, o acesso ao privado da folha anterior pode ser feito diretamente:

1928f21c01c1dd886ff5857b4a3133aa.png

Três, quebrando o jogo

A partir disso, descobrimos que o fólio não é uma coisa nova, mas uma coisa com conceito de coleção, e a estrutura de dados é equivalente à página. Assim pelo menos desmistificamos o fólio.

Vejamos as notas sobre o fólio no kernel:

Um fólio é um conjunto de bytes fisicamente, virtualmente e logicamente contíguos. É uma potência de dois em tamanho e está alinhada a essa mesma potência de dois. É pelo menos tão grande quanto %PAGE_SIZE. Se estiver no cache da página, está em um deslocamento de arquivo que é um múltiplo dessa potência de dois. Ele pode ser mapeado no espaço do usuário em um endereço que está em um deslocamento de página arbitrário, mas seu endereço virtual do kernel está alinhado ao seu tamanho.

Na verdade, fólio é uma coleção de alguns bytes de PAGE_SIZE que são fisicamente contínuos e virtualmente contínuos 2^n vezes. Claro, esse n também pode ser 0. Nessa época, alguns sapatos infantis saltaram. Por que uma coleção de uma página também pode ser chamada de fólio? Você faz essa pergunta para ferir o coração da maioria dos solteiros: não se chama família morar sozinho? O número de membros da família é um, sabe como?

Uma coisa é certa sobre o fólio, não deve ser uma página final. Desta forma, evita-se a confusão semântica do xxx e yyy anteriores (ou seja, a confusão da estrutura da página mencionada pela comunidade Linux).

Depois de entender o conceito, na prática, é relativamente simples. O desenvolvimento do Folio foi concluído em várias etapas, e a solicitação git pull na primeira etapa tinha 90 patches:

8455804dd9c37d1b817a83c204f7e76c.png

Simplesmente olhando para esse número, pode haver alguns calçados infantis que vão direto da entrada para a desistência. No entanto, quando você realmente clica e olha para ele, é realmente um jogo de substituição muito simples. Por exemplo, clicamos aleatoriamente em um mm: Add folio_pfn() [1] Isso é para encontrar o pfn do fólio, como ele se parece? Não simplifique demais:

ebcfe68df6683e6a8951f4e5d4c6f1f5.png

Portanto, para entender o fólio, o mais essencial é entender quando usar o fólio, e usar o fólio como fólio, para quebrar a névoa em seu coração.

No nível do Linux, pelo menos, mas não limitado ao seguinte, deve haver uma coleção:

1. Adicionar lruvec para gerenciamento de recuperação de memória deve ser uma coleção, que é uma página composta ou uma "coleção" comum de página única. No cenário THP de página enorme transparente do kernel, o THP é realmente adicionado ao lruvec como um todo. A alteração dos parâmetros relevantes do lruvec para o fólio pode se adaptar a uma ampla gama de situações: THP e não-THP entram no lruvec. Por exemplo, a famosa função shrink_page_list(), agora chamada de shrink_folio_list(), também é um fólio obtido do lruvec:

6f1d8d51a22e7434825f890921dfd76f.png

2.contagem refcount, bloqueio, etc. deve ser uma coleção

por exemplo:

5a950b4e3f3fa849d77c73b64085d3b5.png

Mesmo que você carregue uma determinada página no fólio, o que eu bloqueio ainda é uma coleção:

d165a251cbd185920c39f4b7e128fc1f.png

3. O encargo contábil do mem_cgroup etc. deve ser um conjunto;

e1656ee6740b34bc9c6fd46d9b464bd2.png

4.wait writeback, bit, etc. deve ser uma coleção, como:

folio_wait_bit(struct folio *folio, int bit_nr);

void folio_wait_writeback(struct folio *folio);

3c255ba4997a6c26671dfec604e5aa49.png

5. As operações de pesquisa, inserção e exclusão do cache da página vinculada ao address_space devem ser uma coleção, porque o cache da página também pode ser THP. Códigos relacionados, como:

537477a216376b2c934e35cc9fe7ac62.png

5156e40657f3e8ccf1c73285645be13b.png

6. A unidade relacionada ao rmap deve ser uma coleção

Como o cache da página da página do arquivo e a página anônima do processo podem ser THP, portanto, em APIs como mapeamento reverso, a operação também deve ser fólio, por exemplo, na função clássica de processamento de falhas de página anônima de do_anonymous_page( struct vm_fault *vmf) e, finalmente, as operações relacionadas a rmap e lruvec são fólio:

0b9e7af30bf54a4f365b68f2db0c47c5.png

Claro, as grandes mudanças na história não serão concluídas em um instante. A conversão da semântica da página para a semântica do fólio não é realizada da noite para o dia, portanto, você pode examinar os últimos envios do kernel do Linux e alguns ainda estão em processo de escape. Esses escapes ocorrem em várias áreas, como sistema de arquivos, gerenciamento de memória, etc.:

fd9e83e7ee0b3e79d4d4d9f1b99d8120.png

Tendo em vista que as estruturas de dados do fólio e da página são essencialmente iguais no sentido da memória, o código que é difícil de mudar a curto prazo com base em razões históricas, se você ainda usa a página, pode realmente alternar entre os fólio e a página com relativa facilidade. Por exemplo, o mais simples é forçar a conversão:

página struct *p = (página struct *) fólio;

É claro que isso é um "mau cheiro" do código.

Como o fólio é uma semântica de coleção, quando nos preocupamos com uma parte de uma coleção ou se uma parte pertence a uma coleção composta, ainda nos preocupamos com as páginas. Por exemplo, as APIs a seguir determinam, respectivamente, se uma página é um final e uma página pertencem a um composto:

26c5781731862fe36785cf7eb5fe41fa.png

Por exemplo, na API a seguir, se você copiar uma coleção de fólios inteira, precisará copiar cada parte da página dentro dela:

9ba719d4d14d9dc7e3984c8dc60245a2.png

folio_page(folio, n) Esta API pode remover a enésima página em um fólio.

Quatro. Conclusão

Deixe a coleção ser a coleção, deixe o indivíduo ser o indivíduo e analisar em detalhes é o ponto de partida fundamental do design de fólio. A origem do problema é mais importante do que como resolvê-lo. Embora Linus Torvalds não goste do nome fólio, ele concorda com o problema que o fólio pretende resolver, que é mais importante do que ser chamado de fólio ou liu esburacado.

referências:

【1】mm:Addfolio_pfn()https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=bf6bd276b374d44

【2】 Esclarecendo o gerenciamento de memória com fólios de página https://lwn.net/Articles/849538/

【3】O radar do kernel: fólios, LRU multigeracional e Rust

https://lwn.net/Articles/881675/

Passado

Esperar

empurrar

recomendar

ShaderNN 2.0: um mecanismo de inferência móvel eficiente e leve baseado na pilha de gráficos completos da GPU

Arquitetura multiprocesso do Chromium, quanto você sabe?

Dez minutos para entender o princípio do HEVC-SCC

e6a4452006c99793ca93e6772345aea0.gif

Pressione e segure para seguir Kernel Craftsman WeChat

Tecnologia Linux Kernel Black | Artigos técnicos | Tutoriais em destaque

Acho que você gosta

Origin blog.csdn.net/feelabclihu/article/details/131485936
Recomendado
Clasificación