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.
|