4. Parte da camada de emulação -MD do Linux®

Esta seção trata da implementação da camada de emulação do Linux® no sistema operacional FreeBSD. Ele primeiro descreve a parte dependente da máquina falando sobre como e onde a interação entre o usuário e o kernel é implementada. Ele fala sobre syscalls, signals, ptrace, traps, correção de pilha. Esta parte discute o i386, mas ele é escrito geralmente para que outras arquiteturas não sejam muito diferentes. A próxima parte é a parte independente da máquina do Linuxulator. Esta seção abrange apenas o tratamento de i386 e ELF. A.OUT está obsoleto e não foi testado.

4.1. Manipulação de Syscall

A manipulação de Syscall é principalmente escrita em linux_sysvec.c, que cobre a maioria das rotinas apontadas na estrutura sysentvec. Quando um processo Linux® executado no FreeBSD emite um syscall, a rotina syscall geral chama a rotina prepsyscall do linux para a ABI do Linux®.

4.1.1. Linux® prepsyscall

Linux® passa argumentos via registradores de syscalls (isso porque ele é limitado a 6 parametros no i386) enquanto o FreeBSD usa uma pilha. A rotina prepsyscall do Linux® deve copiar parametros dos registradores para a pilha. A ordem dos registradores é: %ebx, %ecx, %edx, %esi, %edi, %ebp. O fato é que isso é verdadeiro apenas para a maioria das syscalls. Algumas (mais provavelmente clone) usam uma ordem diferente, mas é demasiadamente facil de arrumar inserindo um parametro dummy no prototype linux_clone.

4.1.2. Escrevendo syscall

Cada syscall implementada no Linuxulator deve ter seu protótipo com vários flags no syscalls.master. A forma do arquivo é:

...
	AUE_FORK STD		{ int linux_fork(void); }
...
	AUE_CLOSE NOPROTO	{ int close(int fd); }
...

A primeira coluna representa o número da syscall. A segunda coluna é para suporte de auditoria. A terceira coluna representa o tipo da syscall. É STD, OBSOL, NOPROTO e UNIMPL. STD é uma syscall padrão com protótipo e implementação completos. OBSOL é obsoleto e define apenas o protótipo. NOPROTO significa que a syscall é implementado em outro lugar, portanto, não precede o prefixo da ABI, etc. UNIMPL significa que a syscall será substituída pela syscall nosys (uma syscall apenas imprime uma mensagem sobre a syscall não sendo implementado e retornando ENOSYS).

De um script syscalls.master, gera três arquivos: linux_syscall.h, linux_proto.h e linux_sysent.c. O linux_syscall.h contém definições de nomes de syscall e seus valores numéricos, por exemplo:

...
#define LINUX_SYS_linux_fork 2
...
#define LINUX_SYS_close 6
...

O linux_proto.h contém definições de estrutura de argumentos para cada syscall, por exemplo:

struct linux_fork_args {
  register_t dummy;
};

E finalmente, linux_sysent.c contém uma estrutura descrevendo a tabela de entrada do sistema, usada para realmente enviar um syscall, por exemplo:

{ 0, (sy_call_t *)linux_fork, AUE_FORK, NULL, 0, 0 }, /* 2 = linux_fork */
{ AS(close_args), (sy_call_t *)close, AUE_CLOSE, NULL, 0, 0 }, /* 6 = close */

Como você pode ver, linux_fork é implementado no próprio Linuxulator, então a definição é do tipo STD e não possui argumento, que é exibido pela estrutura de argumento fictícia. Por outro lado, close é apenas um apelido para o verdadeiro close(2) do FreeBSD para que ele não possua estrutura de argumentos do linux associada e na tabela de entrada do sistema ele não é prefixado com linux, pois ele chama o verdadeiro close(2) no kernel.

4.1.3. Dummy syscalls

A camada de emulação do Linux® não está completa, pois algumas syscalls não estão implementadas corretamente e algumas não estão implementadas. A camada de emulação emprega um recurso para marcar syscalls não implementadas com a macro DUMMY. Estas definições fictícias residem em linux_dummy.c em uma forma de DUMMY(syscall); , que é então traduzido para vários arquivos auxiliares de syscall e a implementação consiste em imprimir uma mensagem dizendo que esta syscall não está implementada. O protótipo UNIMPL não é usado porque queremos ser capazes de identificar o nome da syscall que foi chamado para saber o que é mais importante implementar na syscalls.

4.2. Manuseio de signals

A manipulação de sinais é feita geralmente no kernel do FreeBSD para todas as compatibilidades binárias com uma chamada para uma camada dependente de compatibilidade. A camada de compatibilidade do Linux® define a rotina linux_sendsig para essa finalidade.

4.2.1. Linux® sendsig

Esta rotina primeiro verifica se o signal foi instalado com um SA_SIGINFO, caso em que chama a rotina linux_rt_sendsig. Além disso, ele aloca (ou reutiliza um contexto de identificador de sinal já existente) e cria uma lista de argumentos para o manipulador de signal. Ele traduz o número do signal baseado na tabela de tradução do signal, atribui um manipulador, traduz o sigset. Em seguida, ele salva o contexto para a rotina sigreturn (vários registradores, número da trap traduzida e máscara de signal). Finalmente, copia o contexto do signal para o espaço do usuário e prepara o contexto para que o manipulador de sinal real seja executado.

4.2.2. linux_rt_sendsig

Esta rotina é similar a linux_sendsig apenas a preparação do contexto do sinal é diferente. Adiciona siginfo, ucontext e algumas partes do POSIX®. Pode valer a pena considerar se essas duas funções não poderiam ser mescladas com um benefício de menos duplicação de código e, possivelmente, até mesmo execução mais rápida.

4.2.3. linux_sigreturn

Esta syscall é usada para retornar do manipulador de sinal. Ela faz algumas verificações de segurança e restaura o contexto do processo original. Também desmascara o sinal na máscara de sinal do processo.

4.3. Ptrace

Muitos derivados do UNIX® implementam a syscall ptrace(2) para permitir vários recursos de rastreamento e depuração . Esse recurso permite que o processo de rastreamento obtenha várias informações sobre o processo rastreado, como registros de despejos, qualquer memória do espaço de endereço do processo, etc. e também para rastrear o processo, como em uma instrução ou entre entradas do sistema (syscalls e traps). ptrace(2) também permite definir várias informações no processo de rastreamento (registros, etc.). ptrace(2) é um padrão de toda o UNIX® implementado na maioria dos UNIX®es em todo o mundo.

Emulação do Linux® no FreeBSD implementa a habilidade ptrace(2) em linux_ptrace.c. As rotinas para converter registradores entre Linux® and FreeBSD e a atual emulação de syscall, syscall ptrace(2). A syscall é um longo bloco de trocas que implementa em contraparte no FreeBSD para todo comando ptrace(2). Os comandos ptrace(2) são em sua maioria igual entre Linux® e FreeBSD então uma pequena modificação é necessária. Por exemplo, PT_GETREGS em Linux® opera diretamente dos dados enquanto o FreeBSD usa um ponteiro para o dado e depois performa a syscall ptrace(2) (nativa), uma cópia deve ser feita pra preservar a semantica do Linux®.

A implementação de ptrace(2) no Linuxulator tem algumas fraquezas conhecidas. Houve pânico ao usar o strace (que é um consumidor ptrace(2)) no ambiente Linuxulator. PT_SYSCALL também não está implementado.

4.4. Armadilhas (Traps)

Sempre que um processo Linux® executado na camada de emulação captura a própria trap, ela é tratada de forma transparente com a única exceção da tradução de trap. Linux® e o FreeBSD difere de opinião sobre o que é uma trap, então isso é tratado aqui. O código é realmente muito curto:

static int
translate_traps(int signal, int trap_code)
{

  if (signal != SIGBUS)
    return signal;

  switch (trap_code) {

    case T_PROTFLT:
    case T_TSSFLT:
    case T_DOUBLEFLT:
    case T_PAGEFLT:
      return SIGSEGV;

    default:
      return signal;
  }
}

4.5. Correção de pilha

O editor de links em tempo de execução do RTLD espera as chamadas tags AUX na pilha durante uma execve, portanto, uma correção deve ser feita para garantir isso. Naturalmente, cada sistema RTLD é diferente, portanto, a camada de emulação deve fornecer sua própria rotina de correção de pilha para fazer isso. O mesmo acontece com o Linuxulator. O elf_linux_fixup simplesmente copia tags AUX para a pilha e ajusta a pilha do processo de espaço do usuário para apontar logo após essas tags. Então, a RTLD funciona de maneira inteligente.

4.6. Suporte para A.OUT

A camada de emulação Linux® em i386 também suporta os binários Linux® A.OUT. Praticamente tudo o que foi descrito nas seções anteriores deve ser implementado para o suporte A.OUT (além da tradução de traps e o envio de sinais). O suporte para binários A.OUT não é mais mantido, especialmente a emulação 2.6 não funciona com ele, mas isso não causa nenhum problema, já que os ports linux-base provavelmente não suportam binários A.OUT. Esse suporte provavelmente será removido no futuro. A maioria das coisas necessárias para carregar os binários Linux® A.OUT está no arquivo imgact_linux.c.

All FreeBSD documents are available for download at https://download.freebsd.org/ftp/doc/

Questions that are not answered by the documentation may be sent to <freebsd-questions@FreeBSD.org>.
Send questions about this document to <freebsd-doc@FreeBSD.org>.