2009-05-25

Sinais, parte I

Um pequeno aparte

Bem, eu não gosto muito deste guião. A página do manual da system call signal que é apresentada no guião, começa assim:

The behavior of signal() varies across Unix versions, and has also varied historically across different versions of Linux. Avoid its use: use sigaction(2) instead.

Depois de ler isto é óbvio porque é que eu prefiro sigaction. Mas a sigaction é ligeiramente mais complicada.

Ignorando a minha opinião negativa quanto a este guião, vou resolvê-lo com recurso a signal, porque aparentemente é o que os professores esperam.

CTRL-C

Espero que toda a gente saiba o que faz o CTRL-C. Se por acaso alguém não souber, aqui fica: CTRL-C serve para terminar o processo actual. Experimentem. Corram a shell do exercício anterior e façam CTRL-C.

Mas não basta saber isto para perceber o que temos de fazer. O que é que acontece por detrás das cortinas quando premimos CTRL-C?

Sinais

Em UNIX os processos podem receber vários sinais, e responder adequamente a cada um. Cada sinal tem um significado associado.

De todos os sinais, o mais conhecido é provavelmente o KILL, que serve para terminar um processo. O sinal TERM (de TERMinate) também termina um processo. A diferença entre o TERM e o KILL é que os processos podem responder ao TERM, mas não ao KILL. Podem ver o TERM como um pedido educado para o processo “limpar a casa e sair”, enquanto que o KILL é como dar um tiro no processo, terminando-o inapelavelmente.

Para enviar um sinal a um processo podemos usar o comando kill. Apesar do nome, kill serve para enviar qualquer sinal. Por omissão é enviado o sinal TERM. Alguns de vós já devem ter usado este comando, provavelmente numa destas formas:

$ kill -KILL pid
$ kill –9 pid

Mas como é que o kill envia sinais? Usando a system call kill, que mais uma vez, apesar do nome, serve para enviar qualquer sinal.

Podem ver uma lista de sinais (e os respectivos códigos) correndo este comando:

$ kill –L

Por detrás das cortinas

Quando premimos CTRL-C o que realmente se passa é o envio do sinal INT ao processo actual.

Se consultarem a página do manual signal(7):

$ man 7 signal

… na secção Standard Signals podem ver uma breve descrição dos sinais existentes. Aí podem ver que a acção por omissão do SIGINT é terminar o processo. É por isso que o processo termina quando premimos CTRL-C.

Imunidade

Então como é que vamos tornar a shell imune ao CTRL-C? Vamos simplesmente ignorar o sinal SIGINT.

Na nossa função main vamos usar a system call signal para definir o signal handler para SIGINT como SIG_IGN, que é uma constante pré-definida que faz com que o sinal seja ignorado:

#include <signal.h>
int main(int argc, char** argv)
{

// No início:
signal(SIGINT, SIG_IGN);

// ... Outras cenas
}

E já está.

Ooops

Mas agora surge um problema… Experimentem correr este comando na vossa shell:

 > yes

O CTRL-C não funciona! Não pode ser. Só queremos que a shell fique imune e não todos os processos que esta lança! Temos de mudar isto. Ao invés de ignorar o sinal, vamos “reenviá-lo” ao processo actual.

Ah, se ainda estiverem “presos” com o yes, façam CTRL-Z e corram este comando:

$ kill %1

De volta ao problema. Antes de mais é necessário saber o PID do processo actual. Simples. Enquanto esperamos pelo filho com waitpid, guardámos o seu PID numa variável global:

pid_t actual_pid = -1;

// Outras cenas...
actual_pid = pid;
waitpid(pid, &status, 0);
actual_pid = -1;
// Mais cenas...

Vamos agora criar o nosso signal handler que vai tratar de passar o sinal para o filho. Um signal handler é um função que recebe um inteiro (o sinal) e não devolve nada:

void propagate(int signum)
{
if(actual_pid != -1)
kill(actual_pid, signum);
}

Agora, em vez de registar SIG_IGN, registamos esta função:

signal(SIGINT, propagate);

Agora sim, podemos terminar o yes com CTRL-C, mas não a nossa shell.

E aqui está o código completo.

4 commentários:

Anónimo disse...

Porque não puseste a possibilidade de por processos em brackground e foreground ?

Martinho Fernandes disse...

Ainda não fiz essa parte :) Tou a tratar disso.

N. disse...

Antes de mais, parabéns pelo excelente blog que aqui tens.

Tenho seguido os teus posts desde o inicio e esclarecido algumas dúvidas que eu tinha em relação aos guiões propostos. Por vezes as explicações do prof. ainda complicam mais do que deviam ajudar.

Gostaria de saber se tens e podes disponibilizar código dos restantes guiões. Não devo precisar de muitas explicações, por vezes o código explica-se a si mesmo. Eu fui fazendo todos os guiões nas aulas, mas ficava mais esclarecido em cada um dos temas se pudesse analisar outros pontos de vista.

Muito obrigado.

Martinho Fernandes disse...

@N: Não se se reparaste (e duvido que ainda tenha sido a tempo), mas ainda fiz upload do código dos guiões todos até aos pipes. Pelo que me disseram não se passou disso nas aulas práticas.

Enviar um comentário