Tutorial de chamada do sistema Linux com C

Tutorial de chamada do sistema Linux com C
Em nosso último artigo sobre chamadas do sistema Linux, eu defini uma chamada do sistema, discuti as razões pelas quais alguém poderia usá -las em um programa e mergulhei em suas vantagens e desvantagens. Eu até dei um breve exemplo em Assembléia dentro de C. Ilustrou o ponto e descreveu como fazer a chamada, mas não fez nada produtivo. Não é exatamente um exercício de desenvolvimento emocionante, mas ilustrou o ponto.

Neste artigo, usaremos chamadas reais do sistema para fazer trabalhos reais em nosso programa C. Primeiro, analisaremos se você precisar usar uma chamada do sistema, depois forneça um exemplo usando a chamada sendfile () que pode melhorar drasticamente o desempenho da cópia do arquivo. Por fim, examinaremos alguns pontos a serem lembrados ao usar chamadas do sistema Linux.

Você precisa de uma chamada do sistema?

Embora seja inevitável, você usará uma chamada do sistema em algum momento da sua carreira de desenvolvimento C, a menos que você esteja segmentando alto desempenho ou uma funcionalidade de tipo específico, a biblioteca GLIBC e outras bibliotecas básicas incluídas nas principais distribuições do Linux cuidarão da maioria da maioria dos suas necessidades.

A Biblioteca Padrão Glibc fornece uma estrutura de plataforma cruzada e bem testada para executar funções que, de outra forma, exigiriam chamadas de sistema específicas do sistema. Por exemplo, você pode ler um arquivo com fscanf (), fread (), getc (), etc., ou você pode usar a chamada do sistema read () linux. As funções glibc fornecem mais recursos (i.e. melhor manuseio de erros, IO formatado, etc.) e funcionará em qualquer sistema de suportes do GLIBC do sistema.

Por outro lado, há momentos em que o desempenho intransigente e a execução exata são críticos. O invólucro que Fread () fornece vai adicionar sobrecarga e, embora menor, não é totalmente transparente. Além disso, você pode não querer ou precisar dos recursos extras que o invólucro fornece. Nesse caso, você é melhor servido com uma chamada de sistema.

Você também pode usar chamadas do sistema para executar funções ainda não suportadas pelo Glibc. Se sua cópia do Glibc estiver atualizada, isso dificilmente será um problema, mas o desenvolvimento de distribuições mais antigas com kernels mais recentes poderá exigir essa técnica.

Agora que você leu as isenções de responsabilidade, avisos e desvios em potencial, agora vamos cavar alguns exemplos práticos.

Em que CPU estamos?

Uma pergunta que a maioria dos programas provavelmente não pensa em fazer, mas um válido, no entanto. Este é um exemplo de chamada de sistema que não pode ser duplicada com glibc e não é coberta com um invólucro glibc. Neste código, chamaremos a chamada getCPU () diretamente através da função syscall (). A função syscall funciona da seguinte maneira:

syscall (sys_call, arg1, arg2,…);

O primeiro argumento, sys_call, é uma definição que representa o número da chamada do sistema. Quando você inclui sys/syscall.h estes estão incluídos. A primeira parte é sys_ e a segunda parte é o nome da chamada do sistema.

Argumentos para a chamada entram no arg1, arg2 acima. Algumas chamadas exigem mais argumentos e continuarão em ordem na página do homem. Lembre -se de que a maioria dos argumentos, especialmente para devoluções, exigirá que os ponteiros cheguem a matrizes ou a memória alocada pela função malloc.

Exemplo 1.c

#incluir
#incluir
#incluir
#incluir
int main ()
CPU não assinado, nó;
// Obtenha o núcleo atual da CPU e o nó NUMA via chamada do sistema
// Observe que isso não tem um invólucro glibc, então devemos chamá -lo diretamente
syscall (sys_getcpu, & cpu, & node, null);
// exibe informações
printf ("Este programa está sendo executado no CPU Core %U e nó nó nó.\ n \ n ", cpu, nó);
retornar 0;

Para compilar e executar:
GCC Exemplo1.C -O Exemplo1
./Exemplo 1

Para obter resultados mais interessantes, você pode girar tópicos através da biblioteca Pthreads e depois chamar essa função para ver em qual processador seu tópico está executando.

Sendfile: desempenho superior

O Sendfile fornece um excelente exemplo de aprimoramento do desempenho através de chamadas do sistema. O função sendfile () copia dados de um descritor de arquivo para outro. Em vez de usar as funções múltiplas Fread () e Fwrite (), o Sendfile executa a transferência no espaço do kernel, reduzindo a sobrecarga e assim aumentando o desempenho.

Neste exemplo, vamos copiar 64 MB de dados de um arquivo para outro. Em um teste, usaremos os métodos padrão de leitura/gravação na biblioteca padrão. No outro, usaremos as chamadas do sistema e a chamada sendfile () para explodir esses dados de um local para outro.

Test1.C (glibc)

#incluir
#incluir
#incluir
#incluir
#define buffer_size 67108864
#define buffer_1 "buffer1"
#define buffer_2 "buffer2"
int main ()
Arquivo *fout, *fin;
Printf ("\ ni/o teste com funções tradicionais do glibc.\ n \ n ");
// pegue um buffer buffer_size.
// O buffer terá dados aleatórios, mas não nos importamos com isso.
printf ("alocando buffer de 64 MB:");
char *buffer = (char *) malloc (buffer_size);
printf ("feito \ n");
// Escreva o buffer para Fout
printf ("Escrevendo dados para o primeiro buffer:");
fout = fopen (buffer_1, "wb");
fwrite (buffer, sizeof (char), buffer_size, fout);
fclose (fout);
printf ("feito \ n");
printf ("Copiando dados do primeiro arquivo para o segundo:");
fin = fopen (buffer_1, "rb");
fout = fopen (buffer_2, "wb");
Fread (buffer, sizeof (char), buffer_size, fin);
fwrite (buffer, sizeof (char), buffer_size, fout);
fclose (FIN);
fclose (fout);
printf ("feito \ n");
printf ("Buffer de libertação:");
grátis (buffer);
printf ("feito \ n");
printf ("Excluindo arquivos:");
remover (buffer_1);
remover (buffer_2);
printf ("feito \ n");
retornar 0;

Test2.C (chamadas do sistema)

#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#define buffer_size 67108864
int main ()
int fout, fin;
Printf ("\ ni/o teste com sendfile () e chamadas de sistema relacionadas.\ n \ n ");
// pegue um buffer buffer_size.
// O buffer terá dados aleatórios, mas não nos importamos com isso.
printf ("alocando buffer de 64 MB:");
char *buffer = (char *) malloc (buffer_size);
printf ("feito \ n");
// Escreva o buffer para Fout
printf ("Escrevendo dados para o primeiro buffer:");
fout = aberto ("buffer1", o_rdonly);
escreva (FOUT, & buffer, buffer_size);
fechar (fout);
printf ("feito \ n");
printf ("Copiando dados do primeiro arquivo para o segundo:");
fin = aberto ("buffer1", o_rdonly);
fout = aberto ("buffer2", o_rdonly);
sendfile (fout, fin, 0, buffer_size);
fechar (fin);
fechar (fout);
printf ("feito \ n");
printf ("Buffer de libertação:");
grátis (buffer);
printf ("feito \ n");
printf ("Excluindo arquivos:");
desvincular ("buffer1");
desvincular ("buffer2");
printf ("feito \ n");
retornar 0;

Testes de compilação e corrida 1 e 2

Para construir esses exemplos, você precisará das ferramentas de desenvolvimento instaladas em sua distribuição. No Debian e Ubuntu, você pode instalar isso com:

APT Instale os essenciais de construção

Em seguida, compilar com:

GCC Test1.c -o test1 && gcc test2.C -O Test2

Para executar os dois e testar o desempenho, execute:

tempo ./test1 && time ./test2

Você deve obter resultados como este:

Teste de E/S com funções tradicionais do glibc.

Alocando o buffer de 64 MB: feito
Escrevendo dados para o primeiro buffer: feito
Copiando dados do primeiro arquivo para o segundo: feito
Buffer de libertação: feito
Excluindo arquivos: feito
Real 0m0.397s
Usuário 0m0.000s
SYS 0M0.203s
Teste de E/S com sendfile () e chamadas de sistema relacionadas.
Alocando o buffer de 64 MB: feito
Escrevendo dados para o primeiro buffer: feito
Copiando dados do primeiro arquivo para o segundo: feito
Buffer de libertação: feito
Excluindo arquivos: feito
Real 0m0.019S
Usuário 0m0.000s
SYS 0M0.016S

Como você pode ver, o código que usa as chamadas do sistema funciona muito mais rápido que o equivalente do Glibc.

Coisas para lembrar

As chamadas do sistema podem aumentar o desempenho e fornecer funcionalidades adicionais, mas não estão sem suas desvantagens. Você terá que pesar as chamadas do sistema de benefícios fornecem contra a falta de portabilidade da plataforma e às vezes a funcionalidade reduzida em comparação com as funções da biblioteca.

Ao usar algumas chamadas do sistema, você deve tomar cuidado para usar os recursos devolvidos das chamadas do sistema, em vez de funções da biblioteca. Por exemplo, a estrutura de arquivos usada para as funções FOPEN (), Fread (), Fwrite () e Fclose () não são as mesmas que o número do descritor de arquivo da chamada Open () (retornada como um número inteiro). Misturar isso pode levar a problemas.

Em geral, as chamadas do sistema Linux têm menos faixas de pára -choques do que as funções do GLIBC. Embora seja verdade que as chamadas do sistema têm algum tratamento de erros e relatórios, você obterá uma funcionalidade mais detalhada de uma função glibc.

E finalmente, uma palavra sobre segurança. Chamadas de sistema interface diretamente com o kernel. O kernel Linux tem proteções extensas contra travessuras da terra do usuário, mas existem bugs não descobertos. Não confie que uma chamada do sistema validará sua entrada ou isolá -lo de problemas de segurança. É aconselhável garantir que os dados que você entregam a uma chamada do sistema estão higienizados. Naturalmente, este é um bom conselho para qualquer chamada de API, mas você não pode ter cuidado ao trabalhar com o kernel.

Espero que você tenha gostado deste mergulho mais profundo na terra das chamadas do sistema Linux. Para uma lista completa de chamadas do sistema Linux, consulte nossa lista mestre.