Uma chamada do sistema é uma interface entre um aplicativo de espaço de usuário e um serviço que o kernel fornece. Como o serviço é fornecido no kernel, uma chamada direta não pode ser executada; em vez disso, você deve utilizar um processo de cruzamento do limite de espaço do usuário/kernel. A maneira que você faz isso é diferente com base na arquitetura específica. Por esse motivo, vou aderir à arquitetura mais comum, i386.
Nesse artigo, eu exploro o Linux SCI, demonstro a inclusão de uma chamada do sistema no kernel 2.6.20 e, em seguida, utilizo essa função a partir do espaço do usuário. Eu também investigo algumas das funções que você achará úteis para desenvolvimento de chamada do sistema e alternativas para chamadas do sistema. Finalmente, eu examino alguns dos mecanismos auxiliares relacionados a chamadas do sistema, como o rastreio de seu uso a partir de um determinado processo.
A implementação de chamadas do sistema no Linux é variada com base na arquitetura,
mas ela também pode diferir dentro de uma determinada arquitetura. Por exemplo, processadores
x86 mais antigos utilizavam um mecanismo de interrupção para migrar do espaço de usuário para o espaço do kernel,
mas novos processadores IA-32 fornecem instruções que otimizam essa transição (utilizando instruções
sysenter e sysexit
). Como tantas opções existem e o resultado final é muito complicado,
eu me aterei à discussão superficial dos detalhes da interface. Consulte os Recursos no final desse artigo para obter os detalhes explícitos.
Você não precisa entender completamente as qualidades intrínsecas da SCI para aditá-la; portanto, eu exploro uma
versão simples do processo de chamada do sistema (consulte a Figura 1). Cada chamada do sistema é
diversificada no kernel através de um único ponto de entrada. O registro eax é utilizado
para identificar a chamada do sistema específica que deve ser invocada, que é especificada
na biblioteca C (pela chamada a partir do aplicativo de
espaço do usuário). Quando a biblioteca C tiver carregado o índice
da chamada do sistema e qualquer argumento, uma interrupção de software é invocada (interrupção 0x80),
que resulta na execução (através do manipulador de interrupção) da
system_call . Essa função manipula todas as chamadas
do sistema, conforme identificado pelo conteúdo de eax. Após alguns testes simples, a chamada
do sistema real é invocada utilizando a system_call_table e o
índice contido em eax. No retorno da chamada do sistema,
syscall_exit é finalmente atingida e uma chamada para transições
resume_userspace de volta ao espaço do usuário.
A execução continua na biblioteca C que, em seguida, retorna
para o aplicativo de usuário.
Figura 1. O Fluxo Simplificado de uma Chamada do Sistema Utilizando o Método de Interrupção
No núcleo da SCI está a tabela de demultiplexação de chamada do sistema. Essa tabela, mostrada
na Figura 2, utiliza o índice fornecido em eax para identificar qual chamada do sistema
invocar a partir da tabela (sys_call_table). Uma amostra do
conteúdo dessa tabela e os locais dessas entidades também são mostrados. (Para
saber mais sobre demultiplexação, consulte a barra lateral, "Demultiplexação
de Chamada do Sistema.")
Figura 2. A Tabela de Chamadas do Sistema e Várias Execuções de Links
Incluindo uma Chamada do Sistema Linux
Incluir uma nova chamada do sistema é principalmente processual, embora você deva prestar atenção em algumas coisas. Essa seção percorre a construção de algumas chamadas do sistema para demonstrar sua implementação e uso por um aplicativo de espaço do usuário.
Você executa três etapas básicas para incluir uma nova chamada do sistema no kernel:
- Inclua a nova função.
- Atualize os arquivos de cabeçalho.
- Atualize a tabela de chamadas do sistema para a nova função.
Nota: Esse processo ignora necessidades de espaço do usuário, que será abordado posteriormente.
Muitas vezes, cria-se um novo arquivo para suas funções. No entanto, visando a simplicidade, eu incluo minhas novas funções em um arquivo de origem existente. As primeiras duas funções, mostradas na Lista 1, são exemplos simples de uma chamada do sistema. A Lista 2 fornece uma função pouco mais complicada que utiliza argumentos de ponteiro.
Lista 1. Funções de Kernel Simples para o Exemplo de Chamada do Sistema
asmlinkage long sys_getjiffies( void )
{
return (long)get_jiffies_64();
}
asmlinkage long sys_diffjiffies( long ujiffies )
{
return (long)get_jiffies_64() - ujiffies;
}
|
Na Lista 1, duas funções são fornecidas para monitoramento de jiffies. (Para obter
informações adicionais sobre jiffies, consulte a barra lateral, "Jiffies
do kernel.") A primeira função retorna os jiffies atuais, enquanto a segunda
retorna a diferença do valor atual e do valor que o responsável pela chamada passa.
Observe o uso do modificador asmlinkage . Essa macro
(definida em linux/include/asm-i386/linkage.h) instrui o compilador para passar todos
os argumentos da função na pilha.
Lista 2. Funções de Kernel Finais para o Exemplo de Chamada do Sistema
asmlinkage long sys_pdiffjiffies( long ujiffies,
long __user *presult )
{
long cur_jiffies = (long)get_jiffies_64();
long result;
int err = 0;
if (presult) {
result = cur_jiffies - ujiffies;
err = put_user( result, presult );
}
return err ? -EFAULT : 0;
}
|
A Lista 2 fornece a terceira função. Essa função utiliza dois argumentos: um
long e um ponteiro para um long
que está definido como __user. A macro __user simplesmente informa ao compilador (através de
noderef) que o ponteiro deve ter a referência cancelada
(já que não é significativo no espaço de endereço atual). Essa função calcula
a diferença entre dois valores jiffies e, em seguida, fornece o resultado ao
usuário através de um ponteiro de espaço do usuário. A função put_user
coloca o valor do resultado no espaço de usuário no local que
presult especifica. Se ocorre um erro durante essa
operação, ele será retornado e você notificará, da mesma forma, o responsável pela chamada do espaço do usuário.
Para a etapa 2, eu atualizo os arquivos de cabeçalho para criar espaço para as novas funções na tabela de chamadas do sistema. Para isso, eu atualizo o arquivo de cabeçalho linux/include/asm/unistd.h com os novos números de chamada do sistema. As atualizações são mostradas em negrito na Lista 3.
Lista 3. Atualizações no unistd.h para Criar Espaço para Novas Chamadas do Sistema
#define __NR_getcpu 318
#define __NR_epoll_pwait 319
#define __NR_getjiffies 320
#define __NR_diffjiffies 321
#define __NR_pdiffjiffies 322
#define NR_syscalls 323
|
Agora eu tenho minhas chamadas do sistema kernel e números para representá-las. Tudo que eu preciso fazer agora é desenhar uma equivalência entre esses números (índices de tabela) e as próprias funções. Essa é a etapa 3, atualizando a tabela de chamada do sistema. Conforme mostrado na Lista 4, eu atualizo o arquivo linux/arch/i386/kernel/syscall_table.S para as novas funções que irão preencher os índices específicos mostrados na Lista 3.
Lista 4. Atualizar a Tabela de Chamadas do Sistema com as Novas Funções
.long sys_getcpu
.long sys_epoll_pwait
.long sys_getjiffies /* 320 */
.long sys_diffjiffies
.long sys_pdiffjiffies
|
Nota: O tamanho dessa tabela é definido pela constante simbólica
NR_syscalls.
Nesse momento, o kernel é atualizado. Eu devo recompilar o kernel e tornar a nova imagem disponível para inicialização antes de testar o aplicativo de espaço do usuário.
Lendo e Gravando a Memória do Usuário
O kernel Linux fornece várias funções que é possível utilizar para mover argumentos
de chamada do sistema para e a partir do espaço do usuário. As opções incluem funções simples para tipos básicos
(como get_user ou put_user). Para mover blocos de dados como estruturas
ou matrizes, é possível utilizar um outro conjunto de funções:
copy_from_user e copy_to_user. Mover cadeias terminadas por caractere nulo tem suas
próprias chamadas: strncpy_from_user e strlen_from_user. Também é possível testar a validez de um ponteiro de espaço de usuário através de uma chamada para
access_ok. Essas funções são definidas em
linux/include/asm/uaccess.h.
Utilize a macro access_ok para validar um ponteiro de
espaço de usuário para uma determinada operação. Essa função utiliza o tipo de acesso
(VERIFY_READ ou VERIFY_WRITE), o ponteiro para o bloco de memória de espaço
do usuário e o tamanho do bloco (em bytes). A função retorna zero se tiver êxito:
int access_ok( tipo, endereço, tamanho ); |
Mover tipos simples entre o kernel e o espaço de usuário (como ints ou longs) é
realizado facilmente com get_user e put_user. Cada uma dessas macros adota um valor e um ponteiro
para uma variável. A função get_user move o valor
que o endereço de espaço de usuário especifica (ptr) para a
variável do kernel especificada (var). A função put_user move o valor que a variável do
kernel (var) especifica no endereço de espaço de usuário (ptr). As funções retornam zero se tiverem êxito:
int get_user( var, ptr ); int put_user( var, ptr ); |
Para mover objetos maiores, como estruturas ou matrizes, é possível utilizar as funções
copy_from_user e copy_to_user . Essas funções movem um bloco
inteiro de dados entre o espaço de usuário e o kernel. A função copy_from_user move um bloco de dados do
espaço de usuário para o espaço do kernel e copy_to_user move um
bloco de dados do kernel para o espaço do usuário:
unsigned long copy_from_user( void *to, const void __user *from, unsigned long n ); unsigned long copy_to_user( void *to, const void __user *from, unsigned long n ); |
Finalmente, é possível copiar uma cadeia terminada por caractere NULL do espaço de usuário para o kernel utilizando strncpy_from_user . Antes de chamar
essa função, é possível obter o tamanho da cadeia de espaço de usuário com uma chamada para a macro
strlen_user :
longstrncpy_from_user( char *dst, const char __user *src, long count ); strlen_user( str ); |
Essas funções fornecem as informações básicas para movimento de memória entre o kernel e o espaço do usuário. Algumas funções adicionais existem (como aquelas que reduzem a quantidade da verificação executada). É possível localizar essas funções em uaccess.h.
Utilizando a Chamada do Sistema
Agora que o kernel está atualizado com algumas novas chamadas do sistema, vamos examinar o que é necessário para utilizá-las a partir de um aplicativo de espaço do usuário. Há duas maneiras que é possível utilizar novas chamadas do sistema kernel. A primeira é um método de comodidade (não algo que você provavelmente desejaria executar no código de produção) e a segunda é o método tradicional que requer um pouco mais de trabalho.
Com o primeiro método, chame suas novas funções conforme identificadas por seu índice
através de syscall . Com a função syscall é possível chamar uma chamada do sistema
especificando seu índice de chamada e um conjunto de argumentos. Por exemplo, o aplicativo
curto mostrado na Lista 5 chama seu
sys_getjiffies utilizando seu índice.
Lista 5. Utilizando syscall para Invocar uma Chamada do Sistema
#include <linux/unistd.h>
#include <sys/syscall.h>
#define __NR_getjiffies 320
int main()
{
long jiffies;
jiffies = syscall( __NR_getjiffies );
printf( "Current jiffies is %lx\n", jiffies );
return 0;
}
|
Como é possível ver, a função syscall inclui como seu
primeiro argumento o índice da tabela de chamada do sistema a utilizar. Se houvesse qualquer
argumento para passar, estes seriam fornecidos após o índice de chamada. A maioria das chamadas do sistema
inclui uma constante simbólica SYS_ para especificar seu
mapeamento para os índices __NR_ . Por exemplo, invoque
o índice __NR_getpid com
syscall como:
syscall( SYS_getpid )
|
A função syscall é específica da arquitetura, mas utiliza
um mecanismo para transferir o controle ao kernel. O argumento é baseado em um mapeamento
de índices __NR para símbolos SYS_
fornecidos por /usr/include/bits/syscall.h (definidos quando a libc é construída).
Nunca faça referência a esse arquivo diretamente; em vez disso utilize /usr/include/sys/syscall.h.
O método tradicional requer que se crie chamadas de função que correspondam
àqueles no kernel em termos de índice de chamada do sistema (de forma que esteja chamando o
serviço de kernel correto) e que os argumentos correspondam. O Linux fornece um conjunto de macros para
fornecer esse recurso. As macros _syscallN são
definidas em /usr/include/linux/unistd.h e possuem o seguinte formato:
_syscall0( ret-type, func-name )
_syscall1( ret-type, func-name, arg1-type, arg1-name )
_syscall2( ret-type, func-name, arg1-type, arg1-name, arg2-type, arg2-name )
|
As macros _syscall são definidas com profundidade de até
seis argumentos (embora apenas três sejam mostrados aqui).
Agora, aqui está como utiliza-se as macros _syscall para tornar
suas novas chamadas do sistema visíveis para o espaço do usuário. A Lista 6 mostra um aplicativo
que utiliza cada uma de suas chamadas do sistema conforme definidas pelas macros
_syscall .
Lista 6. Utilizando a Macro _syscall para Desenvolvimento de Aplicativos de Espaço do Usuário
#include <stdio.h>
#include <linux/unistd.h>
#include <sys/syscall.h>
#define __NR_getjiffies 320
#define __NR_diffjiffies 321
#define __NR_pdiffjiffies 322
_syscall0( long, getjiffies );
_syscall1( long, diffjiffies, long, ujiffies );
_syscall2( long, pdiffjiffies, long, ujiffies, long*, presult );
int main()
{
long jifs, result;
int err;
jifs = getjiffies();
printf( "difference is %lx\n", diffjiffies(jifs) );
err = pdiffjiffies( jifs, &result );
if (!err) {
printf( "difference is %lx\n", result );
} else {
printf( "error\n" );
}
return 0;
}
|
Observe que os índices __NR são necessários nesse
aplicativo porque a macro _syscall utiliza o
func-name para construir o índice
__NR (getjiffies
-> __NR_getjiffies). Mas o resultado é que é
possível chamar suas funções kernel utilizando seus nomes, exatamente como qualquer outra chamada do sistema.
Alternativas para Interações Usuário/Kernel
Chamadas do sistema são uma maneira eficiente de solicitar serviços no kernel. O maior problema com elas é que essa é uma interface padronizada. Seria difícil ter sua nova chamada do sistema incluída no kernel; portanto, qualquer inclusão é provavelmente entregue através de outros meios. Se você não tiver intenção de ter suas chamadas do sistema em uma linha principal no kernel Linux público, então chamadas do sistema são convenientes e uma forma eficiente de disponibilizar serviços kernel para o espaço do usuário.
Outra maneira de tornar seus serviços visíveis ao espaço de usuário é através do sistema de arquivos /proc. O sistema de arquivos /proc é um sistema de arquivos virtual para o qual é possível trazer à tona um diretório e arquivos para o usuário e, em seguida, fornecer uma interface no kernel para seus novos serviços através de uma interface de sistema de arquivos (ler, gravar, etc).
Rastreando Chamadas do Sistema com Strace
O kernel Linux fornece uma maneira útil de rastrear as chamadas do sistema que um processo
invoca (assim como aqueles sinais que o processo recebe). O utilitário é
chamado strace e é executado a partir da linha de comando,
utilizando o aplicativo que deseja rastrear como seu argumento. Por exemplo, se
deseja saber quais chamadas do sistema foram invocadas durante o contexto do comando
date , digite o seguinte comando:
strace date |
O resultado é um dump um tanto grande mostrando as várias chamadas do sistema que são
executadas no contexto de uma chamada do comando date . Você
verá o carregamento de bibliotecas compartilhadas, mapeamento da memória e -- no final do
rastreio -- a emissão das informações sobre a data para a saída padrão:
... write(1, "Fri Feb 9 23:06:41 MST 2007\n", 29Fri Feb 9 23:06:41 MST 2007) = 29 munmap(0xb747a000, 4096) = 0 exit_group(0) = ? $ |
Esse rastreio é realizado no kernel quando o pedido de chamada do sistema atual
tem uma configuração de campo especial chamada syscall_trace, que
faz a função do_syscall_trace ser invocada. Também é
possível localizar as chamadas de rastreio como parte do pedido de chamada do sistema em
./linux/arch/i386/kernel/entry.S (consulte
syscall_trace_entry).
Chamadas do sistema são uma maneira eficiente de passar entre o espaço de usuário e o kernel para solicitar serviços no espaço do kernel. Mas elas também são rigorosamente controladas e é bem mais fácil simplesmente incluir uma nova entrada do sistema de arquivos /proc para fornecer as interações usuário/kernel. No entanto, quando a velocidade é importante, chamadas do sistema são uma maneira ideal de extrair o melhor desempenho de seu aplicativo. Consulte Recursos para explorar ainda mais a SCI.
Aprender
- Em "Acessar o
Kernel Linux Utilizando o Sistema de Arquivos /proc" (developerWorks, Março de 2006), saiba
como desenvolver o código kernel que utiliza o sistema de arquivos /proc para comunicação
espaço do usuário/kernel.
- Leia "Mecanismo de Chamada
do Sistema Baseado em Sysenter no Linux 2.6" de Manugarg para fazer um exame detalhado
no portão de chamada do sistema entre o aplicativo de espaço de usuário e o kernel. Esse documento
concentra-se nos mecanismos de transição fornecidos no kernel 2.6.
- Esse documento detalha as execuções de links da
linguagem assembly entre o espaço de usuário e o kernel.
- As macros GNU
CLibrary (glibc) é a biblioteca padrão para GNUC. Será encontrada a glibc para Linux e também para outros numerosos sistemas operacionais. A GNUCLibrary segue numerosas normas, incluindo a ISO C 99, POSIX e UNIX98. É possível encontrar informações adicionais sobre isso no Projeto do GNU. - As macros Página de Instrução
de Syscalls do Linux fornece uma lista completa de chamadas do sistema disponíveis em Linux.
- A Wikipedia fornece uma perspectiva interessante sobre
chamadas do sistema, incluindo implementações históricas e típicas.
- Embora um pouco fora de uso, uma Interface de Programação de Aplicação (API) do kernel que documenta muitas das funções
kernel disponíveis para uso geral (dentro do kernel) é fornecida. Isso inclui as
funções de gerenciamento de memória do espaço de usuário bem como muitas outras.
- Na zona Linux do developerWorks, encontre mais recursos para desenvolvedores Linux.
- Fique atualizado com eventos técnicos e Webcasts do developerWorks.
Obter produtos e tecnologias
-
Solicite o SEK para Linux, um conjunto de dois DVDs que contém o software de período experimental IBM mais recente para Linux a partir do DB2®, Lotus®, Rational®, Tivoli®e WebSphere®.
- Com o Software de período experimental IBM, disponível para download diretamente do developerWorks, construa seu próximo projeto de desenvolvimento em Linux.
Discutir
- Verifique blogs do developerWorks e envolva-se com a comunidade do developerWorks.

M. Tim Jones é um arquiteto de firmwares embarcados e autor de Inteligência Artificial: Sistemas de Abordagem, GNU/Linux, Programação de Aplicativos AI (atualmente em sua segunda edição), Programação de Aplicativos AI (em sua segunda edição) e BSD Sockets Programming from a Multilanguage Perspective. Sua formação em engenharia vai desde o desenvolvimento de kernels para nave espacial geossincrônica até a arquitetura de sistema embarcado e o desenvolvimento de protocolos de interligação de redes. Tim é um Engenheiro Consultor para a Emulex Corp. em Longmont, Colorado.