Estou lendo APUE e o capítulo de Chamadas de Sistema Interrompidas me confunde.
Eu gostaria de escrever minha compreensão com base no livro, por favor, corrija-me.
Uma característica dos sistemas UNIX anteriores era que, se um processo captava um sinal enquanto o processo estava bloqueado em uma chamada de sistema "lenta", a chamada de sistema era interrompida. A chamada do sistema retornou um erro e
errno
foi definida comoEINTR
. Isso foi feito sob a suposição de que, como ocorreu um sinal e o processo o capturou, há uma boa chance de que algo tenha acontecido que deva ativar a chamada de sistema bloqueada.Então está dizendo que os sistemas UNIX anteriores têm um recurso: se meu programa usa uma chamada de sistema, ela seria interrompida/parada, se a qualquer momento o programa pegasse um sinal. (O manipulador padrão também conta como uma captura?)
Por exemplo, se eu tiver uma
read
chamada de sistema, que lê 10 GB de dados, quando estiver lendo, eu envio qualquer um dos sinais (por exemplokill -SIGUSR1 pid
), entãoread
falharia e retornaria.
Para evitar que os aplicativos tenham que lidar com chamadas de sistema interrompidas, o 4.2BSD introduziu o reinício automático de certas chamadas de sistema interrompidas. As chamadas do sistema que foram reiniciadas automaticamente são
ioctl
,read
,readv
,write
,writev
,wait
ewaitpid
. Como mencionamos, as cinco primeiras dessas funções são interrompidas por um sinal somente se estiverem operando em um dispositivo lento;wait
ewaitpid
são sempre interrompidos quando um sinal é captado. Como isso causou um problema para alguns aplicativos que não queriam que a operação fosse reiniciada se ela fosse interrompida, o 4.3BSD permitiu que o processo desabilitasse esse recurso por sinal.
Portanto, antes que a reinicialização automática fosse introduzida, eu tive que lidar sozinho com a chamada do sistema interrompida. Eu preciso escrever código como:
O problema com chamadas de sistema interrompidas é que agora temos que tratar o retorno de erro explicitamente. A sequência de código típica (assumindo uma operação de leitura e assumindo que queremos reiniciar a leitura mesmo que ela seja interrompida) seria:
again:
if ((n = read(fd, buf, BUFFSIZE)) < 0) {
if (errno == EINTR)
goto again; /* just an interrupted system call */
/* handle other errors */
}
Mas hoje em dia não preciso escrever esse tipo de código, por causa do mecanismo de reinicialização automática .
Então, se meu entendimento estiver correto, o que / por que devo me preocupar com a chamada do sistema interrompida agora ..? Parece que o sistema/SO lida com isso automaticamente.
A interrupção de uma chamada de sistema por um manipulador de sinal ocorre apenas no caso de várias chamadas de sistema de bloqueio e acontece quando a chamada de sistema é interrompida por um manipulador de sinal que foi explicitamente estabelecido pelo programador.
Além disso, no caso de uma chamada de sistema de bloqueio ser interrompida por um manipulador de sinal, o reinício automático de chamada de sistema é um recurso opcional . Você opta por reiniciar automaticamente as chamadas do sistema especificando o
SA_RESTART
sinalizador ao estabelecer o manipulador de sinal. Conforme declarado (por exemplo) na página de manual do Linux signal(7) :Conforme sugerido pela última frase citada acima, mesmo quando você opta por usar esse recurso, ele não funciona para todas as chamadas do sistema, e o conjunto de chamadas do sistema para o qual funciona varia entre as implementações do UNIX. A página de manual do Linux
signal(7)
observa várias chamadas de sistema que são reiniciadas automaticamente ao usar oSA_RESTART
sinalizador, mas também observa várias chamadas de sistema que nunca são reiniciadas, mesmo se você especificar esse sinalizador ao estabelecer um manipulador, incluindo:Para essas chamadas de sistema, é essencial a reinicialização manual usando um loop da forma descrita em APUE, algo como:
[Eu não li essa coisa de APUE, mas as coisas que você está citando não parecem muito boas]
Não qualquer chamada de sistema. Apenas algumas chamadas do sistema podem ser interrompidas.
Não.
Seu read() de 10 GB só retornará com EINTR se tiver sido interrompido antes de poder ler até mesmo um único byte ; caso contrário, retornará a quantidade de dados que já havia lido (short read = sucesso, errno não relevante).
[isso não foi explicado no dupe vinculado]
Porque você pode querer fazer algo ao receber um sinal e não pode fazer muito de um manipulador de sinal; qualquer coisa usando malloc() ou stdio (mesmo printf()) está fora de questão. Então você tem que lidar com as interrupções no loop principal do programa, e para poder fazer isso, você deve quebrar de alguma forma um read() de bloqueio (um read() pode bloquear mesmo depois que um poll() retornar um fd como pronto para leitura).
[isso também foi explicado no dupe vinculado]