FXSAVE/XSAVE e Estado de FPU/SIMD
Context Switching já introduz a troca preguiçosa de FPU, adiando o custo de salvar e restaurar estado de registrador de ponto flutuante e SIMD até uma tarefa de fato tocá-lo, sem nomear as instruções reais envolvidas nem a diferença entre o formato de salvamento antigo de tamanho fixo e o novo formato extensível. Este artigo permanece nesse mesmo mecanismo e vai mais fundo em FXSAVE/FXRSTOR, XSAVE/XRSTOR, e na exceção #NM da qual a troca preguiçosa depende para detectar o primeiro uso.
FXSAVE e a área legada de 512 bytes
Seção intitulada “FXSAVE e a área legada de 512 bytes”FXSAVE escreve um bloco fixo de 512 bytes cobrindo os oito registradores de pilha da FPU x87, suas palavras de controle e status, e os registradores XMM0–XMM15 que SSE adicionou; FXRSTOR lê esse mesmo layout de volta. Como o tamanho e o layout interno do bloco são ambos fixados pela própria instrução, nenhum metadado dentro do bloco precisa descrever o que ele contém: um kernel implementando troca preguiçosa só precisa de um buffer de 512 bytes por tarefa e pode salvá-lo ou restaurá-lo incondicionalmente, sem checar quais registradores específicos uma dada tarefa de fato usou.
struct fpu_state { uint8_t data[512];} __attribute__((aligned(16)));
void save_fpu(struct fpu_state *s) { __asm__ volatile("fxsave %0" : "=m"(*s)); }void restore_fpu(struct fpu_state *s) { __asm__ volatile("fxrstor %0" : : "m"(*s)); }XSAVE e estado de tamanho variável
Seção intitulada “XSAVE e estado de tamanho variável”AVX, e extensões posteriores como AVX-512, adicionaram estado de registrador (as metades superiores de YMM, depois ZMM, depois os registradores de máscara do AVX-512) para o qual o layout fixo de 512 bytes do FXSAVE não tem espaço algum e não pode ser estendido para cobrir sem quebrar todo consumidor existente daquele formato fixo. XSAVE o substitui por um layout autodescritivo de tamanho variável: o registrador XCR0 (lido e escrito com XGETBV/XSETBV, não uma MSR comum já que é específico de estado estendido em vez de configuração geral de CPU) tem um bit por componente de estado, x87, SSE, os bits superiores de AVX, e assim por diante, e apenas os componentes cujo bit em XCR0 está definido são de fato salvos ou restaurados numa dada chamada de XSAVE/XRSTOR. A leaf 0x0D do CPUID reporta o tamanho real de área de salvamento que uma combinação particular de componentes habilitados exige, que um kernel consulta uma vez no boot para dimensionar corretamente seus buffers de salvamento por tarefa em vez de presumir um tamanho fixo da forma que FXSAVE permitia.
xor ecx, ecxxgetbv ; EDX:EAX = XCR0 atualor eax, (1 << 2) ; habilita o componente de estado AVX (bit 2)xsetbvXSAVE é em si uma família de instruções relacionadas em vez de um único comportamento fixo: XSAVEOPT adicionalmente pula escrever qualquer componente que não tenha sido modificado desde a última restauração, e XSAVEC/XSAVES adicionam compactação, omitindo componentes desabilitados ou não usados do layout salvo por completo em vez de reservar espaço para eles, ambos refinamentos voltados ao mesmo objetivo que o formato fixo do FXSAVE não conseguia alcançar: não pagar para salvar estado que uma tarefa nunca tocou.
A exceção #NM e a troca preguiçosa
Seção intitulada “A exceção #NM e a troca preguiçosa”A troca preguiçosa de FPU, como Context Switching cobre do lado do escalonamento, funciona limpando CR0.TS para qualquer tarefa que atualmente possua o estado ativo de registrador de FPU/SIMD, e definindo-o para toda outra tarefa: uma tentativa de uma tarefa sem posse de executar qualquer instrução de ponto flutuante, MMX, ou SSE/AVX com TS definido levanta #NM (Device Not Available, vetor 7 na faixa reservada da IDT), em vez de executar a instrução contra qualquer conteúdo de registrador desatualizado que estivesse ali. O handler de #NM é onde o salvamento/restauração real que este artigo descreve acontece: salva o estado do dono anterior com FXSAVE ou XSAVE, restaura o próprio estado previamente salvo da tarefa que causou a falha com FXRSTOR/XRSTOR, limpa TS, e retorna, deixando a instrução que falhou reexecutar com sucesso contra conteúdo de registrador agora correto.
void nm_handler(void) { clts(); // limpa CR0.TS if (fpu_owner) save_fpu(&fpu_owner->fpu_state); restore_fpu(¤t_task->fpu_state); fpu_owner = current_task;}Notas de implementação
Seção intitulada “Notas de implementação”O estado de FPU/SIMD salvo de uma tarefa precisa ser inicializado para um valor definido na primeira vez que é restaurado, não deixado como qualquer lixo que estivesse ocupando o buffer alocado: FXRSTOR/XRSTOR reproduzem fielmente qualquer padrão de bits que recebam, incluindo um inválido, e o buffer de uma tarefa recém-criada precisa conter um estado equivalente a reset válido (comumente produzido uma vez com FXSAVE/XSAVE logo depois que a FPU é resetada pela primeira vez, depois copiado como modelo) em vez de memória não inicializada. A área de salvamento do XSAVE adicionalmente exige alinhamento de 64 bytes, mais rígido que o requisito de 16 bytes do FXSAVE, o que exige atenção deliberada quando um kernel simplesmente troca de uma família de instruções para a outra sem checar novamente o alinhamento de buffers já alocados para o layout antigo, menos rígido.
Referências
Seção intitulada “Referências”- ^ Intel, Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 1, Capítulo 13: o conjunto completo de recursos
XSAVE,XCR0, e o layout da área legadaFXSAVE.
Ver também
Seção intitulada “Ver também”- Context Switching: o mecanismo de troca preguiçosa e o
CR0.TSque as instruções deste artigo de fato implementam. - A IDT: a tabela de vetores de exceção reservados à qual
#NMpertence.