Comando Kernel Utilizando Chamadas do Sistema Linux

Explore a SCI e inclua suas próprias chamadas

Chamadas do sistema Linux® -- nós as utilizamos todos os dias. Mas você sabe como uma chamada do sistema é executada a partir de um espaço de usuário até o kernel? Explore a System Call Interface (SCI) Linux, saiba como incluir chamadas de um novo sistema (e alternativas para fazer isso), e descubra utilitários relacionados à SCI.

M. Tim Jones, Consultant Engineer, Emulex Corp.

M. Tim JonesM. 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.


nível de autor Contribuidor do
        developerWorks

21/Mar/2007

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 SCI

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
O Fluxo Simplificado de uma Chamada do Sistema

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
A Tabela de Chamadas do Sistema e Várias Execuções de Links

Incluindo uma Chamada do Sistema Linux

Demultiplexação de Chamada do Sistema

Algumas chamadas do sistema são demultiplexadas posteriormente pelo kernel. Por exemplo, as chamadas do soquete de Berkeley Software Distribution (BSD) (socket, bind, connect, e assim por diante) são associadas a um único índice de chamada do sistema (__NR_socketcall) mas são demultiplexadas no kernel para a chamada apropriada através de um outro argumento. Consulte a função ./linux/net/socket.c sys_socketcall.

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:

  1. Inclua a nova função.
  2. Atualize os arquivos de cabeçalho.
  3. 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;
}

Jiffies do kernel

O kernel Linux mantém uma variável global chamada jiffies, que representa o número de tiques do cronômetro desde que a máquina iniciou. Essa variável é inicializada em zero e incrementa cada interrupção do cronômetro. É possível ler jiffies com a função get_jiffies_64 e, em seguida, converter esse valor em milissegundos (msec) com jiffies_to_msecs ou em microssegundos (usec) com jiffies_to_usecs. As funções globais e associadas de jiffies são fornecidas em ./linux/include/linux/jiffies.h.

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

Clique aqui para ver lista de códigos

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 )

espaço de usuário e Constantes __NR

Observe que na Lista 6 eu forneci as constantes simbólicas __NR . É possível localizá-las em /usr/include/asm/unistd.h (para chamadas padrão do sistema).

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


Indo Além

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.

Recursos

Aprender

Obter produtos e tecnologias

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Linux
ArticleID=382562
ArticleTitle=Comando Kernel Utilizando Chamadas do Sistema Linux
publish-date=03212007