Mecanismos de Mapeamento de Memória Virtual no Kernel do HarmonyOS

A Função da MMU na Abstração de Andereços

No desenvolvimento de sistemas operacionais como o HarmonyOS, a Unidade de Gerenciamento de Memória (MMU) atua como uma ponte essencial entre o software e o hardware. O sistema utiliza dois conceitos fundamentais de endereçamento:

  • Endereço Virtual (VA): São endereços lineares atribuídos pelo compilador e pelo vinculador (linker). Cada processo enxerga um espaço de memória idêntico, permitindo o isolamento. A CPU opera sobre esses endereços, mas as requisições são interceptadas pela MMU.
  • Endereço Físico (PA): Representa a localização real nos chips de RAM onde as instruções, constantes, variáveis globais e dados dinâmicos são armazenados.

A MMU utiliza tabelas de páginas (Page Tables) para converter VA em PA. Além da tradução, essas tabelas definem permissões de acesso (leitura, escrita, execução) e atributos de cache. O processo foca na manipulação dos 20 bits superiores do endereço virtual, enquanto os 12 bits enferiores (offset) permanecem inalterados, garantindo o alinhamento das páginas de 4KB.

Estrutura de Tabelas em Dois Níveis (L1 e L2)

O HarmonyOS implementa uma estrutura de tradução hierárquica para gerenciar o espaço de endereçamento de 32 bits (4GB). Essa estrutura é dividida em:

  1. Nível 1 (L1): Divide o espaço total em 4096 "seções" de 1MB cada. A base desta tabela é armazenada no registrador TTBR (Translation Table Base Register) do processador ARM.
  2. Nível 2 (L2): Subdivide uma seção de 1MB em 256 "páginas pequenas" de 4KB cada.
/* Tipos de descritores de primeiro nível no kernel */
#define TIPO_DESC_L1_INVALIDO       (0x0 << 0)
#define TIPO_DESC_L1_TABELA_PAG     (0x1 << 0)
#define TIPO_DESC_L1_SECAO          (0x2 << 0)
#define MASCARA_DESC_L1             (0x3 << 0)

Consulta de Endereços Físicos

A função abaixo demonstra como o kernel traduz um endereço virtual em físico, verificando os níveis da tabela de páginas.

STATUS_T ConsultarMapeamentoMmu(const LosArchMmu *mmuContext, VADDR_T vAddr, PADDR_T *pAddr, UINT32 *flags) {
   // Recupera a entrada de nível 1 usando os bits superiores do VA
   PTE_T entradaL1 = BuscarEntradaL1(mmuContext->virtTtb, vAddr);
   PTE_T entradaL2;
   PTE_T *baseL2 = NULL;

   if (ValidarEntradaInvalidaL1(entradaL1)) {
       return LOS_ERRNO_VM_NOT_FOUND;
   } 
   
   // Caso seja uma seção direta de 1MB
   if (VerificarSeSecaoL1(entradaL1)) {
       if (pAddr != NULL) {
           *pAddr = EXTRAIR_ADDR_SECAO_L1(entradaL1) + (vAddr & (TAMANHO_SECAO_1MB - 1));
       }
       ExtrairFlagsMmu(entradaL1, flags);
   } 
   // Caso a entrada aponte para uma tabela de segundo nível
   else if (VerificarSeTabelaPaginaL1(entradaL1)) {
       baseL2 = ObterBaseL2(entradaL1);
       if (baseL2 == NULL) return LOS_ERRNO_VM_NOT_FOUND;

       entradaL2 = BuscarEntradaL2(baseL2, vAddr);
       if (VerificarPaginaPequenaL2(entradaL2)) {
           if (pAddr != NULL) {
               *pAddr = EXTRAIR_ADDR_PAGINA_L2(entradaL2) + (vAddr & (TAMANHO_PAGINA_4KB - 1));
           }
           ConverterFlagsL2(entradaL1, entradaL2, flags);
       } else {
           // Páginas grandes (64KB) não implementadas nesta versão
           return LOS_ERRNO_VM_NOT_FOUND;
       }
   }
   return LOS_OK;
}

Inicialização e Configuração de Segmentos do Kernel

Durante o boot, o kernel precisa configurar as permissões de suas próprias seções de memória (código, dados e BSS). Isso garante que a área de código seja apenas leitura e execução, enquanto a área de dados permita escrita.

STATIC VOID ConfigurarSecoesKernel(VOID) {
   UINTPTR inicioTexto = (UINTPTR)&__text_start;
   UINTPTR fimTexto = (UINTPTR)&__text_end;
   UINTPTR inicioDados = (UINTPTR)&__ram_data_start;
   UINTPTR fimBss = (UINTPTR)&__bss_end;

   // Definição do mapeamento para o segmento de código (Text)
   LosArchMmuInitMapping mapaTexto = {
       .phys = SYS_MEM_BASE + inicioTexto - KERNEL_VMM_BASE,
       .virt = inicioTexto,
       .size = ALINHAR_4K(fimTexto - inicioTexto),
       .flags = VM_MAP_PERM_READ | VM_MAP_PERM_EXECUTE,
       .name = "k_text"
   };

   // Aplicação das configurações na MMU
   LosVmSpace *espacoK = LOS_GetKVmSpace();
   espacoK->archMmu.virtTtb = (PTE_T *)g_firstPageTable;
   espacoK->archMmu.physTtb = TraduzirParaFisico(espacoK->archMmu.virtTtb);

   // Mapeamento efetivo no hardware
   LOS_ArchMmuMap(&espacoK->archMmu, mapaTexto.virt, mapaTexto.phys, 
                  mapaTexto.size >> 12, mapaTexto.flags);

   // Atualização do registrador de controle e limpeza de TLB
   AtualizarTtbr0(espacoK->archMmu.physTtb);
   LimparCacheTLB();
}

O Processo de Mapeamento (Map)

A operação de mapeamento consiste em preenhcer as tabelas de páginas com a relação correta entre endereços virtuais e físicos. Se o endereço estiver alinhado em 1MB, o kernel opta por mapeamentos de seção (L1) para maior eficiência. Caso contrário, utiliza o mapeamento granular de 4KB (L2).

status_t ExecutarMapeamentoMmu(LosArchMmu *mmu, VADDR_T vaddr, PADDR_T paddr, size_t paginas, UINT32 flags) {
   INT32 mapeados = 0;

   while (paginas > 0) {
       // Tenta mapear como seção de 1MB se houver alinhamento e quantidade suficiente
       if (ALINHADO_1MB(vaddr) && ALINHADO_1MB(paddr) && paginas >= 256) {
           mapeados += CriarEntradaSecaoL1(mmu, &vaddr, &paddr, &paginas, flags);
       } else {
           // Mapeamento via tabela L2 (páginas de 4KB)
           PTE_T entryL1 = ObterEntradaL1(mmu->virtTtb, vaddr);
           if (VerificarInvalido(entryL1)) {
               ProvisionarTabelaL2(mmu, &entryL1, vaddr);
           }
           mapeados += PreencherPaginasL2(entryL1, &vaddr, &paddr, &paginas, flags);
       }
   }
   return mapeados;
}

Tags: HarmonyOS kernel MMU VirtualMemory ARM

Publicado em 8-1 17:58