Archivo

Entradas Etiquetadas ‘señales’

Conociendo el proceso que me ha enviado una señal (signal)

Jueves, 24 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Hace tiempo hablábamos de capturar señales, aunque en ocasiones, es necesario saber quién me envía esa señal, si por ejemplo nos envían un SIGINT o SIGTERM, tal vez queremos saber qué proceso nos quiere muerto y qué usuario lo ha invocado. O tal vez estamos esperando una señal de control (SIGUSR1, por ejemplo) por parte de un proceso cliente específico.

El problema es que por nuestro modo actual de direccionar señales (con signal(señal, funcion)) sólo comunicamos el número de señal que se ha recibido, sin más información.

signals_from

Para capturar esta información necesitamos utilizar sigaction para establecer la función a la que llamaremos cuando llegue la señal, para utilizarla podemos utilizar el siguiente código:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void recibe_signal(int signum, siginfo_t *siginfo, void *context)
{
  /* Mostramos toda la información necesaria */
  printf ("[%d] Recibida %d (%s) de %d (UID: %d) ¿Error? %d \n",
      getpid(),
      signum,
      strsignal(signum),
      siginfo->si_pid,
      siginfo->si_uid,
      siginfo->si_errno);
}


int main(int argc, char *argv[])
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = recibe_signal;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    sigaction(SIGUSR1, &sact, NULL);

    /* Bucle infinito, para cerrar el proceso, utilizar kill -9 PID */
    while (1)
      pause;

    return 0;
}

Como vemos sigaction tiene 3 parámetros:

  • La señal que queremos capturar
  • Un registro de tipo struct sigaction
  • La acción que se ejecutaba antes, por si queremos guardarla para restaurarla más tarde

Dentro del registro, tendremos como valores importantes (struct sigaction act):

  • sact.sa_sigaction : que especifica la función que la llamaremos, esta función será del tipo void funcion(int, siginfo_t*, void*), donde el primer int recibirá la señal que nos han enviado (tal y como hacía la función que asignábamos con signal), el segundo parámetro será información asociada a la señal, y la tercera el contexto de ejecución.
  • sact.sa_handler : será la función a la que se llama, es la misma que cuando hacemos signal(señal, funcion)
  • sact.sa_flags : Es un conjunto de flags, si especificamos SA_SIGINFO, estaremos dando paso a la función especificada por sa_sigaction, si no utilizaremos sa_handler. Aunque esta variable vale para más cosas, para esta práctica nos vale con saber esto. (man sigaction para más info)
  • sact.sa_mask
  • : Nos proporciona una máscara de las señales que serán bloquedas, podemos utilizar sigfillset() para rellenar toda la máscara automáticamente. Si queremos que unas se bloqueen y otras no podemos utilizar sigaddset() y sigdelset()

Ahora bien, la función que se llamará cuando venga una señal tendrá el prototipo que hemos visto antes con tres parámetros: la señal recibida, la información de la señal con un puntero a siginfo_t y el contexto de ejecución, que se pasará con un puntero a void, que por ahora no utilizaremos. La estructura que controla la información de la señal entre otros muchos datos nos facilitará lo siguiente:

  • si_pid : PID que nos envía la señal (y esta acción da título al post)
  • si_uid : Usuario que ejecuta el proceso que nos manda la señal
  • si_errno : Algún error que haya causado la señal
  • … para más información, man sigaction

Para preparar la sentencia tendremos que escribir varias líneas definiendo los valores del registro así como la llamada a la función sigaction(), por lo tanto podremos crear una función que tenga los parámetros más comunes a la hora de definir este tipo de acciones, así lo llamamos como a signal():

1
2
3
4
5
6
7
8
9
10
11
12
int sigact(int signum, void funcion(int, siginfo_t *, void *))
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = funcion;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    return sigaction(signum, &sact, NULL);
}

En esta función es importante poner bien el prototipo de la función como parámetro, eso lo comenté en un post anterior sobre callbacks

Ahora, vamos a dejar el código más bonito, este programa implementará el control de errores y nos permitirá salir cuando recibamos 5 SIGINT (pulsemos 5 veces control+C):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void error(char *cadena);
int sigact(int signum, void funcion(int, siginfo_t *, void *));
void llegasigint();
void recibe_signal(int signum, siginfo_t *siginfo, void *context);

int main(int argc, char *argv[])
{
  if (sigact(SIGUSR1, recibe_signal)==-1)
    error("No puedo establecer SIGUSR1");

  if (sigact(SIGINT, recibe_signal)==-1)
    error("No puedo establecer SIGINT");
  if (sigact(SIGTERM, recibe_signal)==-1)
    error("No puedo establecer SIGTERM");
  if (sigact(SIGUSR2, recibe_signal)==-1)
    error("No puedo establecer SIGUSR2");
  if (sigact(SIGQUIT, recibe_signal)==-1)
    error("No puedo establecer SIGQUIT");

    /* Bucle infinito, para cerrar el proceso, utilizar kill -9 PID */
    while (1)
      pause();

    return 0;
}

void error(char *cadena)
{
  fprintf(stderr, "ERROR: %s (errno: %d: %s)\n", cadena, errno, strerror(errno));
  exit(1);
}

void llegasigint()
{
  static int num=0;     /* No he querido usar variables globales */

  num++;
  if (num==5)
    {
      printf ("FIN del programa\n");
      exit(1);
    }
}

void recibe_signal(int signum, siginfo_t *siginfo, void *context)
{
  /* Mostramos toda la información necesaria */
  printf ("[%d] Recibida %d (%s) de %d (UID: %d) ¿Error? %d \n",
      getpid(),     /* PID del proceso actual */
      signum,       /* Número de señal recibida */
      strsignal(signum),    /* Señal escrita en texto (inglés) */
      siginfo->si_pid,  /* PID del proceso que nos manda la señal */
      siginfo->si_uid,  /* Usuario que nos manda la señal */
      siginfo->si_errno);   /* Error producido */

  if (signum==SIGINT)
    llegasigint();
}

int sigact(int signum, void funcion(int, siginfo_t *, void *))
{
    struct sigaction sact;
   
    sigfillset(&sact.sa_mask);
    /* Definimos la función callback */
    sact.sa_sigaction = funcion;
    /* Queremos usar el callback sigaction en lugar del dado por signal() */
    sact.sa_flags     = SA_SIGINFO;

    return sigaction(signum, &sact, NULL);
}

Interfaces Gráficos en Linux con gtkmm 1 [Hola Mundo]

Jueves, 14 de Octubre de 2010 Gaspar Fernández 4 comentarios

ventanaAunque a veces, cuando nos dedicamos a programar en Linux, recurrimos a la consola (ya que muchos de nosotros tenemos siempre una abierta), a veces es interesante crear un Interfaz Gráfico de Usuario (en inglés GUI, Graphical User Interface), para ello, si no queremos complicarnos demasiado tenemos dos opciones: Gtk+ y Qt.

Bien, vamos con Gtk+, está muy extendido y escrita en C, aunque aquí hablaré de una interfaz de Gtk+ para C++ llamada gtkmm, que nos proporciona las clases necesarias para jugar con la potencia de Gtk+ de una forma un poco más amigable (ya que podemos crear un botón, una etiqueta o una ventana como objetos de C++, hará todo un poco más intuitivo).

Una primera prueba que me gusta hacer es poner todo el código junto, sin mucha organización, para crear un “hola mundo”, para ver más o menos a qué nos enfrentamos y cómo, si vamos a tener que escribir mucho y esas cosas:

[ holamundo.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <gtkmm.h>

using namespace Gtk;

int main(int argc, char *argv[])
{
  // Inicialización GTK
  Main entorno(argc, argv);

  // Declaración de objetos
  Window ventana;
  Label etiqueta;

  // Características de la ventana
  ventana.set_title("Hola Mundo!");
  ventana.set_border_width(5);
  ventana.set_default_size(400, 200);

  // Etiqueta
  etiqueta.set_text("Hola Mundo!!");
  ventana.add(etiqueta);

  // Mostrar todo
  ventana.show_all_children();

  // Ejecutar GUI
  entorno.run(ventana);

  return 0;
}

Para compilar debemos hacer lo siguiente:

$ g++ -o holamundo holamundo.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Eso sí, debemos sustituir el 2.4 por la versión de gtkmm que tengamos instalada. Para ver cuál es, podemos hacer lo siguiente:

$ pkg-config –list-all | grep gtkmm

Bien, una vez hecho esto, vamos a intentar adquirir una metodología de programación que nos permita reaprovechar código, y tenerlo todo organizado, vamos a crear archivos .h y .cpp:

[ hworld.h ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef _HWORLD_H
#define _HWORLD_H

#include <gtkmm.h>

using namespace Gtk;

class HolaMundo : public Window
{
 public:
  HolaMundo();
  ~HolaMundo();

  Label etiqueta;
};

#endif

[ hworld.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "hworld.h"

HolaMundo::HolaMundo()
{
  this->set_title("Hola Mundo!");
  this->set_border_width(5);
  this->set_default_size(400, 200);

  etiqueta.set_text("Hola Mundo!!");
  this->add(etiqueta);

  this->show_all_children();
}

HolaMundo::~HolaMundo()
{
}

[ hellomain.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <gtkmm.h>
#include "hworld.h"

int main(int argc, char *argv[])
{
  Main entorno (argc, argv);

  // Creamos la ventana
  HolaMundo hmundo;

  // Ejecutamos
  entorno.run(hmundo);

  return 0;
}

Ahora compilamos con:

$ g++ -o hellom hellomain.cpp hworld.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Hará lo mismo, ¡vaya tontería! pero lo tendremos todo mucho más organizado, hemos introducido todo lo referente a la ventana en una misma clase.

Una pequeña nota: escribo arriba using namespace Gtk para no repetir todo el rato Gtk::[Tipo] , Gtk::[Método], ya que todo lo que estoy utilizando hasta ahora pertenece al espacio Gtk.

Ahora vamos a hacer algo más complicado, vamos a utilizar algunos elementos más y vamos a introducir algún botón, para que se desempeñe alguna acción:
[ hworld.h ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef _HWORLD_H
#define _HWORLD_H

#include <gtkmm.h>

using namespace Gtk;

class HolaMundo : public Window
{
 public:
  HolaMundo();
  ~HolaMundo();


  void click_salir();
  void click_mensaje();

  VBox cajaV;
  HButtonBox botonera;
  Label etiqueta;
  Button *botonSalir, *botonMensaje;
};

#endif

Creamos un objeto derivado de Gtk::Window en el que incluimos los elementos que habrá en la ventana:

  • VBox es una caja con divisiones horizontales, es como si cortáramos horizontalmente en trozos a lo largo de la vertical
  • HButtonBox es una caja para poner botones a lo largo de la horizontal
  • Button es un botón. Pondremos dos, uno para salir y otro para mostrar un mensaje emergente
  • Label es nuestra etiqueta de “Hola Mundo”

Definimos dos métodos para los eventos click_salir() y click_mensaje().

Los botones los he declarado como punteros para demostrar cómo podemos trabajar con objetos de este tipo.

[ hworld.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include "hworld.h"
#include <sstream>      // Para hacer string<<int

HolaMundo::HolaMundo()
{
  // Configuro la ventana
  set_title("Hola Mundo!");
  set_border_width(5);
  set_default_size(400, 200);

  // Configuro la etiqueta
  etiqueta.set_text("Hola Mundo!!");

  // Configuro la botonera
  botonSalir=new Button(Stock::QUIT);
  botonMensaje=new Button("Mensaje");

  botonSalir->signal_clicked().connect(sigc::mem_fun(*this, &HolaMundo::click_salir));
  botonMensaje->signal_clicked().connect(sigc::mem_fun(*this, &HolaMundo::click_mensaje));

  botonera.pack_start(*botonSalir, PACK_SHRINK);
  botonera.pack_start(*botonMensaje, PACK_SHRINK);

  // Configuro la división
  add(cajaV);
  cajaV.pack_start(etiqueta, PACK_EXPAND_WIDGET); // Esto ocupará el máximo tamaño
  cajaV.pack_start(botonera, PACK_SHRINK);

  show_all_children();
}

HolaMundo::~HolaMundo()
{
  delete botonSalir;
  delete botonMensaje;
}

void HolaMundo::click_mensaje()
{
  int res;
  std::stringstream ss;
  MessageDialog dialog("Esto es un mensaje emergente");
  res=dialog.run();

  if (res==RESPONSE_OK)
    etiqueta.set_text("Has dicho OK");
  else if (res==RESPONSE_DELETE_EVENT)
    etiqueta.set_text("Has cancelado el diálogo");
  else
    {
      ss<<"Has respondido otra cosa: "<<res;
      etiqueta.set_text(ss.str().data());
    }
}

void HolaMundo::click_salir()
{
  hide();
}

Vemos cómo en el constructor hemos configurado todos los elementos de la ventana, vemos cómo en la botonera y la cajaV, para añadir objetos se ha utilizado el método pack_start, las propiedades Gtk::PACK_SHRINK (encogerá el objeto contenedor) y Gtk::PACK_EXPAND_WIDGET (lo expanderá)

Pero la cajaV la añadiremos a la ventana con el método add.

Los botones los creamos con new (recordamos que eran punteros), tenemos muchos botones de Stock predefinidos, y podemos crear uno con esta propiedad (todos están en Gtk::Stock::XXXXX), aunque también podemos crearlos directamente con un texto.

Para conectar los eventos (de los botones por ejemplo) usamos connect(), como parámetro podemos incluir una función (con sigc::ptr_fun(&funcion)) o un método de clase (con sigc::mem_fun(*objeto, &Clase::metodo)), será la función o método que llamaremos cuando se genere el evento (en este ejemplo será el click sobre el botón).

Además, incluyo una caja de diálogo en la que luego distinguimos si se cierra por el botón aceptar (Gtk::RESPONSE_OK), o cerrando la ventana (Gtk::RESPONSE_DELETE_EVENT) modificando el texto del Label.

[ hellomain.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <gtkmm.h>
#include "hworld.h"

int main(int argc, char *argv[])
{
  Main entorno (argc, argv);

  // Creamos la ventana
  HolaMundo hmundo;

  // Ejecutamos
  entorno.run(hmundo);

  return 0;
}

Como vemos, el programa principal es exactamente igual que el anterior, como la compilación del proyecto:

g++ -o hellom hellomain.cpp hworld.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Controlar señales

Viernes, 5 de Junio de 2009 Gaspar Fernández Sin comentarios

A veces, se nos presenta la necesidad de modificar el comportamiento de nuestro programa por ejemplo cuando el usuario pulse Control+C, o cuando terminemos nuestra aplicación, o cuando… dividamos por 0, o más cosas… podemos ver un listado de las diferentes señales con:

man 7 signal

Podemos utilizar para probar este programa (signals.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void salir(int recib);
void alarma(int que);
int tmp=0;
int divi=5;
int main()
{
    signal(SIGINT, salir);  /* Control+C */
    signal(SIGKILL, salir); /* kill */
    signal(SIGFPE, salir);  /* división por 0 */
    signal(SIGALRM, alarma);    /* alarma */
    signal(SIGUSR1, alarma);    /* definido por el usuario */
    int numero=4;
    while (1)
        {
            usleep(1000);       /* Le damos un respiro a la CPU mientras estamos en el bucle */
            tmp++;
            if (tmp%1000==0)
            {
                tmp=tmp/divi;
                printf("tmp = %d\n", tmp);
            }
        }
}
void salir(int recib)
{
    printf("Salgo porque ");
    switch (recib)
        {
        case SIGINT: printf("has pulsado Control+C...\n"); break;
        case SIGTERM: printf("alguien me ha terminado\n"); break;
        case SIGFPE: printf("he dividido por 0 y eso no se puede hacer\n"); break;
        default: printf("yo que sé !!");
        }
    printf("Sólo quería decirte que me quedé contando por %d\n\n", tmp);
    exit(0);
}
void alarma(int que)
{
    switch (que)
        {
        case SIGALRM:
            printf("Voy contando por %d\n\nDivi vale %d\n", tmp, divi); break;
        case SIGUSR1:
            divi--; break;
        }
}

Para aprovechar su funcionalidad, tenemos que ejecutarlo desde un terminal, y abrir otro terminal al lado, para ir ejecutando ciertas cosas:

killall signals (o el nombre que le hayáis dado al ejecutable)
killall -14 signals (alarma)
killall -SIGUSR1 signals (varias veces)

Observaremos que con “killall signals” cerraremos directamente, igual que con Control+C, pero antes de salir del programa, nos dará cierta información (función salir); también veremos que con “killall -14 signals” veremos información en pantalla (función alarma), y que con killall -SIGUSR1 signals” veremos que tmp se va dividiendo por un número menor cada vez que la pulsamos, y cuando lo hemos hecho varias veces, dividirá por 0 (y también capturaremos ese error).

Esto puede servir, por ejemplo, por si estamos grabando un informe de lo que estamos haciendo y se cancela la ejecución, pero queremos cerrar el archivo, queremos mostrar un resumen, capturar errores, ignorar las señales… o comunicarnos con otras aplicaciones de forma sencilla

Categories: C/C++ Tags: , , , ,

Visita otras webs de la red