Chamadas de Sistema
Uma chamada de sistema é o mecanismo controlado pelo qual código rodando no ring 3 requisita um serviço (ler um arquivo, alocar memória, enviar dados pela rede) que só o ring 0 tem permissão de realizar diretamente. Diferente de uma chamada de função comum, uma chamada de sistema precisa cruzar uma fronteira de privilégio que a CPU aplica ativamente, o que significa que não pode ser uma instrução call simples; exige uma de um pequeno número de instruções especificamente projetadas para elevar privilégio de forma controlada e definida pelo kernel.
A abordagem de interrupção de software
Seção intitulada “A abordagem de interrupção de software”O mecanismo original, ainda suportado por compatibilidade retroativa, usa uma interrupção de software comum: código em user mode executa int 0x80 (o vetor específico é uma convenção, não um requisito arquitetural; o Linux se estabeleceu em 0x80, outros sistemas usaram outros), que a CPU trata de forma idêntica a uma interrupção de hardware chegando naquele vetor, consultando a IDT e transicionando para qualquer handler registrado ali, no ring 0, exatamente como faria para qualquer outra interrupção. O número da chamada de sistema e seus argumentos são convencionalmente passados em registradores de propósito geral antes de executar int 0x80, já que não existe mecanismo dedicado de passagem de argumentos embutido na própria interrupção.
Essa abordagem funciona corretamente mas é comparativamente lenta: uma interrupção de software passa por toda a maquinaria de despacho de interrupção (consultando a IDT, verificando os requisitos de privilégio do gate, salvando um frame completo de interrupção), maquinaria projetada para lidar com eventos de hardware arbitrários e imprevisíveis, em vez de otimizada para o caso específico e bem conhecido de “código de usuário requisitando voluntariamente entrada no kernel”.
O caminho rápido
Seção intitulada “O caminho rápido”x86-64 define um par dedicado de instruções, syscall e sysret, especificamente para esse caso comum, contornando a IDT por completo. Em vez de consultar um handler através de uma tabela, syscall salta diretamente para um endereço que o kernel configurou de antemão em uma model-specific register (LSTAR), e simultaneamente carrega um novo seletor de segmento de código e stack de outra MSR (STAR) e mascara um conjunto configurável de flags (via FMASK), realizando em uma instrução o que o caminho de interrupção espalha por vários estágios de consultas de tabela e verificações. sysret reverte a transição, retornando ao ring 3 no endereço deixado em um registrador por convenção.
// Configurando o caminho rápido de syscall (executado uma vez, durante a init do kernel)void init_syscalls(void) { wrmsr(MSR_STAR, ((uint64_t)USER_CS_BASE << 48) | ((uint64_t)KERNEL_CS << 32)); wrmsr(MSR_LSTAR, (uint64_t)syscall_entry); // endereço do handler wrmsr(MSR_FMASK, 0x200); // limpa IF na entrada, entre outras flags}O par predecessor de 32 bits, sysenter e sysexit, segue a mesma filosofia idêntica (um ponto de entrada fixo, configurado via MSR, em vez de uma consulta à IDT) para código de 32 bits, e o syscall/sysret do long mode é melhor entendido como a mesma ideia levada adiante e adaptada para operação de 64 bits, não um mecanismo não relacionado.
Convenção de chamada
Seção intitulada “Convenção de chamada”Como nem syscall nem int 0x80 se comportam como uma chamada de função comum, a convenção de passagem de argumentos para uma chamada de sistema é definida pelo kernel, não pela CPU ou pela convenção de chamada C regular da plataforma, embora kernels comumente escolham algo próximo da convenção de chamada System V por familiaridade, tipicamente passando o número da chamada de sistema em um registrador fixo e argumentos em um pequeno conjunto de outros, com o valor de retorno vindo de volta em qualquer registrador que a convenção de chamada regular já use para esse propósito. Essa convenção precisa ser acordada de forma idêntica tanto pelo handler do kernel quanto por qualquer código (mais comumente uma biblioteca C de user space) que de fato emite a instrução syscall, já que nenhum dos lados consegue inferi-la a partir da própria instrução.
Notas de implementação
Seção intitulada “Notas de implementação”Um handler de syscall roda com interrupções tipicamente ainda mascaradas pela configuração FMASK até que as reabilite explicitamente, e, de forma crítica, roda em qualquer stack pointer que estivesse ativo em user mode no momento da chamada: syscall não troca automaticamente para uma stack de kernel da forma que um interrupt gate pode. Um kernel usando o caminho rápido, portanto, precisa trocar para uma stack de kernel conhecidamente boa como a primeiríssima coisa que seu handler faz, antes que seja seguro fazer praticamente qualquer coisa que possa por si só falhar ou precisar de uma stack confiável, comumente salvando o stack pointer de usuário em uma localização por CPU e carregando um stack pointer de kernel pré-estabelecido em seu lugar. Misturar os dois mecanismos descuidadamente também é uma fonte comum de confusão durante o desenvolvimento inicial: uma biblioteca de user space construída esperando int 0x80 não vai funcionar contra um kernel que só implementa o caminho syscall, e vice-versa, já que, da perspectiva do código chamador, essas são simplesmente duas formas diferentes e mutuamente incompatíveis de pedir entrada no kernel.
Referências
Seção intitulada “Referências”- ^ Intel, Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 2B: a referência para o par de instruções
SYSCALL/SYSRETe sua configuração via MSR. - ^ AMD, AMD64 Architecture Programmer’s Manual, Volume 2, Capítulo 6: descreve as model-specific registers
STAR/LSTAR/FMASKreferenciadas acima.
Ver também
Seção intitulada “Ver também”- The IDT: a tabela de despacho pela qual o caminho legado
int 0x80passa. - Multitasking: como uma stack de kernel por processo, referenciada acima, se encaixa no estado de processo de forma mais ampla.
- Model-Specific Registers: o mecanismo geral RDMSR/WRMSR por trás de
STAR/LSTAR/FMASK. - A System V ABI: a convenção de chamada comum que os registradores de argumento de syscall de um kernel comumente ecoam.