Chamadas de Sistema no Linux
   
  Thobias Salazar Trevisan (22/03/2002)
e-mail: thobias at cos.ufrj.br

Um programa comunica-se com o sistema operacional e requisita serviços através das chamadas de sistemas.

Exitem dois mecanismos no Linux para implementar chamadas de sistemas.

lcall7/lcall27 call gates;
int 0x80 software interrupt.

Programas nativos do Linux usam int 0x80, enquanto que binários vindo de outros sabores de UNIX (Solaris, UnixWare 7, etc.) usam o mecanismo lcall7.

Neste texto eu vou abordar o int 0x80.

No linux as system calls são chamadas executando a instrução em assembler int $0x80, a qual é uma execução programada (interrupção de software).

Como o kernel implementa muitas system calls, o processo deve passar um parâmetro chamado "system call number" para identificar a desejada. O registrador %eax é usado para este propósito. Assim o kernel usa um número para identificar cada chamada de sistema. A fim de associar cada número com cada system call o kernel usa uma "system call dispatch table", que é armazenada no vetor "sys_call_table" e tem "NR_syscalls" entradas (normalmente 256). NR_syscalls é o limite estático do número máximo de chamadas de sistema implemetáveis. Isto não indica que o número atual de system calls implementada seja este. As entradas na "sys_call_table" que não contém uma chamada de sistema associada possuem "sys_ni_syscall", que é uma função que retorna o erro "-ENOSYS" Deste modo a entrada número n na "sys_call_table" contém o endereço da rotina da chamada de sistema tendo como número n. A lista completa das chamadas de sistema e seus respectivos números podem ser encontradas em /usr/src/linux/include/asm-i386/unistd.h

Quando o sistema inicia (boot), a função trap_init em arch/i386/kernel/traps.c é chamada. Ela seta a entrada na IDT correspondente ao vetor 0x80 para apontar para a função system_call, que pode ser encontrada em arch/i386/kernel/entry.S

IDT (Interrupt Descriptor Table) é uma tabela do sistema que associa cada interrupção ou vetor de exceção com o endereço correspondente ao handler da interrupção ou exceção. A IDT deve ser inicializada antes do kernel habilitar interrupções.

#define SYSCALL_VECTOR 0x80
set_system_gate(SYSCALL_VECTOR,&system_call);

A função system_call é que implementa o handler para as chamada de sistemas. Quando uma aplicação faz uma chamada de sistema, os argumentos são passados via registrador, e a intrução int 0x80 é executada. Assim, entrando em modo kernel e o processador executando a função system_call.

Como system_call é a única entrada para todas as chamadas de sistemas, cada uma tem pelo menos um parâmetro que é o seu número, passado através do registrador %eax. O número de parâmetros para uma chamada de sistema não pode ser maior que sete. Os sete registradores para isto são: %eax, %ebx, %ecx, %edx, %esi e %edi, sendo que o registrador %ebp pode ser usado temporariamente. Quando uma chamada de sistema tem mais do que sete parâmetros, ela utiliza o segundo registrador (%ebx) para passar um endereço de memória do processo que contém os parâmetros restantes. As chamadas de sistema frequentemente tem que ler e escrever dados no espaço endereçamento do processo, para isto elas utilizam as macros get_user() e put_user().

Vamos olhar para a função system_call.

ENTRY(system_call)
        pushl %eax                      # save orig_eax
        SAVE_ALL
        GET_CURRENT(%ebx)
        testb $0x02,tsk_ptrace(%ebx)    # PT_TRACESYS
        jne tracesys
        cmpl $(NR_syscalls),%eax
        jae badsys
        call *SYMBOL_NAME(sys_call_table)(,%eax,4)
        movl %eax,EAX(%esp)             # save the return value
ENTRY(ret_from_sys_call)
       RESTORE_ALL
...

Alguns dos passos do código acima são: a macro SAVE_ALL copia todos os registradores para a pilha da CPU, assim as funções das chamadas de sistema encontrarão seus argumentos na pilha e não nos registradores; o conteúdo de %eax é comparado com NR_syscalls (256), se for maior falha com o erro ENOSYS; depois disso é chamada a função que executará a rotina desejada. Normalmente estas funções começam com o prefixo sys_, exemplo sys_open, sys_chmod, etc...

Cada entrada na sys_call_table tem o tamanho de 4 bytes. O kernel acha o endereço da rotina a ser invocada multiplicando o número da chamada de sistema por 4, depois adicionando o endereço inicial da sys_call_table e extraindo o ponteiro para a rotina da entrada x da tabela.

Quando a rotina chamada terminar, a função ret_from_sys_call termina a chamada de sistema.

Quando uma aplicação faz uma chamada de sistema, ela não se preocupa com o número da mesma ou como passar os seus argumentos. Quem faz este trabalho por exemplo é a libc. Ela utiliza as chamadas macros _syscallX, onde X é o número de argumentos passados. X vai de 0 à 6. Estas macros podem ser encontradas em /usr/src/linux/include/asm-i386/unistd.h

Para um exemplo de como funcionam as _syscallX e as chamadas de sistemas, olhe os códigos adiciona_syscall e sequestra, em exemplos de código.


© 2001-2002 by the people in #kernel-br - Webdesign by Caio Begotti (caio1982) & Fábio Minami (sussumo)