índice
1. Processo de chamada do sistema
2. Três métodos de chamada de sistema
2.1. Funções de biblioteca fornecidas pela glibc
2.2, use syscall para chamar diretamente
Chamada de sistema é um conjunto de interfaces fornecidas pelo sistema operacional para a interação entre processos em execução no modo de usuário e dispositivos de hardware (como CPUs, discos, impressoras, etc.). Quando o processo do usuário precisa fazer uma chamada de sistema, a CPU muda para o modo kernel por meio de uma interrupção suave para iniciar a execução da função de chamada de sistema kernel.
1. Processo de chamada do sistema
Pegue o Linux 0.11 como um exemplo para descrever brevemente o processo de chamada e não verifique se o sistema operacional moderno mudou, mas a ideia básica deve ser semelhante. Como mostrado abaixo:
Em primeiro lugar, o programa aplicativo pode chamar diretamente a API fornecida pelo sistema, o que pode ser feito no modo de usuário (Ring3).
Em seguida, a API correspondente salvará o número de chamada do sistema correspondente no registro eax (esta etapa é implementada por montagem embutida) e, em seguida, usará int 0x80 para acionar a interrupção (montagem embutida) e inserir a função de processamento de interrupção (a função é Completamente escrito em código assembly), desta vez entra no modo kernel (Ring0).
Na função de tratamento de interrupções, a chamada do sistema correspondente ao número de chamada do sistema é chamada. Nesta função, os dois registradores ds (registrador de segmento de dados) e es (registrador extra) serão configurados para apontar para o espaço do kernel . Desta forma, não podemos transferir dados do modo do usuário para o modo kernel (como open (const char * filename, int flag, ...), o endereço da string apontada pelo ponteiro do nome do arquivo está no espaço do usuário, Se você tomar o lugar correspondente no espaço do kernel, não existe tal string), o que devo fazer? O registro fs na função de tratamento de interrupção é definido para apontar para o espaço do usuário , então o problema está resolvido.
As operações correspondentes são realizadas na chamada do sistema, como abrir e gravar arquivos.
Após o processamento, ele retornará à função de processamento de interrupção, e o valor de retorno será armazenado no registrador eax.
Retornar da função de tratamento de interrupção para a API ainda salva o valor de retorno no registrador eax. Nesse momento, ele é restaurado do modo kernel para o modo do usuário.
Pegue o valor de eax na API e faça o julgamento correspondente para retornar um valor diferente para indicar a conclusão da operação.
Por que tantas chamadas de sistema podem ser chamadas usando a interrupção int 0x80?
No modo protegido, existem várias interrupções e a chamada do sistema está vinculada à interrupção 0x80. Quando uma chamada de sistema deve ser chamada, int 0x80 é disparado e a função de tratamento de interrupção sabe qual chamada de sistema deseja chamar por meio de eax. A razão para isso é que há muitas chamadas de sistema e o número de interrupção será insuficiente, portanto, um é usado para gerenciamento centralizado.
Existe uma tabela no sistema operacional, que é usada para armazenar os endereços de várias funções de chamada do sistema. Esta tabela é um array, portanto os endereços das diferentes funções podem ser acessados por subscritos. Portanto, um número de interrupção + vários números de chamada do sistema podem gerenciar várias chamadas do sistema.
O texto acima foi retirado de "Apresentando o Processo de Chamada de Sistema no Linux"
2. Três métodos de chamada de sistema
O seguinte apresenta três métodos de ocorrência de chamada de sistema no Linux.
2.1. Funções de biblioteca fornecidas pela glibc
glibc é uma biblioteca C padrão de código aberto usada no Linux.É a biblioteca libc lançada pelo GNU, ou seja, a biblioteca de tempo de execução. A glibc fornece aos programadores uma API (Interface de Programação de Aplicativo) rica. Além dos serviços do modo de usuário, como processamento de string e operações matemáticas, o mais importante é encapsular os serviços do sistema fornecidos pelo sistema operacional, ou seja, o encapsulamento das chamadas do sistema. Então, qual é a relação entre a API de chamada do sistema fornecida pela glibc e a chamada do sistema específica do kernel?
- Normalmente, cada chamada de sistema específica corresponde a pelo menos uma função de biblioteca encapsulada por glibc. Por exemplo, a chamada de sistema de arquivo aberto fornecida pelo sistema
sys_open
corresponde a umaopen
função em glibc ; - Em segundo lugar, glibc uma chamada API separada pode invocar vários sistemas, tais como a fornecida por glibc
printf
função vai chamar, tais comosys_open
,sys_mmap
,sys_write
,sys_close
como a chamada do sistema; - Além disso, várias APIs podem corresponder apenas à mesma chamada de sistema. Por exemplo , as funções implementadas em glibc, como ,,
malloc
e assim por diantecalloc
,free
são usadas para alocar e liberar memória, todas usando assys_brk
chamadas de sistema do kernel .
Por exemplo, usamos a chmod
função fornecida pela glibc para alterar o etc/passwd
atributo do arquivo para 444:
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
int main()
{
int rc = 0;
rc = chmod("/etc/passwd", 0444);
if (rc == -1)
fprintf(stderr, "chmod failed, errno = %d\n", errno);
else
printf("chmod success!\n");
return 0;
}
Em uso normal, o usuário para compilar , como saída:
o chmod falhou, errno = 1
A chamada do sistema acima retorna um valor de -1, indicando que a chamada do sistema falhou, um código de erro, no / usr / include / asm-generic / errno-base O arquivo .h tem a seguinte descrição de código de erro:
#define EPERM 1 / * Operação não permitida * /
Ou seja, não há permissão para realizar a operação. Não podemos modificar os atributos do arquivo / etc / passwd com permissões de usuário comuns. O resultado está correto.
2.2, use syscall para chamar diretamente
Há muitas vantagens em usar o método acima. Primeiro, você não precisa saber mais detalhes, como o número de chamada do sistema chmod. Você só precisa entender o protótipo da API fornecida pela glibc. Em segundo lugar, o método tem melhor portabilidade e você pode mudar facilmente O programa é transplantado para outras plataformas, ou a biblioteca glibc é substituída por outra biblioteca, o programa só precisa ser alterado algumas vezes.
Mas uma desvantagem é que se a glibc não encapsular uma chamada de sistema fornecida por um determinado kernel, não posso chamar a chamada de sistema por meio do método acima . Se eu adicionar uma chamada de sistema compilando o kernel sozinho, a glibc não poderá ter a API de encapsulamento de sua nova chamada de sistema. Neste momento, podemos usar a syscall
função fornecida pela glibc para chamar diretamente. A função é definida no unistd.h
arquivo de cabeçalho e o protótipo da função é o seguinte:
long int syscall (long int sysno, ...)
- sysno é o número da chamada do sistema e cada chamada do sistema possui um número de chamada do sistema exclusivo para identificá-la. Em
sys/syscall.h
há todo o sistema possível chama a definição de macro. - ... São os parâmetros de comprimento variável restantes, que são os parâmetros da chamada do sistema. Dependendo da chamada do sistema, pode levar de 0 a 5 parâmetros. Se exceder os parâmetros que uma chamada de sistema específica pode assumir, os parâmetros extras serão ignorar.
- Valor de retorno O valor de retorno desta função é o valor de retorno de uma chamada do sistema específica. Depois que a chamada do sistema for bem-sucedida, você pode converter o valor de retorno para um tipo específico. Se a chamada do sistema falhar, ele retorna -1 e o código de erro é armazenado
errno
nele.
Pegue também a modificação acima dos atributos do arquivo / etc / passwd como exemplo, desta vez use syscall para chamar diretamente:
...
//rc = chmod("/etc/passwd", 0444);
rc = syscall(SYS_chmod, "/etc/passwd", 0444);
...
Compile e execute sob usuários normais, e o resultado de saída é o mesmo que o exemplo acima.
2.3, preso pela instrução int
Se conhecermos todo o processo da chamada do sistema, devemos ser capazes de saber que o programa do modo de usuário int 0x80
entra no modo kernel por meio da instrução de interrupção suave (as sysenter
instruções são introduzidas no Intel Pentium II ), o parâmetro é passado pelo registrador e eax é o número de chamada do sistema. , Ebx, ecx, edx, esi e edi para passar até cinco parâmetros por sua vez, quando a chamada do sistema retorna, o valor de retorno é armazenado em eax.
Ainda tomando a modificação acima dos atributos do arquivo como exemplo, escreva a seção que chama a chamada do sistema como código de assembly embutido:
#include <stdio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <errno.h>
int main()
{
long rc;
char *file_name = "/etc/passwd";
unsigned short mode = 0444;
asm(
"int $0x80"
: "=a" (rc)
: "0" (SYS_chmod), "b" ((long)file_name), "c" ((long)mode)
);
if ((unsigned long)rc >= (unsigned long)-132) {
errno = -rc;
rc = -1;
}
if (rc == -1)
fprintf(stderr, "chmode failed, errno = %d\n", errno);
else
printf("success!\n");
return 0;
}
Se o valor de retorno armazenado no registrador eax (armazenado na variável rc) estiver entre -1 e -132, deve ser interpretado como um código de erro ( /usr/include/asm-generic/errno.h
o código de erro máximo definido no arquivo é 132). Neste momento, escreva o código de erro Em errno, defina o valor de retorno da chamada do sistema para -1; caso contrário, ele retorna o valor em eax.
O programa acima é compilado e executado em Linux de 32 bits com direitos de usuário comuns, e o resultado é o mesmo que os dois anteriores! , Em um ambiente de 64 bits, chmode falhou, errno = 22: O parâmetro é inválido.