Pular para o conteúdo

Protected Mode

Protected mode é o modo de CPU que introduz a maquinaria da qual praticamente todo sistema operacional moderno depende: proteção de memória aplicada por hardware em vez de convenção, e múltiplos níveis de privilégio que a CPU verifica ativamente em vez de apenas documentar. Onde o real mode confia igualmente em todo código em execução, o protected mode dá à CPU um lugar (tabelas de descritores) para registrar para que cada região de memória serve e quem tem permissão de tocá-la, e aplica esses registros em todo acesso.

O real mode calcula um endereço como segmento × 16 + offset, com o valor de segmento usado diretamente, sem consulta alguma envolvida. O protected mode reinterpreta o conteúdo do registrador de segmento por completo: em vez de um endereço-base deslocável, ele se torna um seletor (um índice em uma tabela de descritores, mais alguns bits baixos registrando o nível de privilégio requisitado), e a CPU consulta a base, o limite e os direitos de acesso reais do segmento na tabela para a qual o seletor aponta. Essa indireção é o que torna a proteção possível: o software pode carregar livremente qualquer valor de seletor, mas a CPU só permite um acesso se os direitos do descritor correspondente permitirem, e apenas dentro da faixa que o limite do descritor especifica.

O campo de índice de um seletor de segmento selecionando uma entrada na GDT, cujo descritor fornece a base, o limite, e os direitos de acesso do segmentoSeletor de segmentoGDTDescritor (8 bytes)Índice15:3TI2RPL1:001234567Base [31:0]Limite [19:0]Access (P, DPL, tipo)Flags, Limite [19:16]

A GDT contém os descritores para os quais seletores apontam, carregada com a instrução LGDT a partir de uma pequena estrutura fornecendo o tamanho e o endereço da tabela, o mesmo formato limite/base que LIDT usa para a tabela de interrupções. Cada descritor de 8 bytes empacota um endereço-base de 32 bits, um limite de 20 bits (interpretado como bytes ou páginas de 4 KB dependendo de um bit de granularidade), e direitos de acesso incluindo o próprio nível de privilégio do descritor e se ele descreve código ou dados.

struct gdt_entry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_mid;
uint8_t access;
uint8_t granularity; // nibble alto: flags; nibble baixo: bits 19-16 do limite
uint8_t base_high;
} __attribute__((packed));
Os seis campos do struct gdt_entry dispostos em seus 8 bytes, com os quatro subcampos do byte access (P, DPL, S, Type) detalhados abaixolimit_lowbytes 0–1base_lowbytes 2–3base_midbyte 4accessbyte 5granularitybyte 6base_highbyte 7byte access, detalhadoP7DPL6:5S4Type3:0

Uma GDT convencionalmente começa com um descritor nulo no índice 0: carregar um seletor nulo é uma forma legítima de marcar um registrador de segmento como não usado, e a CPU gera uma falha se código de fato tentar usá-lo para um acesso, exatamente o comportamento que um placeholder deliberadamente inválido deveria ter. Além da entrada nula, uma GDT mínima de kernel precisa de pelo menos um descritor de código e um de dados para o ring 0, e tipicamente um par correspondente para o ring 3, se execução em user mode for suportada.

Todo seletor de segmento carrega um Requested Privilege Level de dois bits em seus bits baixos, e todo descritor carrega seu próprio Descriptor Privilege Level. A CPU compara esses (junto com o Current Privilege Level, rastreado como parte do segmento de código atualmente carregado) em operações relevantes, recusando um acesso onde o privilégio requisitado é numericamente maior (menos privilegiado) do que o descritor alvo permite. Essa verificação tripla (CPL, DPL, RPL) é o que torna inútil para código do ring 3 simplesmente carregar um seletor de dados do ring 0 e esperar acesso de ring 0: os próprios bits de privilégio do seletor, combinados com os do descritor, ainda bloqueiam isso.

CPL e RPL se combinando num nível de privilégio requisitado, comparado contra o DPL do descritor alvo para decidir se o acesso é permitido ou falhaCPLsegmento de código atualRPLbits 1:0 do seletormax(CPL, RPL)DPLdescritor alvopriv. requisitado ≤ DPL?acesso permitidofalha #GPsimnão

Uma estrutura adicional, o Task State Segment (TSS), contém o stack pointer para o qual a CPU troca automaticamente quando uma interrupção ou exceção elevando privilégio ocorre. Sem ela, uma transição ring 3 → ring 0 não tem stack de kernel definida para onde ir, o que torna um TSS válido um requisito prático para qualquer kernel que suporte código em user mode, mesmo que o propósito original do TSS, troca de tarefas por hardware, tenha pouco uso hoje.

A transição em si é uma sequência curta e rígida: carregar uma GDT válida com LGDT, definir o bit 0 (PE) de CR0, e realizar um far jump para o seletor de um descritor de código. Esse far jump faz trabalho real: esvazia o pipeline da CPU de quaisquer instruções decodificadas em real mode que ainda estivessem na fila e força uma nova busca sob as regras de decodificação do novo modo.

lgdt [gdt_descriptor]
mov eax, cr0
or eax, 1
mov cr0, eax
jmp CODE_SEG:protected_mode_entry

Registradores de segmento além do de código (DS, ES, SS, e assim por diante) também precisam ser recarregados com seletores válidos de protected mode imediatamente depois do jump: seus valores de real mode não são automaticamente reinterpretados, e usá-los sem recarregar lê qualquer valor de seletor desatualizado que estivesse lá, geralmente falhando na primeira vez que é usado para um acesso.

O protected mode não remove, por si só, o teto de endereçamento de 1 MB associado ao cálculo de endereço de 20 bits do real mode: esse limite é um efeito colateral da linha A20, um recurso de compatibilidade legado que mascara o 21º bit de endereço a menos que explicitamente habilitado, e precisa ser habilitado separadamente (através de um de vários mecanismos históricos, mais comumente o controlador de teclado ou uma porta fast-A20 dedicada) antes que o código consiga endereçar memória acima de 1 MB de forma confiável, independentemente de qual modo de CPU esteja ativo. Esquecer esse passo produz uma classe de bug particularmente confusa: acessos de memória parecem funcionar, mas silenciosamente fazem alias para um endereço 1 MB abaixo do pretendido, já que o bit superior é simplesmente descartado em vez de causar qualquer falha.

  1. ^ Intel, Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A, Capítulos 3 e 5: define descritores de segmento, a GDT, e aplicação de nível de privilégio.
  • x86 Architecture: a visão geral de modos mais ampla que este artigo expande.
  • Long Mode: a transição adicional do protected mode para execução de 64 bits.
  • A Task State Segment: a estrutura completa para a qual a breve menção à TSS deste artigo se aprofunda.
  • A Linha A20: a peculiaridade de endereçamento por trás do teto de 1 MB observado acima.
  • Unreal Mode: um truque para alcançar memória acima desse teto sem realizar a transição completa deste artigo.