8

Controlo de processos e sinais

 

Este capítulo aborda os processos. A criação e controlo de processos no Bash shell e pelo Linux API e sinais para processos e e seu tratamento:

 


Controlo simples

 

Cada processo tem um identificador único (pid - process id). O comando Unix ps permite ver os processos que estão a correr.  Normalmente, um programa requer um só processo para correr. Mas, a execução dum programa pode lançar e envolver vários processos que comunicam entre si. Dái o conceito duma tarefa ou “Job”. Existem processos especiais no sistema Unix. Por exemplo, o pid 0 é normalmente o scheduler process (ou swapper) que faz parte do núcleo (kernel). Este processo não tem qualquer programa em disco. Em windows aparece como o “system idle process” ver em baixo,

 

Exemplo:

noe> psAf

PID       TTY            S                    TIME                                                                        CMD

0          ??             R<             10:36.27                                                             [kernel idle]

1          ??               I              00:01.57                                                             /sbin/init –a

3          ??              IW             00:00.03                                                          /sbin/kloadsrv

21        ??               S              01:32.49                                                            /sbin/update

94        ??               I              00:10.96                                                      /usr/sbin/syslogd

350       ??               I              00:43.24                                sendmail:accepting connections

410       ??               I              00:29.29                                                         /usr/sbin/inetd

420       ??               S              00:01.53                                                       /usr/sbin/snmpd

445       ??               I              00:00.34                                                          /usr/sbin/cron

462       ??               I              00:00.01                                                             /usr/lbin/lpd

 

O pid 1 é o processo init que é chamado pelo núcleo no fim do processo de arranque (boot). O processo init é responsável pela inicialização e configuração (os ficheiros /etc/rc*) do Unix. Este processo nunca morre. Não é um processo do sistema, mas sim um processo do utilizador. No entanto corre com os privilégios do superutilizador. O programa em disco deste processo é o ficheiro /sbin/init. Este processo nunca morre. Este processo torna-se progenitor de qualquer processo orfão.

Actualmente num sistema como fedora-21 ou ubuntu podem ver em vez do processo init outro como o systemd (system daemon) que serve o mesmo prepósito.

 

 

 

 Figura:  O explorador de Processos de Windows paar ver os processos e oe seus ID’s

 

Alguns Artigos Interessantes:

·       TheWindows idle process https://blog.codinghorror.com/why-is-the-system-idle-process-hogging-all-the-resources/

·       Unix Swapper process history https://superuser.com/questions/377572/what-is-the-main-purpose-of-the-swapper-process-in-unix

JOBS

 

Em Linux existe o conceito da tarefa (job) que consiste em um ou mais processos (process). Cada processo contém uma ou mais linhas de execução (Thread)

 

·        Existe um conjunto de comandos para o controlo de tarefas e processos com os comandos do sistema operativo disponíveis por linha de comando no Bash Shell. Os comandos mais relevantes são

·        ps        listar processos. Um processo é identificado pelo PID (inteiro)

·        jobs      listar tarefas. Uma tarefa é identificada pelo seu “job number” (%inteiro)

·        kill        envie sinais para um processo ou tarefa (p.ex   kill -9 1234 ,  kill %3)

·        bg, fg  Executar tarefas de Background ou Foreground ou mover a sua execução dum plano

            para outro

·        &          Execução em background

 

·        Outros Para Investigar: top, uptime, nice, nohup, at, atq etc

 

·        O seu controlo e criação através das funções do API Linux  

·        fork, wait, exit e kill.

 

·        Para finalizar a última secção trate dos sinais (Signals) que nao é mais nada do que a comunicação de eventos aos processos e a seu tratamento seguinte.

 

     Também deverá ler apontamentos adicionais aqui  e  Process Control” do Linux Document Project.

    

Experimentar este comando (kill, kilall, fg %x, jobs etc, ps .)

 

Escrever e compilar o programa seguinte e depois executar e experimentar os comandos !

 

./ola 1;  ctrl c 

./ola 1 &

./ola 2 &

ps

jobs

fg %1

 

//ola.c

#include <stdio.h> <unistd.h>

int main (int argc, char *argv[])

{

  if (1==argc) return 0;

  while (1) {

      printf ("ola %s\n", argv[1]);

      sleep(3);

  }

  return 0;

}

 


Devolução de identificadores de processos

 

Há funções que devolvem os identificadores associados com um dado processo, nomeadamente:

 

#include <sys/types.h>

#include <unistd.h>

 

pid_t getpid(void);                                  Retorna: ID do processo invocador

pid_t getppid(void);                           Retorna: ID do progenitor do processo invocador

uid_t getuid(void);                                       Retorna: ID do utilizador real do processo invocador

uid_t geteuid(void);                               Retorna: ID do utilizador efectivo do processo invocador

gid_t getgid(void);                                           Retorna: ID do grupo real do processo invocador

gid_t getegid(void);                                   Retorna: ID do grupo efectivo do processo invocador

 

 

 

Criação dum novo processo

 

A única maneira de criar um novo processo é através da função fork() (com a excepção dos processos especiais como o swapper e o init).  Esta função fork() é invocada a partir dum processo já existente que é, pois, o processo progenitor. O novo processo gerado através da função fork() é chamado o processo progénito.

 

#include <sys/types.h>

#include <unistd.h>

 

pid_t fork(void);                                     

Retorna: 0 a partir do progénito, ID do progénito a partir do progenitor, -1 em caso de erro

 

 

Note-se que a função fork é chamada uma vez, mas devolve duas vezes: uma a partir do processo progénito, outra a partir do processo progenitor.

O programa seguinte mostra a utilização da função fork() com duas cópias do programa a correr simultaneamente (multitasking).

 

#include <sys/types.h>

#include <unistd.h>

#include <stdio.h>

 

main()

{

            int  pid;

            printf("Processo progenitor pid=%d\n", getpid());

            printf("Bifurcando o processo\n" );

            pid=fork();

            /* As instruções que seguem são executadas duas vezes:

               uma vez para o progenitor, outra vez para o progénito*/

            printf( "O processo progenitor id %d e o seu progenito %d\n", getpid(), pid);

            system("date");

}

 

Exemplo 8.1.
Exercício 8.1:

·        Escreva e execute o programa anterior. Qual é o processo que executa primeiro, o progenitor ou o progénito?

o   Normalmente, nunca se sabe qual é o processo que começa primeiro, se é o progenitor ou o progénito, pois isso depende do algoritmo de escalonamento da CPU.

·        A seguir modifique o programa e introduzir uma estrutura de controlo, como se segue, para gerar o output indicado em baixo.

 

int pid;                                     /* identificador do processo*/

 

if ( (pid=fork()) < 0) {

            printf("Cannot fork\n");

            exit(0);

}

if (pid == 0)

    printf("Filho com ID %d\n",getpid());

else

   /* pid do processo progenitor é pid do progénito …*/

 

system("date");

 

Ouput Esperado

Pai com processo ID 12345 e filho com ID 123456

Filho com ID 123456

Thu Apr 15 16:45:20 WEST 2008

Thu Apr 15 16:45:20 WEST 2008

 

A função de Sincronização wait(

 

Existe a necessidade de sincronizar as operações dos processos progenitor e progénito. Uma das funções de sincronização disponível é a função wait(). Normalmente esta função é usado pelo processo progenitor esperar a terminação de um dos seus progénitos.

 

·        int wait(int *status); Força o progenitor a esperar pela paragem ou terminação do progénito. Esta função devolve o pid do progénito ou –1 no caso de erro. O estado de exit do progénito é devolvido para a status.

§  void exit(int status); Termina o processo que chama esta função e devolve o valor de status.

§  A função waitpid(pid_t pid, int *wstatus, int options) espera a terminação dum progénito especifico.

 

      A função wait() é as vezes usado em conjunto com um ciclo caso que uma

      chamada a função wait não seja bloquenate

             while ( wait(&status_filho) != pid_filho ) ;

 

Um exemplo da necessidade de sincronização é quando a entrada e saída estandardizadas do processo progenitor são redireccionadas, então o mesmo acontece com entrada e saída estandardizadas do processo progénito.

Além disso, todos os descritores dos ficheiros abertos pelo progenitor serão duplicados pelo progénito e também os conteúdos de quaisquer buffers das bibliotecas standard. Consequentemente, pode haver problemas de sincronização na entrada e na saída e output difícil de explicar.

 


Considere o seguinte exemplo

 

Exemplo 8.1b:

 

main()

{

   printf ("Bom Dia  ");

   if ( 0 == fork() )

       printf("disse o Filho ");

  else

       printf("ao Pai.\n");

}

 

O Output do programa Exemplo 8.1b é

Bom Dia ao Pai.

Bom Dia disse o Filho

 

Como é que podemos obter sempre o output

Bom Dia disse o filho ao Pai.

 

Exercício 8.1b :  Escreva um programa para produzir sempre o output desejado.

                        Solução: utilizar a função fflush() e wait() para sincronização

 

Exercício 8.2:

 

Escreva um programa que inicialize uma variável int v=5 e depois abre um ficheiro “res.txt” em modo escrita. Antes da bifurcação, escreva para o ficheiro res.txt o valor da variável v.

Depois da bifurcação, altere o seu valor para 10 no progenitor (pai) e para 15 no progénito (filho).

Depois, escreva estes valores conjuntamente com os valores dos PID’s do progenitor e do progénito para este mesmo ficheiro.  Os valores do progénito (filho) devem ser escritos em primeiro lugar – precisará novamente da função wait !

 

Ouput Esperado: cat res.txt

 

V =  5  Pai com processo ID 12345

V = 15  filho Processo com ID 54321

V = 10  Pai Processo com ID 12345

 

 

 

Notas:  Situações de utilização da função fork():

 

·        Quando um processo pretende replicar-se. Por exemplo, um servidor quando recebe um pedido dum cliente faz um fork para que o progénito trate do pedido enquanto o servidor continua a correr e à espera doutros pedidos.

·        Quando um processo pretende executar outro programa. Por exemplo, dentro dum programa shell. Neste caso, o progénito faz um exec() logo a seguir ao fork(). Nalguns sistemas operativos, estas duas chamadas (fork e exec) são combinadas numa única operação chamada spawn(), por exemplo em Windows.

 


Funções exec

 

Um processo pode carregar um novo programa usando a chamada ao sistema exec(). Quando um processo chama uma das funções da família exec, o programa que começa a executar fá-lo substituindo o programa original pelo novo. O pid não muda com um exec porque não há a criação dum processo novo. Basicamente, exec faz a substituição do contexto do processo atual, ou seja, texto, dados, heap, stack, etc.) pelo novo programa, embora algumas características sejam herdadas (por exemplo, pid, ppid, user id, root directory, etc.).

 

Existem seis funções exec, mas aqui só vamos referir uma delas:

§  int execl(const char *pathname, const char *arg0, …);

§  Significa execute and leave, i.e. executa e termina o comando indicado pelo pathname com as opções indicadas pelos restantes argumentos da função.

§  Para mais informações sobre a família exec ver man 2 exec

 

Exemplo 8.2:

 

main()

{

            printf("processo main %d\n", getpid());

            printf("Ficheiros na directoria:\n" );

            execl( "/bin/ls", "ls", "-l", 0 );

}

 

 

Consultar o man page para a função execvp e execl

 

Exercicios (Exame do 2017, Freq 2015)

 

Especificar qual  é o output dos seguinte programas?  O output é determinístico? Justifique! Deverá mostrar o trace  do funcionamento do programa. A seguir deve implementar e executar o programa.

 


char *args[2] = {"date", NULL};

 int status, pid, x = 9;

 pid = fork();

 if (0 == pid) {

     x++;

     pid = fork();

     if (0 == pid)

         execvp(*args, args);

     else

         wait(&status);

 }

 else

     x--;

 printf("x=%d\n", x);

 

 

  int pid, x = 2;

  if  ( (pid = fork()) == 0){

     fork();

     fork();

     x--;

  }

  else{

     execl("/bin/date", "date", NULL);

     x = x + 2;

  }

  printf("x=%d\n", x);

 

 

 


 


Execução de comandos Unix a partir dum programa em linguagem C

 

Podemos executar comandos Unix a partir dum programa em C através do uso da função system() que se encontra declarada no ficheiro <stdlib.h>.

§  int system(char *string); string contém o nome do comando.

 

Exemplo 8.3:

 

main()

{

            int res;

            printf("Ficheiros na directoria: " );

            res = system("ls -l" );

            printf( "%s\n", (0==res)? "sucesso": "insucesso");

           

            printf("Ficheiro VidaNova.txt na directoria: ");

            res = system( "ls –l VidaNova.txt" );

            printf("%s\n", (0==res)? "sucesso": "insucesso");

}

 

Nota: a função system() é uma chamada ao sistema composta por outras três chamadas ao sistema: execl(), wait() e fork() (veja-se o ficheiro <unistd.h>).

 

 

 

 

Um Shell Simples (soshell)

 

Exemplo 8.4 Um Interpretador de Comandos

 

Este exemplo serve de base à programação duma shell limitada.

 

soshell ­­ - example of a fork in a program used as a simple shell.

   The program asks for Unix commands to be typed and are read into a string.

   The string is then “parsed” by locating blanks, etc.

   Each command and corresponding arguments are put in a args array.

 execvp is called to execute these commands in child process spawned by fork().

 

 

Os ficheiros deste projecto são : 

o   shell.h – ficheiro com os protótipos e ficheiros de inclusão do programa.

o   main.c – ficheiro com o programa principal.

o   parse.c – ficheiro com a função que particiona o comando Unix.

o   execute.c – ficheiro com a funçao que cria um processo progénito e executa um programa.

o   Makefile – ficheiro de gerencia de compilação do projecto.

o   soshell – ficheiro executável criado pelo Makefile.


 

#A Simple Ilustrative Makefile for soshell

#

CC=cc

FLAGS=-c -Wall

LIBS=-lm

OBS=main.o execute.o parse.o

 

all :  soshell

 

main.o : shell.h main.c

            $(CC) $(FLAGS) main.c

execute.o : shell.h execute.c

            $(CC) $(FLAGS) execute.c

parse.o : shell.h parse.c

            $(CC) $(FLAGS) parse.c

soshell : $(OBS)

            $(CC)  -o soshell  $(OBS) $(LIBS)

clean limpar:

            rm -f soshell *.o *~

 

 

 

/*

    shell.h – ficheiros de inclusao e prototipos.

*/

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <pthread.h>

 

void parse(char *buf, char **args);

void execute(char **args);

 

int builtin (char **args);

 

/* constantes que podem tornar uteis*/

#define BG 0

#define FG 1 

 

 


#include "shell.h"

/*

   main.c – cria um processo progénito e executa um programa

*/

char prompt[100];

int main()

{

            char buf[1024];     /* um comando */

            char *args[64];    /* com um maximo de 64 argumentos */

            strcpy (prompt, "SOSHELL: Introduza um comando: prompt>"); /*prompt inicial*/

           

            while (1)

            {

                  printf( "%s", prompt );

                  if ( gets(buf) == NULL)

                  {

                      printf( "\n" );

                      exit(0);

                  }

 

            parse(buf,args);  /* particiona a string em argumentos */

 

            if ( !builtin(args) )

               execute(args);    /* executa o commando se não for uma funcionalidade embutida */

            }

            return 0;

}

 

int builtin( char *args[] ) /* verifiicar se o comando é embutido no shell */

{

   if (strcmp (args[0], "sair") == 0)

    {

      exit (0);

      return 1;    /* funcionalidade embutida */

    }

   if (strncmp (args[0], "42", 2) == 0)

    {

      printf("42 is the answer to life the universe and everything\n");

      return 1;    /* funcionalidade embutida*/

    }

 

   return (0) ;   ;    /* indicar que vamos continuar para a função execute*/

}


#include "shell.h"

/*

    parse.c – particiona o comando Unix (armazenado em buf) em argumentos

*/

 

void parse (char *buf, char **args)

{

         while (*buf != '\0')

         {

         /* strip whitespace. Usa um NULL para indicar que o argumento anterior e’ o ultimo */

             while ((*buf == ' ' ) || (*buf == '\t'))

                     *buf++ = '\0';

 

             *args++ = buf;          /* salvaguarda argumento */

 

              while ((*buf != NULL) && (*buf != ' ') && (*buf != '\t'))   /* salta sobre o argumento*/

                        buf++;

         }

        *args = NULL;              /* o ultimo argumento e’ NULL */

}

 

Nota: Pode-se usar a função isspace() para detectar espaço em branco – o chamado whitespace

 

#include "shell.h"

/*

   execute.c – cria um processo progénito e executa um programa

*/

 

void execute(char **args)

{

         int pid, status;

 

         if ((pid = fork()) < 0)                                                 /* cria um processo progenito */

         {

            perror(“fork”);   /* NOTE: perror() produz uma pequema mensagem de erro para o stream */

            exit(1);             /* estandardizado de erros que descreve o ultimo erro encontrado */

                                        /* durante uma chamada ao sistema ou funcao duma biblioteca */

         }

         if (pid == 0)

         {

            execvp(*args, args);  /* NOTE: as versoes execv() e execvp() de execl() sao uteis quando */

            perror(*args);         /* o numero de argumentos nao e’ conhecido. Os argumentos de  */

            exit(1);                 /* execv() e execvp() sao o nome do ficheiro a ser executado e um */

         }             /* vector de strings que contem os argumentos. */                                                                                                            

                            /* O ultimo argumento do string tem de ser seguido por um ponteiro 0. /*

 

         /* Execução em foreground */

         while (wait(&status) != pid)                                   /* O progenitor executa a espera */

             /* ciclio vazio repare que o ; é importante */ ;

}

 

 

 

 

Exercícios  : 

 

1 Implemente e Estude o Exemplo 

 

O código está num servidor seguro de subversion que poderá ser acedido usando um browser com o endereço seguinte : https://spocs.it.ubi.pt:8443/svn/crocker/sisops/

 

Utilizando o programa “svn” (linha do comando) poderá usar o seguinte comando para obter os ficheiros:

svn --username aluno checkout https://spocs.it.ubi.pt:8443/svn/crocker/sisops/trunk/soshell

 

Verifique o código do main() para ver qualquer alteração desta ficha e o código do svn

(a função 42 não está implementada e o main utilize agora fgets() em vez do gets() )

 

2 Aumente a funcionalidade do seu Shell

 

(i)     implemente a possibilidade de mudar dinamicamente (durante runtime) o prompt usando o comando prompt> PS1=String   : onde String será o novo prompt

 

(ii)    implemente um comando embutido chamado “quemsoueu” que mostre detalhes sobre a identificação do usuário. Baste chamar a função system(“id”) na função “builtin”.

 

(iii)   implemente o comando embutido “socp” que permite o shell copiar um ficheiro através do comando prompt> socpy destino fonte

 

Detalhes – escreva uma função void socp(char *fonte, char *destino) num ficheiro socp.c e chame-a dentro da função builtin do programa principal. Esta função deverá utilizar a função solowlevelcopy() das fichas anteriores. Também terá que modificar o Makefile.

 

(iv)   implemente controlo de processos com a possibilidade de execução em foreground ou em background (adicionado o símbolo & no fim do comando)

 

(v)    implemente redireccionamento do stdin e stdout (<) e (>)


Sinais (signals)

 

 

 

O que é um sinal?

 

Sinal é um evento que pode ocorrer enquanto um programa está a correr.

 

Podemos distinguir dois tipos de sinais: interrupções e excepções.

-      As interrupções são geralmente geradas pelo programador, como um CTRL-C, por exemplo.

-      As excepções são erros que ocorrem durante a execução de programas, como um “overflow” ou um ponteiro “out-of-range”, por exemplo.

 

A ocorrência dum sinal despoleta uma acção que lhe foi atribuída previamente, por defeito. Por exemplo, a acção associada a CTRL-C é terminar o programa.

 

 

 

Os tipos de sinais mais comuns em UNIX são:

 

Número

Sinal

Acção por defeito

Significado

 

 

 

 

1

SIGHUP

Exit

Perdeu a ligação com o terminal (hangup)

2

SIGINT

Exit

Interrupção (CTRL-C) na Shell

3

SIGQUIT

Core Dumped

Quit

4

SIGKILL

Core Dumped

Sinal ilegal

5

SIGTRAP

Core Dumped

Interrupção de trace (usado por debbugers como o dbx)

8

SIGFTP

Core Dumped

Floating Pointing Exception

9

SIGKILL

Exit

Terminate execution (não pode ser ignorado)

10

SIGBUS

Core Dumped

Erro no bus (violação da protecção de memória)

11

SIGSEGV

Core Dumped

Violação da segmentação de memória

14

SIGALARM

Exit

Alarme do relógio – time out

15

SIGTERM

Exit

Termina a execução (não pode ser ignorado)

17

SIGSTP

Stop Job

Stop signal (do processo)

18

SIGTSTP

Stop Job

Stop signal (do teclado)

Figura 8.5: Sinais em Unix.

 

 

Na ocorrência dum “core dump”, o conteúdo de um programa (código, variáveis e estado) são colocados num ficheiro chamado core antes do programa terminar.

 

 

Todos estes sinais estão definidos no ficheiro signal.h


 

Rotinas de Gestão (Handler Routines)

 

As “handler routines” são chamadas quando ocorrem erros. Um sinal faz com que uma “handler routine” seja imediatamente executado. Quando uma “handler routine” termina, a execução do programa é retomada onde o erro tinha ocorrido.

 

 

Utilização de Sinais

 

A função signal especifica a “handler routine” a ser executada quando um certo sinal ocorre. Esta função permite também re-definir a acção ou a “handler routine” quando um sinal ocorre. Além do mais, esta função pode ser usada para ignorar sinais. A sua sintaxe é a seguinte:

 

#include <signal.h>

                   

void (*signal(int signo, void (*func)(int)))(int);

 

Retorna: ponteiro para o “signal handler” anterior

 

O argumento signo é o nome dum dos sinais listados na Figura 8.5. O valor de func é um dos seguintes:

§  a constante SIG_IGN;

§  a constante SIG_DFL;

§  o endereço duma função (ou “handler routine”) que é chamada quando o sinal ocorre.

 

Se a constante SIG_IGN é especificada na função signal, isso quer dizer que estamos a dizer ao sistema para ignorar o sinal. (Há, no entanto, dois sinais, SIGKILL e SIGSTOP, que não podem ser ignorados.). A constante SIG_DFL é usada quando se pretende que a acção associada com o sinal seja a acção por defeito.

 

 

Hibernação dum processo durante um período de tempo (segundos)

 

A função sleep pára a execução do programa durante um determinado número de segundos. A sua sintaxe é a seguinte:

 

#include <unistd.h>

 

unsigned int sleep(unsigned int seconds);

 

Retorna: 0 se o período se esgotou; tempo que falta para esgotar o período seconds

 

 

Esta chamada usa o alarme de interrupção SIGALARM para atrasar o programa. Note-se que ao fim do período seconds não há a garantia que o programa retome imediatamente a sua execução, pois isso depende da política de escalonamento da fila dos processos prontos-a-correr (“ready queue”).

 

Existe uma função semelhante à função sleep, designada por usleep. Funciona da mesma forma que o sleep, excepto que o período de hibernação ou de espera é em microsegundos e não em segundos. A sua sintaxe é a seguinte:

 

 

#include <unistd.h>

 

void usleep(unsigned long usec);

Retorna: nada.

 

Ignorar e restaurar acções por defeito   O Exemplo 9.2 mostra como ignorar um sinal e restaurar a acção por defeito que lhe está associada. Usa ainda a função sleep, fazendo com que o processo espere um determinado período de tempo em segundos. A seguir restaure a acçao por defeito (SIG_DFL)

 

Exemplo 8.6:

 

#include <stdio.h> <signal.h>

main()

{

   int i;

 

   signal(SIGINT, SIG_IGN);  //vamos ignorar o sinal SIGINT

 

   for(i=0; i<15; i++) {

      printf("Nao pode usar CTRL-C para terminar. Experimente !\n");

      sleep(1);

   }

   signal(SIGINT, SIG_DFL);

   printf("\nAgora pode Primar CTRL-C para terminar...\n");

   sleep(10);

}

 

Ignorando o sinal SIGINT (CTRL-C), o programa não pode ser interrompido durante 15 segundos. Depois dos 15 segundos, é restaurado a “handler routine” por defeito e o CTRL-C já pode interromper o programa.

 

Criar “handler routines    As “handler routines” são funções que têm as seguintes restrições:

 

1.    Não podem ter parâmetros;

2.    Têm que ser declaradas no código antes de serem referenciadas.

 

O Exemplo 9.3 mostra como se pode resolver o problema acidental do utilizador premir CTRL-C, o que provoca a terminação do programa. A “handler routineconfirma associada ao sinal SIGINT (CTRL-C) pede ao utilizador para confirmar ou não o fim do programa.

 

Exemplo 8.7:

 

#include <stdio.h> <stdlib.h> <signal.h>

main() {

   int i;

   signal(SIGINT, confirma);

 

   for (i=1; i<20; i++) {

       printf("Estamos no ciclo numero press ctrl-c%d.\n", i);

       sleep(1);

   }

}

int confirma() {

   char sim_ou_nao, enter;

 

   printf("\nQuer mesmo terminar? (S/N)");

   scanf("%c%c", &sim_ou_nao, &enter);

  

   printf("%c\n", sim_ou_nao);

   if ((sim_ou_nao == 'S') || (sim_ou_nao == 's')) exit();

}

 

 


 

Abate ou terminação dum processo

 

A função kill permite que um processo possa enviar um sinal de abate ou terminação a outro processo. Enviando um sinal SIGTERM ou SIGKILL para outro processo faz com que este termine, desde que:

 

1.    Os processos pertençam ao mesmo utilizador, ou o processo emissor de signal pertença ao super-utilizador root.

2.    O processo saiba o identificador (pid) do processo a matar.

 

A sintaxe da função kill é a seguinte:

 

#include <sys/types.h>

#include <signal.h>

 

int kill(pid_t pid, int sig);

Retorna: 0 se OK; -1 em caso de erro

 

Normalmente, o sinal sig  é SIGTERM (pedido para terminar) ou SIGKILL (força o processo a terminar).

 

 

Programação de alarmes para emissão de sinais

 

Todas as versões UNIX têm um relógio de alarmes. O relógio pode ser programado de tal modo que uma determinada “handler routine” pode ser executada quando determinada hora/instante chega.

O sinal SIGALARM é enviado quando um alarme de relógio é gerado, fazendo com que uma rotina “handler” seja executada.

A programação temporal dum alarme é feita com a função alarm, a qual tem a seguinte sintaxe:

 

#include <unistd.h>

 

unsigned int alarm(unsigned int seconds);

 

 

Um processo não pára a sua execução quando faz uma chamada de alarme ao sistema; ele retorna imediatamente da chamada sem esperar e continua a sua execução.

 

Nota: a função sleep faz o reset do relógio de alarmes. Assim, é muito importante não usar a função sleep se também se pretende usar um alarme.

 

 

 


Exemplo 8.8:

 

O programa seguinte usa a função sleep e a função alarm sem quaisquer problemas. Tudo funciona correctamente porque o processo filho usa a função sleep, enquanto o processo pai usa a função alarm, separadamente.

O processo filho corre um ciclo infinito.

O processo pai envia um alarme para parar cinco segundos e espera que o processo filho termine. Depois dos cinco segundos, e se o processo filho ainda não terminou, o pai mata o filho com a função kill.

 

#include <signal.h>

#include <sys/wait.h>

 

int pid;

 

int myalarm()

{

   kill(pid, SIGKILL);

   printf("Adeus filho!\n");

}

 

main()

{

   union wait status;

 

   pid = fork();

   if (-1 == pid)

   {

       perror("Erro no fork.");

       exit();

   }

 

   if (0 == pid)

      for(;;)

         {

         printf("Sou um processo filho - louco e livre!!\n");

         sleep(1);

         }

   else

   {

      signal(SIGALARM, myalarm);

      alarm(5);

      wait(&status);

   }

}