                Elementos de design do sistema de VM do FreeBSD

  Matthew Dillon

       <dillon@apollo.backplane.com>
     

   Revisao: 4e5fc58f9b

   FreeBSD is a registered trademark of the FreeBSD Foundation.

   Linux is a registered trademark of Linus Torvalds.

   Microsoft, IntelliMouse, MS-DOS, Outlook, Windows, Windows Media and
   Windows NT are either registered trademarks or trademarks of Microsoft
   Corporation in the United States and/or other countries.

   Motif, OSF/1, and UNIX are registered trademarks and IT DialTone and The
   Open Group are trademarks of The Open Group in the United States and other
   countries.

   Many of the designations used by manufacturers and sellers to distinguish
   their products are claimed as trademarks. Where those designations appear
   in this document, and the FreeBSD Project was aware of the trademark
   claim, the designations have been followed by the "(TM)" or the "(R)"
   symbol.

   Este artigo foi publicado originalmente na edic,ao de janeiro de 2000 do
   DaemonNews. Esta versao do artigo pode incluir atualizac,oes feitas por
   Matt e outros autores para refletir as mudanc,as na implementac,ao da VM
   do FreeBSD.

   2018-09-13 02:34:41 +0000 por Edson Brandi.
   Resumo

   O titulo e realmente apenas uma maneira extravagante de dizer que vou
   tentar descrever todo o grupo de itens de uma VM, espero que de uma forma
   que todos possam acompanhar. Pelo ultimo ano eu me concentrei em varios
   dos principais subsistemas do kernel dentro do FreeBSD, com os subsistemas
   VM e Swap sendo os mais interessantes e o NFS sendo "uma tarefa
   necessaria". Eu reescrevi apenas pequenas partes do codigo. Na area de VM,
   a unica grande reescrita que fiz foi para o subsistema de troca. A maior
   parte do meu trabalho foi de limpeza e manutenc,ao, com apenas uma
   moderada reescrita de codigo e sem grandes ajustes nos algoritimos do
   subsistema VM. A maior parte da base teorica do subsistema VM permanece
   inalterada e muito do credito pelo esforc,o de modernizac,ao nos ultimos
   anos pertence a John Dyson e David Greenman. Nao sendo um historiador como
   Kirk, eu nao tentarei marcar todos os varios recursos com nomes de
   pessoas, ja que invariavelmente vou errar.

   [ Documento HTML em partes / Documento HTML completo ]

     ----------------------------------------------------------------------

   Indice

   1. Introduc,ao

   2. Objetos de VM

   3. Camadas de SWAP

   4. Quando libertar uma pagina

   5. Otimizac,oes de Pre-Falhas ou para Zerar

   6. Otimizac,oes da Tabela de Paginas

   7. Page Coloring

   8. Conclusao

   9. Sessao bonus de QA por Allen Briggs <briggs@ninthwonder.com>

1. Introduc,ao

   Antes de avanc,armos para o design atual, vamos dedicar um pouco de tempo
   a necessidade de manter e modernizar qualquer base de codigo duradoura. No
   mundo da programac,ao, os algoritmos tendem a ser mais importantes do que
   o codigo, e e precisamente devido as raizes academicas do BSD que uma
   grande atenc,ao foi dada ao design do algoritmo desde o inicio. Mais
   atenc,ao ao design geralmente leva a uma base de codigo limpa e flexivel
   que pode ser facilmente modificada, estendida ou substituida ao longo do
   tempo. Embora o BSD seja considerado um sistema operacional "antigo" por
   algumas pessoas, aqueles de nos que trabalham nele tendem a ve-lo mais
   como uma base de codigo "madura" que possui varios componentes
   modificados, estendidos, ou substituido por codigo moderno. Ele evoluiu e
   o FreeBSD esta no topo, nao importa quantos anos tenha o codigo. Esta e
   uma distinc,ao importante a ser feita e infelizmente perdida para muitas
   pessoas. O maior erro que um programador pode cometer e nao aprender com a
   historia, e esse e precisamente o erro que muitos outros sistemas
   operacionais modernos cometeram. Windows NT(R) e o melhor exemplo disso, e
   as consequ:encias foram terriveis. O Linux tambem cometeu esse erro ate
   certo ponto - o suficiente para que nos, do BSD, possamos fazer pequenas
   piadas sobre isso de vez em quando, entretanto. O problema do Linux e
   simplesmente a falta de experiencia e historico para comparar ideias, um
   problema que esta sendo resolvido de forma facil e rapida pela comunidade
   Linux, da mesma forma como foi abordado na comunidade BSD - pelo
   desenvolvimento continuo de codigo. Por outro lado, o povo do Windows
   NT(R), repetidamente comete os mesmos erros resolvidos no UNIX(R) decadas
   atras e depois gasta anos corrigindo-os. De novo e de novo. Eles tem um
   caso grave de "nao foi projetado aqui" e "estamos sempre certos porque
   nosso departamento de marketing diz que sim". Tenho pouca tolerancia para
   quem nao pode aprender com a historia.

   Grande parte da complexidade aparente do design do FreeBSD, especialmente
   no subsistema VM/Swap, e um resultado direto de ter que resolver serios
   problemas de desempenho que ocorrem sob varias condic,oes. Estes problemas
   nao se devem ao mau design de algoritimo, mas sim a fatores ambientais. Em
   qualquer comparac,ao direta entre plataformas, estes problemas tornam-se
   mais aparentes quando os recursos do sistema comec,am a ficar estressados.
   Como descrevo o subsistema VM/Swap do FreeBSD, o leitor deve sempre manter
   dois pontos em mente:

    1. O aspecto mais importante do design de desempenho e o que e conhecido
       como "Otimizando o Caminho Critico ". Muitas vezes, as otimizac,oes de
       desempenho inflam um pouco o codigo, para que o caminho critico tenha
       um melhor desempenho.

    2. Um design solido e generalizado supera um projeto altamente otimizado
       a longo prazo. Enquanto um design generalizado pode acabar sendo mais
       lento do que um projeto altamente otimizado quando eles sao
       implementados pela primeira vez, o design generalizado tende a ser
       mais facil de se adaptar as mudanc,as de condic,oes e o projeto
       altamente otimizado acaba tendo que ser descartado.

   Qualquer base de codigo que sobreviva e seja sustentavel por anos deve,
   portanto, ser projetada adequadamente desde o inicio, mesmo que isso custe
   algum desempenho. Vinte anos atras, as pessoas ainda argumentavam que
   programar em assembly era melhor do que programar em uma linguagem de alto
   nivel porque produzia um codigo que era dez vezes mais rapido. Hoje, a
   queda desse argumento e obvia - assim como os paralelos com o design de
   algoritimo e a generalizac,ao de codigo.

2. Objetos de VM

   A melhor maneira de comec,ar a descrever o sistema de VM do FreeBSD e
   examina-lo da perspectiva de um processo em nivel de usuario. Cada
   processo do usuario ve um espac,o de enderec,o de VM unico, privado e
   contiguo, contendo varios tipos de objetos de memoria. Esses objetos
   possuem varias caracteristicas. O codigo do programa e os dados do
   programa sao efetivamente um unico arquivo mapeado na memoria (o arquivo
   binario sendo executado), mas o codigo do programa e read-only enquanto os
   dados do programa sao copy-on-write. O programa BSS e apenas memoria
   alocada e preenchida com zeros sob demanda, chamado de demanda de
   preenchimento de pagina com zero. Arquivos arbitrarios tambem podem ser
   mapeados na memoria dentro do espac,o de enderec,amento como bem entender,
   que e como o mecanismo de biblioteca compartilhada funciona. Esses
   mapeamentos podem exigir modificac,oes para permanecerem privados para o
   processo que os produz. A chamada do sistema de fork adiciona uma dimensao
   totalmente nova ao problema de gerenciamento de VMs alem da complexidade
   ja fornecida.

   Uma pagina de dados binarios do programa (que e uma pagina basica de
   copy-on-write) ilustra a complexidade. Um programa binario contem uma
   sec,ao de dados pre-inicializada que e inicialmente mapeada diretamente a
   partir do arquivo de programa. Quando um programa e carregado no espac,o
   de VM de um processo, esta area e inicialmente mapeada na memoria e
   suportada pelo proprio binario do programa, permitindo que o sistema de VM
   liberte/reutilize a pagina e depois carregue-a de volta a partir do
   binario. No entanto, no momento em que um processo modifica esses dados, o
   sistema de VM deve fazer uma copia privada da pagina para esse processo. A
   partir do momento que a copia privada tenha sido modificada, o sistema de
   VM pode nao mais libera-la, porque nao ha mais como restaura-la depois.

   Voce notara imediatamente que o que originalmente era um mapeamento de
   arquivo simples se tornou muito mais complexo. Os dados podem ser
   modificados pagina a pagina, enquanto o mapeamento de arquivos abrange
   muitas paginas de uma so vez. A complexidade aumenta ainda mais quando
   existe um fork do processo. Quando um processo se duplica, o resultado sao
   dois processos - cada um com seus proprios espac,os de enderec,amento
   privados, incluindo quaisquer modificac,oes feitas pelo processo original
   antes de chamar um fork(). Seria bobagem o sistema de VM fizesse uma copia
   completa dos dados no momento do fork() porque e bem possivel que pelo
   menos um dos dois processos precise apenas ler essa pagina a partir de
   entao, permitindo que a pagina original continue a ser usada. O que era
   uma pagina privada e feito um copy-on-write novamente, ja que cada
   processo (pai e filho) espera que suas proprias modificac,oes pos-fork
   permanec,am privadas para si mesmas e nao afetem a outra.

   O FreeBSD gerencia tudo isso com um modelo de objetos de VM em camadas. O
   arquivo de programa binario original acaba sendo a camada de objeto de VM
   mais baixa. Uma camada copy-on-write e colocada acima dela para conter as
   paginas que tiveram que ser copiadas do arquivo original. Se o programa
   modificar uma pagina de dados pertencente ao arquivo original, o sistema
   de VM assumira que existe uma falha e fara uma copia da pagina na camada
   superior. Quando existe um fork do processo, as camadas adicionais de
   objetos de VM sao ativadas. Isso pode fazer um pouco mais de sentido com
   um exemplo bastante basico. Um fork() e uma operac,ao comum para qualquer
   sistema *BSD, entao este exemplo ira considerar um programa que inicia e e
   feito um fork. Quando o processo e iniciado, o sistema de VM cria uma
   camada de objeto, vamos chamar isso de A:

   A picture

   A representa o arquivo - as paginas podem ser paginadas dentro e fora da
   midia fisica do arquivo, conforme necessario. Paginar a partir do disco e
   razoavel para um programa, mas nos realmente nao queremos voltar atras e
   sobrescrever o executavel. O sistema de VM, portanto, cria uma segunda
   camada, B, que sera fisicamente suportada pelo espac,o de troca:

   Na primeira escrita em uma pagina depois disso, uma nova pagina e criada
   em B e seu conteudo e inicializado a partir de A. Todas as paginas em B
   podem ser paginadas para dentro ou para fora por um dispositivo de troca.
   Quando e feito o fork do programa, o sistema de VM cria duas novas camadas
   de objetos - C1 para o processo pai e C2 para o filho - que ficam no topo
   de B:

   Neste caso, digamos que uma pagina em B seja modificada pelo processo pai
   original. O processo tera uma falha de copy-on-write e duplicara a pagina
   em C1, deixando a pagina original em B intocada. Agora, digamos que a
   mesma pagina em B seja modificada pelo processo filho. O processo assumira
   uma falha de copy-on-write e duplicara a pagina em C2. A pagina original
   em B agora esta completamente oculta, ja que C1 e C2 tem uma copia e B
   poderia, teoricamente, ser destruido se nao representasse um arquivo
   "real"; no entanto, esse tipo de otimizac,ao nao e trivial de se fazer,
   porque e muito refinado. O FreeBSD nao faz essa otimizac,ao. Agora,
   suponha (como e frequentemente o caso) que o processo filho execute um
   exec(). Seu espac,o de enderec,o atual e geralmente substituido por um
   novo espac,o de enderec,o representando um novo arquivo. Nesse caso, a
   camada C2 e destruida:

   Neste caso, o numero de filhos de B cai para um, e todos os acessos para B
   passam agora por C1. Isso significa que B e C1 podem ser unidas. Todas as
   paginas em B que tambem existem em C1 sao excluidas de B durante a uniao.
   Assim, mesmo que a otimizac,ao na etapa anterior nao possa ser feita,
   podemos recuperar as paginas mortas quando um dos processos finalizar ou
   executar um exec().

   Este modelo cria varios problemas potenciais. O primeiro e que voce pode
   acabar com uma pilha relativamente profunda de objetos de VM em camadas,
   que pode custar tempo de varredura e memoria quando ocorrer uma falha.
   Camadas profundas podem ocorrer quando houver forks dos processos e, em
   seguida, houver um fork novamente (do processo pai ou filho). O segundo
   problema e que voce pode acabar com paginas profundas inacessiveis e
   mortas no meio da pilha de objetos de VM. Em nosso ultimo exemplo, se os
   processos pai e filho modificarem a mesma pagina, ambos receberao suas
   proprias copias privadas da pagina e a pagina original em B nao podera
   mais ser acessada por ninguem. Essa pagina em B pode ser liberada.

   O FreeBSD resolve o problema de camadas profundas com uma otimizac,ao
   especial chamada "All Shadowed Case". Este caso ocorre se C1 ou C2 tiverem
   falhas de COW suficientes para fazer uma copia de sombra completa de todas
   as paginas em B. Digamos que C1 consiga isso. C1 agora pode ignorar B
   completamente, entao, em vez de temos C1->B->A e C2->B->A temos agora
   C1->A e C2->B->A. Mas veja o que tambem aconteceu - agora B tem apenas uma
   referencia (C2), entao podemos unir B e C2. O resultado final e que B e
   deletado inteiramente e temos C1->A e C2->A. E comum que B contenha um
   grande numero de paginas e nem C1 nem C2 possam ofuscar completamente. Se
   nos forc,armos novamente e criarmos um conjunto de camadas D, no entanto,
   e muito mais provavel que uma das camadas D eventualmente seja capaz de
   ofuscar completamente o conjunto de dados muito menor representado por C1
   ou C2. A mesma otimizac,ao funcionara em qualquer ponto do grafico e o
   grande resultado disso e que, mesmo em uma maquina diversos forks, pilhas
   de objetos da VM tendem a nao ficar muito mais profundas do que 4. Isso e
   verdade tanto para o processo pai quanto para os filhos e verdadeiro quer
   seja o processo pai fazendo o fork ou os processos filhos fazendo forks em
   cascata.

   O problema da pagina morta ainda existe no caso em que C1 ou C2 nao
   ofuscaram completamente as paginas de B. Devido as nossas outras
   otimizac,oes, este caso nao representa um grande problema e simplesmente
   permitimos que as paginas fiquem inativas. Se o sistema ficar com pouca
   memoria, ele ira troca-las, comendo uma pequena parte da swap, mas e isso.

   A vantagem do modelo de objetos de VM e que o fork() e extremamente
   rapido, ja que nao e necessaria nenhuma copia de dados real. A desvantagem
   e que voce pode criar uma camada de Objetos de VM relativamente complexa
   que reduz um pouco o tratamento de falhas de pagina e gasta memoria
   gerenciando as estruturas de Objetos de VM. As otimizac,oes que o FreeBSD
   faz prova reduzir os problemas o suficiente para que as falhas possam ser
   ignoradas, nao deixando nenhuma desvantagem real.

3. Camadas de SWAP

   As paginas de dados privadas sao inicialmente paginas copy-on-write ou
   zero-fill. Quando uma alterac,ao e, portanto, uma copia, e feita, o objeto
   de apoio original (geralmente um arquivo) nao pode mais ser usado para
   salvar uma copia da pagina quando o sistema da VM precisar reutiliza-lo
   para outras finalidades. E ai que o SWAP entra. O SWAP e alocado para
   criar um suporte de armazenamento para a memoria que nao o possui. O
   FreeBSD aloca a estrutura de gerenciamento de troca para um objeto de VM
   somente quando for realmente necessario. No entanto, historicamente, a
   estrutura de gerenciamento de troca teve problemas:

     * Sob o FreeBSD 3.X, a estrutura de gerenciamento de swap pre-aloca uma
       matriz que engloba todo o objeto que requer suporte para armazenamento
       da swap - mesmo que apenas algumas paginas desse objeto sejam
       suportadas por swap. Isto cria um problema de fragmentac,ao de memoria
       do kernel quando grandes objetos sao mapeados ou processos com fork de
       grandes runsizes (RSS).

     * Alem disso, para manter o controle do espac,o de swap, uma "lista de
       espac,os vazios" e mantida na memoria do kernel, e isso tende a ficar
       severamente fragmentado tambem. Como a lista "de espac,os vazios" e
       uma lista linear, o desempenho de alocac,ao e liberac,ao de swap e uma
       troca O(n)-per-page (Uma por pagina) nao ideal.

     * Requer que as alocac,oes de memoria do kernel ocorram durante o
       processo de troca de swap, e isto cria problemas de deadlock de pouca
       memoria.

     * O problema e ainda mais exacerbado por buracos criados devido ao
       algoritmo de intercalac,ao.

     * Alem disso, o mapa de blocos da swap pode se fragmentar com bastante
       facilidade, resultando em alocac,oes nao contiguas.

     * A memoria do kernel tambem deve ser alocada dinamicamente para
       estruturas adicionais de gerenciamento da swap quando ocorre uma
       troca.

   E evidente a partir dessa lista que havia muito espac,o para melhorias.
   Para o FreeBSD 4.X, eu reescrevi completamente o subsistema de swap:

     * As estruturas de gerenciamento de swap sao alocadas por meio de uma
       tabela de hash, em vez de um array linear, fornecendo um tamanho de
       alocac,ao fixo e uma granularidade muito mais fina.

     * Em vez de usar uma lista vinculada linearmente para acompanhar as
       reservas de espac,o de troca, ele agora usa um bitmap de blocos de
       troca organizados em uma estrutura de arvores raiz com dicas de
       espac,o livre nas estruturas do no de origem. Isto efetivamente faz a
       alocac,ao de swap e libera uma operac,ao O(1).

     * Todo o bitmap da arvore raiz tambem e pre-alocado para evitar ter que
       alocar a memoria do kernel durante operac,oes criticas de troca com
       memoria baixa. Afinal de contas, o sistema tende a trocar quando esta
       com pouca memoria, por isso devemos evitar a alocac,ao da memoria do
       kernel nesses momentos para evitar possiveis deadlocks.

     * Para reduzir a fragmentac,ao, a arvore raiz e capaz de alocar grandes
       blocos contiguos de uma so vez, pulando pedac,os menores e
       fragmentados.

   Eu nao dei o ultimo passo de ter um "ponteiro de sugestao de alocac,ao"
   que percorria uma porc,ao da swap conforme as alocac,oes eram feitas a fim
   de garantir alocac,oes contiguas ou pelo menos a referencia localmente,
   mas assegurei que tal adic,ao poderia ser feita.

4. Quando libertar uma pagina

   Como o sistema de VM usa toda a memoria disponivel para o cache em disco,
   geralmente ha poucas paginas realmente livres. O sistema de VM depende de
   poder escolher corretamente as paginas que nao estao em uso para
   reutilizar em novas alocac,oes. Selecionar as paginas ideais para liberar
   e possivelmente a func,ao mais importante que qualquer sistema de VM pode
   executar, porque se fizer uma selec,ao ruim, o sistema de VM podera ser
   desnecessariamente forc,ado a recuperar paginas do disco, prejudicando
   seriamente o desempenho do sistema.

   Quanta sobrecarga estamos dispostos a sofrer no caminho critico para
   evitar a liberac,ao da pagina errada? Cada escolha errada que fazemos nos
   custara centenas de milhares de ciclos da CPU e uma paralisac,ao notavel
   dos processos afetados, por isto estamos dispostos a suportar uma
   quantidade significativa de sobrecarga, a fim de ter certeza de que a
   pagina certa e escolhida. E por isto que o FreeBSD tende a superar outros
   sistemas quando os recursos de memoria ficam estressados.

   O algoritmo de determinac,ao de pagina livre e construido sobre um
   historico do uso das paginas de memoria. Para adquirir este historico, o
   sistema tira proveito de um recurso de um bit usado pela pagina que a
   maioria das tabelas de pagina de hardware possui.

   Em qualquer caso, o bit usado na pagina e desmarcado e, em algum momento
   posterior, o sistema de VM encontra a pagina novamente e ve que o bit
   usado na pagina foi definido. Isso indica que a pagina ainda esta sendo
   usada ativamente. Se o bit ainda estiver desmarcado, e uma indicac,ao de
   que a pagina nao esta sendo usada ativamente. Ao testar este bit
   periodicamente, e desenvolvido um historico de uso (na forma de um
   contador) para a pagina fisica. Quando, posteriormente, o sistema de VM
   precisar liberar algumas paginas, a verificac,ao desse historico se
   tornara a base da determinac,ao da melhor pagina candidata a ser
   reutilizada.

   Para as plataformas que nao possuem esse recurso, o sistema realmente
   emula um bit usado na pagina. Ele remove o mapeamento ou protege uma
   pagina, forc,ando uma falha de pagina se a pagina for acessada novamente.
   Quando a falha de pagina acontece, o sistema simplesmente marca a pagina
   como tendo sido usada e desprotege a pagina para que ela possa ser usada.
   Embora a tomada de tais falhas de pagina apenas para determinar se uma
   pagina esta sendo usada parec,a ser uma proposta cara, e muito menos
   dispendioso do que reutilizar a pagina para outra finalidade, apenas para
   descobrir que um processo precisa dela e depois ir para o disco .

   O FreeBSD faz uso de varias filas de paginas para refinar ainda mais a
   selec,ao de paginas para reutilizac,ao, bem como para determinar quando
   paginas inativas devem ser liberadas para o suporte ao armazenamento. Como
   as tabelas de paginas sao entidades dinamicas sob o FreeBSD, nao custa
   virtualmente nada desmapear uma pagina do espac,o de enderec,o de qualquer
   processo que a utilize. Quando uma pagina cadidata ser escolhida com base
   no contador de uso de pagina, isso e precisamente o que e feito. O sistema
   deve fazer uma distinc,ao entre paginas limpas que teoricamente podem ser
   liberadas a qualquer momento, e paginas inativas que devem primeiro ser
   escritas em seu repositorio de armazenamento antes de serem reutilizaveis.
   Quando uma pagina candidata for encontrada, ela sera movida para a fila
   inativa, se estiver inativas, ou para a fila de cache, se estiver limpa.
   Um algoritmo separado baseado na proporc,ao de paginas inativas para
   limpas determina quando paginas inativas na fila inativa devem ser
   liberadas para o disco. Depois que isso for feito, as paginas liberadas
   serao movidas da fila inativa para a fila de cache. Neste ponto, as
   paginas na fila de cache ainda podem ser reativadas por uma falha de VM a
   um custo relativamente baixo. No entanto, as paginas na fila de cache sao
   consideradas "imediatamente livres" e serao reutilizadas em uma forma LRU
   (usada menos recentemente) quando o sistema precisar alocar nova memoria.

   E importante notar que o sistema de VM do FreeBSD tenta separar paginas
   limpas e inativas pelo motivo expresso de evitar descargas desnecessarias
   de paginas inativas (que consomem largura de banda de I/O), nem move
   paginas entre as varias filas de paginas gratuitamente quando o subsistema
   de memoria nao esta sendo enfatizado. E por isto que voce vera alguns
   sistemas com contagens de fila de cache muito baixas e contagens alta de
   fila ativa ao executar um comando systat -vm. A medida que o sistema de VM
   se torna mais estressado, ele faz um esforc,o maior para manter as varias
   filas de paginas nos niveis determinados para serem mais eficazes.

   Uma lenda urbana circulou durante anos que o Linux fez um trabalho melhor
   evitando trocas do que o FreeBSD, mas isso de fato nao e verdade. O que
   estava realmente ocorrendo era que o FreeBSD estava proativamente
   numerando paginas nao usadas a fim de abrir espac,o para mais cache de
   disco enquanto o Linux mantinha paginas nao utilizadas no nucleo e
   deixando menos memoria disponivel para paginas de cache e processo. Eu nao
   sei se isso ainda e verdade hoje.

5. Otimizac,oes de Pre-Falhas ou para Zerar

   Pegar uma falha de VM nao e caro se a pagina subjacente ja estiver no
   nucleo e puder simplesmente ser mapeada no processo, mas pode se tornar
   cara se voce pegar muitas delas regularmente. Um bom exemplo disso e
   executar um programa como ls(1) ou ps(1) varias vezes. Se o programa
   binario e mapeado na memoria, mas nao mapeado na tabela de paginas, entao
   todas as paginas que serao acessadas pelo programa irao estar com falha
   toda vez que o programa for executado. Isso e desnecessario quando as
   paginas em questao ja estao no cache de VM, entao o FreeBSD tentara
   preencher previamente as tabelas de paginas de um processo com as paginas
   que ja estao no cache de VM. Uma coisa que o FreeBSD ainda nao faz e
   pre-copiar-durante-escrita certas paginas no exec. Por exemplo, se voce
   executar o programa ls(1) ao executar o vmstat 1, notara que sempre pega
   um determinado numero de falhas de pagina, mesmo quando voce o executa
   varias vezes. Estas sao falhas de preenchimento com zero, nao falhas de
   codigo de programa (que ja foram pre-falhas). A pre-copia de paginas em
   exec ou fork e uma area que poderia se utilizar de mais estudos.

   Uma grande porcentagem de falhas de pagina que ocorrem sao falhas de
   preenchimento com zero. Geralmente, voce pode ver isso observando a saida
   de vmstat -s. Estas falhas ocorrem quando um processo acessa paginas em
   sua area BSS. Espera-se que a area BSS seja inicialmente zero, mas o
   sistema de VM nao se preocupa em alocar memoria alguma ate que o processo
   realmente a acesse. Quando ocorre uma falha, o sistema de VM deve alocar
   nao apenas uma nova pagina, mas deve zera-la tambem. Para otimizar a
   operac,ao de zeramento, o sistema de VM tem a capacidade de pre-zerar
   paginas e marca-las como tal, e solicitar paginas pre-zeradas quando
   ocorrem falhas de preenchimento com zero. O pre-zeramento ocorre sempre
   que a CPU esta inativa, mas o numero de paginas que o sistema pre-zeros e
   limitado, a fim de evitar que os caches de memoria sejam dissipados. Este
   e um excelente exemplo de adic,ao de complexidade ao sistema de VM para
   otimizar o caminho critico.

6. Otimizac,oes da Tabela de Paginas

   As otimizac,oes da tabela de paginas constituem a parte mais contenciosa
   do design de VM do FreeBSD e mostraram alguma tensao com o advento do uso
   serio de mmap(). Eu acho que isso e realmente uma caracteristica da
   maioria dos BSDs, embora eu nao tenha certeza de quando foi introduzido
   pela primeira vez. Existem duas otimizac,oes principais. A primeira e que
   as tabelas de paginas de hardware nao contem estado persistente, mas podem
   ser descartadas a qualquer momento com apenas uma pequena quantidade de
   sobrecarga de gerenciamento. A segunda e que cada entrada ativa da tabela
   de paginas no sistema tem uma estrutura governante pv_entry que e amarrada
   na estrutura vm_page. O FreeBSD pode simplesmente iterar atraves desses
   mapeamentos que sao conhecidos, enquanto o Linux deve verificar todas as
   tabelas de paginas que possam conter um mapeamento especifico para ver se
   ele o faz, o que pode alcanc,ar O(n^2) situac,oes. E por isso que o
   FreeBSD tende a fazer melhores escolhas em quais paginas reutilizar ou
   trocar quando a memoria e estressada, dando-lhe melhor desempenho em
   sobrecarga. No entanto, o FreeBSD requer o ajuste do kernel para acomodar
   situac,oes de grandes espac,os de enderec,os compartilhados, como aquelas
   que podem ocorrer em um sistema de noticias, porque ele pode rodar sem
   estruturas pv_entry.

   Tanto o Linux quanto o FreeBSD precisam funcionar nesta area. O FreeBSD
   esta tentando maximizar a vantagem de um modelo de mapeamento ativo
   potencialmente esparso (nem todos os processos precisam mapear todas as
   paginas de uma biblioteca compartilhada, por exemplo), enquanto o Linux
   esta tentando simplificar seus algoritmos. O FreeBSD geralmente tem a
   vantagem de desempenho aqui, ao custo de desperdic,ar um pouco de memoria
   extra, mas o FreeBSD quebra no caso em que um arquivo grande e
   massivamente compartilhado em centenas de processos. O Linux, por outro
   lado, se quebra no caso em que muitos processos mapeiam esparsamente a
   mesma biblioteca compartilhada e tambem sao executados de maneira nao
   ideal ao tentar determinar se uma pagina pode ser reutilizada ou nao.

7. Page Coloring

   Terminaremos com as otimizac,oes de page coloring. Page coloring e uma
   otimizac,ao de desempenho projetada para garantir que acessos a paginas
   contiguas na memoria virtual fac,am o melhor uso do cache do processador.
   Nos tempos antigos (isto e, ha mais de 10 anos), os caches de processador
   tendiam a mapear a memoria virtual em vez da memoria fisica. Isso levou a
   um grande numero de problemas, incluindo a necessidade de limpar o cache
   em cada troca de contexto em alguns casos e problemas com o alias de dados
   no cache. Caches de processador modernos mapeiam a memoria fisica com
   precisao para resolver esses problemas. Isto significa que duas paginas
   lado a lado em um espac,o de enderec,o de processos podem nao corresponder
   a duas paginas lado a lado no cache. Na verdade, se voce nao for
   cuidadoso, as paginas lado a lado na memoria virtual podem acabar usando a
   mesma pagina no cache do processador - conduzindo para que dados em cache
   sejam descartados prematuramente e reduzindo o desempenho da CPU. Isto e
   verdade mesmo com caches auto associativos de multiplas vias (embora o
   efeito seja um pouco mitigado).

   O codigo de alocac,ao de memoria do FreeBSD implementa otimizac,oes de
   page coloring, o que significa que o codigo de alocac,ao de memoria
   tentara localizar paginas livres contiguas do ponto de vista do cache. Por
   exemplo, se a pagina 16 da memoria fisica for atribuida `a pagina 0 da
   memoria virtual de um processo e o cache puder conter 4 paginas, o codigo
   de page coloring nao atribuira a pagina 20 da memoria fisica a pagina 1 da
   memoria virtual de um processo. Em vez disso, atribui a pagina 21 da
   memoria fisica. O codigo de page coloring tenta evitar assimilar a pagina
   20, porque ela e mapeada sobre a mesma memoria cache da pagina 16 e
   resultaria em um armazenamento nao otimizado. Este codigo adiciona uma
   quantidade significativa de complexidade ao subsistema de alocac,ao de
   memoria de VM, como voce pode imaginar, mas o resultado vale o esforc,o.
   Page coloring torna a memoria de VM tao determinante quanto a memoria
   fisica em relac,ao ao desempenho do cache.

8. Conclusao

   A memoria virtual em sistemas operacionais modernos deve abordar varios
   problemas diferentes de maneira eficiente e para muitos padroes de uso
   diferentes. A abordagem modular e algoritmica que o BSD historicamente
   teve nos permite estudar e entender a implementac,ao atual, bem como
   substituir de forma relativamente limpa grandes sec,oes do codigo. Houve
   uma serie de melhorias no sistema de VM do FreeBSD nos ultimos anos e o
   trabalho esta em andamento.

9. Sessao bonus de QA por Allen Briggs <briggs@ninthwonder.com>

   9.1. O que e "o algoritmo de intercalac,ao" ao qual voce se refere em sua
   listagem dos males dos arranjos de swap do FreeBSD 3.X?

   9.2. Como a separac,ao de paginas limpas e sujas (inativas) esta
   relacionada `a situac,ao em que voce ve baixas contagens de filas de cache
   e altas contagens de filas ativas no systat -vm? As estatisticas do systat
   rolam as paginas ativa e inativas juntas para a contagem de filas ativas?

   9.3. No exemplo ls(1) / vmstat 1, algumas falhas de pagina nao seriam
   falhas de pagina de dados (COW do arquivo executavel para a pagina
   privada)? Ou seja, eu esperaria que as falhas de pagina fossem um
   preenchimento com zero e alguns dados do programa. Ou voce esta sugerindo
   que o FreeBSD faz pre-COW para os dados do programa?

   9.4. Em sua sec,ao sobre otimizac,oes de tabela de paginas, voce pode dar
   um pouco mais de detalhes sobre pv_entry e vm_page (ou vm_page deveria ser
   vm_pmap- como em 4.4, cf. pp. 180-181 of McKusick, Bostic, Karel,
   Quarterman)? Especificamente, que tipo de operac,ao/reac,ao exigiria a
   varredura dos mapeamentos?

   9.5. Finalmente, na sec,ao de page coloring, pode ser util descrever um
   pouco mais o que voce quer dizer aqui. Eu nao segui bem isso.

   9.1. O que e "o algoritmo de intercalac,ao" ao qual voce se refere em sua  
        listagem dos males dos arranjos de swap do FreeBSD 3.X?               
        O FreeBSD usa um intercalador de swap fixo, cujo padrao e 4. Isso     
        significa que o FreeBSD reserva espac,o para quatro areas de swap,    
        mesmo se voce tiver apenas uma, duas ou tres. Como a swap e           
        intercalada, o espac,o de enderec,amento linear representando as      
        "quatro areas de troca" estara fragmentado se voce nao tiver quatro   
        areas de troca. Por exemplo, se voce tiver duas areas de swap, A e B, 
        a representac,ao do espac,o de enderec,amento do FreeBSD para esta    
        area de troca sera intercalada em blocos de 16 paginas:               
                                                                              
        A B C D A B C D A B C D A B C D                                       
                                                                              
        O FreeBSD 3.X usa uma abordagem de "lista sequencial de regioes       
        livres" para contabilizar as areas de swap livres. A ideia e que      
        grandes blocos de espac,o linear livre possam ser representados com   
        um unico no da lista (kern/subr_rlist.c). Mas devido a fragmentac,ao, 
        a lista sequencial acaba sendo insanamente fragmentada. No exemplo    
        acima, a swap completamente sem uso tera A e B mostrados como         
        "livres" e C e D mostrados como "todos alocados". Cada sequencia A-B  
        requer um no da lista para considerar porque C e D sao buracos,       
        portanto, o no de lista nao pode ser combinado com a proxima          
        sequencia A-B.                                                        
                                                                              
        Por que nos intercalamos nosso espac,o de swap em vez de apenas       
        colocar as areas de swap no final e fazer algo mais sofisticado?      
        Porque e muito mais facil alocar trechos lineares de um espac,o de    
        enderec,amento e ter o resultado automaticamente intercalado em       
        varios discos do que tentar colocar esta sofisticac,ao em outro       
        lugar.                                                                
                                                                              
        A fragmentac,ao causa outros problemas. Sendo uma lista linear sob    
        3.X, e tendo uma enorme quantidade de fragmentac,ao inerente,         
        alocando e liberando swap leva a ser um algoritmo O(N) ao inves de um 
        algoritmo O(1). Combinado com outros fatores (troca pesada) e voce    
        comec,a a entrar em niveis de sobrecarga O(N^2) e O(N^3), o que e     
        ruim. O sistema 3.X tambem pode precisar alocar o KVM durante uma     
        operac,ao de troca para criar um novo no da lista que pode levar a um 
        impasse se o sistema estiver tentando fazer uma liberac,ao de pagina  
        em uma situac,ao de pouca memoria.                                    
                                                                              
        No 4.X, nao usamos uma lista sequencial. Em vez disto, usamos uma     
        arvore raiz e bitmaps de blocos de swap em vez de lista de nos        
        variaveis. Aceitamos o sucesso de pre-alocar todos os bitmaps         
        necessarios para toda a area de swap na frente, mas acaba             
        desperdic,ando menos memoria devido ao uso de um bitmap (um bit por   
        bloco) em vez de uma lista encadeada de nos. O uso de uma arvore raiz 
        em vez de uma lista sequencial nos da quase o desempenho O(1), nao    
        importa o quao fragmentada a arvore se torne.                         
   9.2. Como a separac,ao de paginas limpas e sujas (inativas) esta           
        relacionada `a situac,ao em que voce ve baixas contagens de filas de  
        cache e altas contagens de filas ativas no systat -vm? As             
        estatisticas do systat rolam as paginas ativa e inativas juntas para  
        a contagem de filas ativas?                                           
                                                                              
        Eu nao entendo o seguinte:                                            
                                                                              
          E importante notar que o sistema de VM do FreeBSD tenta separar     
          paginas limpas e inativas pelo motivo expresso de evitar descargas  
          desnecessarias de paginas inativas (que consomem largura de banda   
          de I/O), nem mover paginas entre as varias filas de paginas         
          gratuitamente quando subsistema de memoria nao esta sendo           
          estressado. E por itso que voce vera alguns sistemas com contagens  
          de fila de cache muito baixas e contagens de fila ativa altas ao    
          executar um comando systat -vm.                                     
        Sim, isto e confuso. A relac,ao e "meta" versus "realidade". Nosso    
        objetivo e separar as paginas, mas a realidade e que, se nao estamos  
        em uma crise de memoria, nao precisamos realmente fazer isso.         
                                                                              
        O que isto significa e que o FreeBSD nao tentara muito separar        
        paginas sujas (fila inativa) de paginas limpas (fila de cache) quando 
        o sistema nao esta sendo estressado, nem vai tentar desativar paginas 
        (fila ativa -> fila inativa) quando o sistema nao esta sendo          
        estressado, mesmo que nao estejam sendo usados.                       
   9.3. No exemplo ls(1) / vmstat 1, algumas falhas de pagina nao seriam      
        falhas de pagina de dados (COW do arquivo executavel para a pagina    
        privada)? Ou seja, eu esperaria que as falhas de pagina fossem um     
        preenchimento com zero e alguns dados do programa. Ou voce esta       
        sugerindo que o FreeBSD faz pre-COW para os dados do programa?        
        Uma falha de COW pode ser preenchimento com zero ou dados de          
        programa. O mecanismo e o mesmo dos dois modos, porque os dados do    
        programa de apoio quase certamente ja estao no cache. Eu estou        
        realmente juntando os dois. O FreeBSD nao faz o pre-COW dos dados do  
        programa ou preenchimento com zero, mas faz pre-mapeamento de paginas 
        que existem em seu cache.                                             
   9.4. Em sua sec,ao sobre otimizac,oes de tabela de paginas, voce pode dar  
        um pouco mais de detalhes sobre pv_entry e vm_page (ou vm_page        
        deveria ser vm_pmap- como em 4.4, cf. pp. 180-181 of McKusick,        
        Bostic, Karel, Quarterman)? Especificamente, que tipo de              
        operac,ao/reac,ao exigiria a varredura dos mapeamentos?               
                                                                              
        Como o Linux faz no caso onde o FreeBSD quebra (compartilhando um     
        grande mapeamento de arquivos em muitos processos)?                   
        Uma vm_page representa uma tupla (objeto,indice#). Um pv_entry        
        representa uma entrada de tabela de pagina de hardware (pte). Se voce 
        tem cinco processos compartilhando a mesma pagina fisica, e tres      
        dessas tabelas de paginas atualmente mapeiam a pagina, esta pagina    
        sera representada por uma unica estrutura vm_page e tres estruturas   
        pv_entry.                                                             
                                                                              
        As estruturas pv_entry representam apenas as paginas mapeadas pela    
        MMU (uma pv_entry representa uma pte). Isso significa que quando      
        precisamos remover todas as referencias de hardware para uma vm_page  
        (para reutilizar a pagina para outra coisa, paginar, limpar, inativar 
        e assim por diante), podemos simplesmente escanear a lista encadeada  
        de pv_entry associada a essa vm_page para remover ou modificar os     
        pte's de suas tabelas de paginas.                                     
                                                                              
        No Linux, nao existe essa lista vinculada. Para remover todos os      
        mapeamentos de tabelas de paginas de hardware para um vm_page, o      
        linux deve indexar em todos os objetos de VM que possam ter mapeado a 
        pagina. Por exemplo, se voce tiver 50 processos, todos mapeando a     
        mesma biblioteca compartilhada e quiser se livrar da pagina X nessa   
        biblioteca, sera necessario indexar na tabela de paginas para cada um 
        desses 50 processos, mesmo se apenas 10 deles realmente tiverem       
        mapeado a pagina. Entao, o Linux esta trocando a simplicidade de seu  
        design com o desempenho. Muitos algoritmos de VM que sao O(1) ou      
        (pequeno N) no FreeBSD acabam sendo O(N), O(N^2), ou pior no Linux.   
        Como os pte's que representam uma determinada pagina em um objeto     
        tendem a estar no mesmo offset em todas as tabelas de paginas em que  
        estao mapeados, reduzir o numero de acessos nas tabelas de paginas no 
        mesmo pte offset evitara a linha de cache L1 para esse deslocamento,  
        o que pode levar a um melhor desempenho.                              
                                                                              
        O FreeBSD adicionou complexidade (o esquema pv_entry) para aumentar o 
        desempenho (para limitar os acessos da tabela de paginas a somente    
        aqueles pte's que precisam ser modificados).                          
                                                                              
        Mas o FreeBSD tem um problema de escalonamento que o Linux nao        
        possui, pois ha um numero limitado de estruturas pv_entry e isso      
        causa problemas quando voce tem um compartilhamento massivo de dados. 
        Nesse caso, voce pode ficar sem estruturas pv_entry, mesmo que haja   
        bastante memoria livre disponivel. Isto pode ser corrigido com        
        bastante facilidade aumentando o numero de estruturas pv_entry na     
        configurac,ao do kernel, mas realmente precisamos encontrar uma       
        maneira melhor de faze-lo.                                            
                                                                              
        Em relac,ao `a sobrecarga de memoria de uma tabela de paginas verso   
        do esquema pv_entry: o Linux usa tabelas "permanentes" que nao sao    
        descartadas, mas nao precisa de um pv_entry para cada pte             
        potencialmente mapeado. O FreeBSD usa tabelas de paginas "throw       
        away", mas adiciona em uma estrutura pv_entry para cada pte realmente 
        mapeado. Eu acho que a utilizac,ao da memoria acaba sendo a mesma,    
        dando ao FreeBSD uma vantagem algoritmica com sua capacidade de jogar 
        fora tabelas de paginas a vontade com uma sobrecarga muito baixa.     
   9.5. Finalmente, na sec,ao de page coloring, pode ser util descrever um    
        pouco mais o que voce quer dizer aqui. Eu nao segui bem isso.         
        Voce sabe como funciona um cache de memoria de hardware L1? Vou       
        explicar: Considere uma maquina com 16MB de memoria principal, mas    
        apenas 128K de cache L1. Geralmente, a maneira como este cache        
        funciona e que cada bloco de 128K de memoria principal usa o mesmo    
        128K de cache. Se voce acessar o offset 0 na memoria principal e      
        depois deslocar 128K na memoria principal, voce pode acabar jogando   
        fora os dados em cache que voce leu do offset 0!                      
                                                                              
        Agora estou simplificando muito as coisas. O que acabei de descrever  
        e o que e chamado de cache de memoria de hardware "diretamente        
        mapeado". A maioria dos caches modernos sao chamados de definic,ao de 
        associac,oes de 2 vias ou definic,ao de associac,oes de 4 vias. A     
        definic,ao de associacoes permite acessar ate N regioes de memoria    
        diferentes que se sobrepoem `a mesma memoria de cache sem destruir os 
        dados armazenados em cache anteriormente. Mas apenas N.               
                                                                              
        Entao, se eu tenho um cache associativo de 4-way, eu posso acessar o  
        offset 0, offset 128K, 256K e offset 384K e ainda ser capaz de        
        acessar o offset 0 novamente e te-lo vindo do cache L1. Se eu, entao, 
        acessar o deslocamento 512K, no entanto, um dos quatro objetos de     
        dados armazenados anteriormente em cache sera descartado pelo cache.  
                                                                              
        E extremamente importante... extremamente importante para que a       
        maioria dos acessos de memoria de um processador possam vir do cache  
        L1, porque o cache L1 opera na frequencia do processador. No momento  
        em que voce tem uma falha de cache L1 e precisa ir para o cache L2 ou 
        para a memoria principal, o processador ira parar e potencialmente    
        sentar-se por centenas de instruc,oes aguardando uma leitura de       
        memoria principal para completar. A memoria principal (o ram dinamico 
        que voce coloca em um computador) e lenta, quando comparada `a        
        velocidade de um nucleo de processador moderno.                       
                                                                              
        Ok, agora em page coloring: Todos os caches de memoria modernos sao   
        conhecidos como caches fisicos. Eles armazenam em cache enderec,os de 
        memoria fisica, nao enderec,os de memoria virtual. Isto permite que o 
        cache seja deixado sozinho em uma opc,ao de contexto de processo, o   
        que e muito importante.                                               
                                                                              
        Mas no mundo UNIX(R) voce esta lidando com espac,os de enderec,o      
        virtual, nao com espac,os de enderec,o fisico. Qualquer programa que  
        voce escreva vera o espac,o de enderec,o virtual dado a ele. As       
        paginas reais fisicas subjacentes a este espac,o de enderec,o virtual 
        nao sao necessariamente contiguas fisicamente! De fato, voce pode ter 
        duas paginas que estao lado a lado em um espac,o de enderec,o de      
        processos que termina no offset 0 e desloca 128K na memoria fisica.   
                                                                              
        Um programa normalmente pressupoe que duas paginas lado a lado serao  
        armazenadas em cache de maneira ideal. Ou seja, voce pode acessar     
        objetos de dados em ambas as paginas sem que elas descartem a entrada 
        de cache uma da outra. Mas isso so e verdadeiro se as paginas fisicas 
        subjacentes ao espac,o de enderec,o virtual forem contiguas (no que   
        se refere ao cache).                                                  
                                                                              
        E isso que o disfarce de pagina faz. Em vez de atribuir paginas       
        fisicas aleatorias a enderec,os virtuais, o que pode resultar em      
        desempenho de cache nao ideal, o disfarce de pagina atribui paginas   
        fisicas razoavelmente contiguas a enderec,os virtuais. Assim, os      
        programas podem ser escritos sob a suposic,ao de que as               
        caracteristicas do cache de hardware subjacente sao as mesmas para    
        seu espac,o de enderec,o virtual, como seriam se o programa tivesse   
        sido executado diretamente em um espac,o de enderec,o fisico.         
                                                                              
        Note que eu digo "razoavelmente" contiguo ao inves de simplesmente    
        "contiguo". Do ponto de vista de um cache mapeado direto de 128K, o   
        enderec,o fisico 0 e o mesmo que o enderec,o fisico 128K. Assim, duas 
        paginas lado a lado em seu espac,o de enderec,o virtual podem acabar  
        sendo compensadas em 128K e compensadas em 132K na memoria fisica,    
        mas tambem podem ser facilmente compensadas em 128K e compensadas em  
        4K na memoria fisica e ainda manter as mesmas caracteristicas de      
        desempenho de cache. Portanto, disfarce de pagina nao tem que         
        atribuir paginas verdadeiramente contiguas de memoria fisica a        
        paginas contiguas de memoria virtual, basta certificar-se de atribuir 
        paginas contiguas do ponto de vista do desempenho e da operac,ao do   
        cache.                                                                
