Programação C

Tutorial de chamada do sistema Linux com C

Tutorial de chamada do sistema Linux com C
Em nosso último artigo sobre chamadas de sistema do Linux, eu defini uma chamada de sistema, discuti os motivos pelos quais alguém pode usá-la em um programa e investiguei suas vantagens e desvantagens. Até dei um breve exemplo em montagem dentro de C. Ilustrou o ponto e descreveu como fazer a ligação, mas não fez nada produtivo. Não é exatamente um exercício de desenvolvimento emocionante, mas ilustrou o ponto.

Neste artigo, vamos usar chamadas de sistema reais para fazer um trabalho real em nosso programa C. Primeiro, vamos revisar se você precisa usar uma chamada de sistema e, em seguida, fornecer um exemplo usando a chamada sendfile () que pode melhorar drasticamente o desempenho da cópia de arquivo. Por fim, examinaremos alguns pontos a serem lembrados ao usar chamadas de sistema Linux.

Você precisa de uma chamada de sistema?

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

A biblioteca padrão glibc fornece uma estrutura multiplataforma 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 de sistema read () Linux. As funções glibc fornecem mais recursos (i.e. melhor tratamento de erros, IO formatado, etc.) e funcionará em qualquer sistema de suporte glibc.

Por outro lado, há momentos em que o desempenho inflexível e a execução exata são essenciais. O wrapper que fread () fornece adicionará overhead e, embora seja pequeno, não é totalmente transparente. Além disso, você pode não querer ou precisar dos recursos extras que o wrapper oferece. Nesse caso, você é melhor atendido com uma chamada de sistema.

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

Agora que você leu as isenções de responsabilidade, avisos e possíveis desvios, vamos examinar alguns exemplos práticos.

Em que CPU estamos?

Uma pergunta que a maioria dos programas provavelmente não pensa em fazer, mas válida mesmo assim. Este é um exemplo de uma chamada de sistema que não pode ser duplicada com glibc e não é coberta por um wrapper glibc. Neste código, chamaremos a chamada getcpu () diretamente por meio 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.

Os argumentos para a chamada vão para arg1, arg2 acima. Algumas chamadas requerem mais argumentos e continuarão em ordem a partir de sua página de manual. Lembre-se de que a maioria dos argumentos, especialmente para retornos, exigirão ponteiros para matrizes de char ou memória alocada por meio da função malloc.

Exemplo 1.c

#incluir
#incluir
#incluir
#incluir
 
int main ()
 
CPU não assinada, nó;
 
// Obtenha o núcleo da CPU e o nó NUMA atuais através da chamada do sistema
// Observe que não há wrapper glibc, portanto, devemos chamá-lo diretamente
syscall (SYS_getcpu, & cpu, & node, NULL);
 
// Exibir informações
printf ("Este programa está a correr no núcleo da CPU% ue no nó NUMA% u.\ n \ n ", cpu, nó);
 
return 0;
 

 
Para compilar e executar:
 
gcc example1.c -o exemplo1
./Exemplo 1

Para resultados mais interessantes, você pode girar threads por meio da biblioteca pthreads e, em seguida, chamar esta função para ver em qual processador seu thread está sendo executado.

Sendfile: Desempenho Superior

Sendfile fornece um excelente exemplo de melhoria de desempenho por meio de chamadas de sistema. A função sendfile () copia dados de um descritor de arquivo para outro. Em vez de usar várias funções fread () e fwrite (), sendfile realiza a transferência no espaço do kernel, reduzindo a sobrecarga e, portanto, aumentando o desempenho.

Neste exemplo, vamos copiar 64 MB de dados de um arquivo para outro. Em um teste, vamos usar os métodos padrão de leitura / gravação na biblioteca padrão. No outro, usaremos chamadas de sistema e a chamada sendfile () para enviar 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 ("\ nTeste I / O com funções glibc tradicionais.\ n \ n ");
 
// Pega 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 ("Gravando dados no 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 ("Liberando buffer:");
livre (tampão);
printf ("FEITO \ n");
 
printf ("Excluindo arquivos:");
remover (BUFFER_1);
remover (BUFFER_2);
printf ("FEITO \ n");
 
return 0;
 

test2.c (chamadas de sistema)

#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
 
# define BUFFER_SIZE 67108864
 
int main ()
 
int fOut, fIn;
 
printf ("\ nTeste I / O com sendfile () e chamadas de sistema relacionadas.\ n \ n ");
 
// Pega 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 ("Gravando dados no primeiro buffer:");
fOut = aberto ("buffer1", O_RDONLY);
escrever (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 ("Liberando buffer:");
livre (tampão);
printf ("FEITO \ n");
 
printf ("Excluindo arquivos:");
unlink ("buffer1");
unlink ("buffer2");
printf ("FEITO \ n");
 
return 0;
 

Compilando e executando os testes 1 e 2

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

apt install build-essentials

Em seguida, compile com:

gcc test1.c -o test1 && gcc test2.c -o test2

Para executar ambos e testar o desempenho, execute:

Tempo ./ test1 && time ./ test2

Você deve obter resultados como este:

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

Alocando buffer de 64 MB: CONCLUÍDO
Gravando dados no primeiro buffer: CONCLUÍDO
Copiando dados do primeiro arquivo para o segundo: CONCLUÍDO
Liberando buffer: CONCLUÍDO
Excluindo arquivos: CONCLUÍDO
real 0m0.397s
usuário 0m0.000s
sys 0m0.Década de 203
Teste de I / O com sendfile () e chamadas de sistema relacionadas.
Alocando buffer de 64 MB: CONCLUÍDO
Gravando dados no primeiro buffer: CONCLUÍDO
Copiando dados do primeiro arquivo para o segundo: CONCLUÍDO
Liberando buffer: CONCLUÍDO
Excluindo arquivos: CONCLUÍDO
real 0m0.019s
usuário 0m0.000s
sys 0m0.016s

Como você pode ver, o código que usa as chamadas de sistema é executado muito mais rápido do que o equivalente glibc.

Coisas para lembrar

As chamadas do sistema podem aumentar o desempenho e fornecer funcionalidade adicional, mas têm suas desvantagens. Você terá que pesar os benefícios que as chamadas de sistema oferecem em relação à falta de portabilidade da plataforma e, às vezes, 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 retornados das chamadas do sistema em vez de funções de biblioteca. Por exemplo, a estrutura FILE usada para as funções fopen (), fread (), fwrite () e fclose () do glibc não são iguais ao número do descritor de arquivo da chamada de sistema open () (retornado como um inteiro). Misturar isso pode levar a problemas.

Em geral, as chamadas do sistema Linux têm menos faixas bumper do que as funções glibc. Embora seja verdade que as chamadas de sistema têm algum tratamento de erros e relatórios, você obterá funcionalidades mais detalhadas de uma função glibc.

E, finalmente, uma palavra sobre segurança. As chamadas do sistema fazem interface direta com o kernel. O kernel do Linux tem proteções extensas contra travessuras da terra do usuário, mas existem bugs não descobertos. Não confie que uma chamada de sistema irá validar sua entrada ou isolar você de problemas de segurança. É aconselhável garantir que os dados que você entrega para uma chamada de sistema sejam 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 de sistema Linux. Para uma lista completa de chamadas de sistema Linux, consulte nossa lista principal.

Como mostrar o contador de FPS em jogos Linux
Os jogos Linux tiveram um grande impulso quando a Valve anunciou o suporte Linux para o cliente Steam e seus jogos em 2012. Desde então, muitos jogos ...
Como baixar e jogar Sid Meier's Civilization VI no Linux
Introdução ao jogo Civilization 6 é uma versão moderna do conceito clássico introduzido na série de jogos Age of Empires. A ideia era bastante simples...
Como instalar e jogar Doom no Linux
Introdução ao Doom A série Doom teve origem nos anos 90 após o lançamento do Doom original. Foi um sucesso instantâneo e, a partir desse momento, a sé...