Ya sabemos que no se pueden almacenar punteros en memoria compartida (las direcciones de memoria son diferentes para diferentes procesos). Esto acarrea que no se puedan crear vectores multidimensionales de la manera "tradicional" ( int ** ). La solución esta en emular las matrices al puro estilo de malloc (métodoindex magic). Supongamos que queremos declarar una matriz de enteros de 5x5 en memoria compartida. El tamaño de la memoria será: el tamaño del tipo de dato multiplicado por la cantidad de columnas y la cantidad de filas.
Los grupos de procesos se usan para controlar a un conjunto de procesos asociados entre si a través de un identificador. De manera especifica se emplean para la distribución de señales. Así, si se le envía una señal al grupo de procesos cada uno de los miembros de ese grupo recibe dicha señal. El identificador del grupo de proceso recibe el nombre de pgid.
Es importante saber que:
Todo proceso pertenece a un grupo de procesos.
En un grupo de procesos pueden haber uno o más procesos.
Cuando un proceso crea un hijo este hereda el PGID del padre.
Un proceso puede cambiarse de grupo usando la función setpgid()
Cualquier proceso puede saber a que grupo pertenece otro proceso si conoce el PID.
Asignar un PGID
int setpgid(pid_tpid, pid_tpgid);
La función setpgid asigna un pgid al proceso a un proceso. El parámetro pid indica el PID del proceso a modificar. Si pid es cero, se emplea el PID del proceso en curso.
Si pgid es cero, se emplea el PID del proceso especificado por pid.
Si setpgid se utiliza para mover un proceso de un grupo de procesos a otro (como hacen algunos shells cuando crean tuberías), ambos grupos de procesos deben formar parte de la misma sesión. En este caso, pgid especifica el grupo de procesos existente en el que vamos a entrar, y el ID de sesión de ese grupo de procesos debe coincidir con el ID de sesión del proceso que quiere entrar. En caso de éxito, setpgid y setpgrp devuelven cero. En caso de error. devuelven -1 y ponen un valor apropiado en errno
Obtener un PGID
La función getpgid devuelve el ID del grupo de proceso del especificado por pid. Si pid es cero, se emplea el PID del proceso en curso. getpgid devuelve un grupo de proceso si acaba bien; -1 en caso de error, y pone un valor apropiado en errno.
Enviar señales a un grupo de procesos
Se usa la función kill, con la salvedad que el pgid debe ser negativo. Esto para diferenciar del PID de algun proceso.
kill(-PGID, SIGNAL) // Note el signo negativo antes del PGID
Ejemplo #1:
En este ejemplo el proceso principal ( P1 ) creará 5 procesos hijos, los cuales heredarán su PID.
Los hijos se dormirán al crearse y esperarán la señal del padre.
El padre luego de crearlos duerme 2 segundos y luego le envía un mensaje a su propio grupo de procesos para despertar a todos los hijos.
Para ello usa kill(-getpgid(getpid()),SIGCONT);
#include <iostream>#include <stdio.h>#include <unistd.h>#include <sys/shm.h>#include <stdlib.h>#include <signal.h>usingnamespace std;
// SIGCONT Signal handlervoidh(int){}
intmain(int argc, char *argv[])
{
signal(SIGCONT,&h);
int pid=0;
cout<<"PID Padre="<<getpgid(getpid())<<endl;
cout<<"PGID Padre="<<getpgid(getpid())<<endl;
// El padre crea 5 hijosfor (int i = 0; i < 5; ++i) {
pid=fork();
// Hijoif (pid==0){
cout<<"Hijo "<<getpid()<<": Mi PGID es="<<getpgid(getpid())<<endl;
// EL hijo se duerme
sleep(300);
// Cuando recibe la señal SIGCONT el hijo se despierta
cout<<"Hijo "<<getpid()<<": Me despertaron"<<endl;
exit(0);
}
}
sleep(2);
cout<<"Padre: envío SIGCONT al grupo "<<getpgid(getpid())<<endl;
// Note que para enviar al grupo de procesos el valor// debe ser negativo
kill(-getpgid(getpid()),SIGCONT);
}
Para manejar memoria compartida basta con realizar estos tres pasos:
Lo primero es crear la clave para la memoria compartida. Para ello se usa la función ftok que recibe dos parámetros:La ruta de un fichero existente y un número para generar la clave:
//Secrealaclaveparalamemoriacompartida
clave=ftok("/bin/ls",34);
Luego creamos la memoria con la clave conseguida. Para ello usamos shmget.
El primer parámetro es la clave
El segundo es la cantidad de bytes que se va a ocupar la memoria. En este caso queremos reservar espacio para un solo entero.
El tercero son los flags (permisos y modos). IPC_CREAT indica que debecrear la zona de memoria si esta no existe. En caso de que exista la usa de igual forma. Si se le agrega IPC_EXCLfalla si el id ya existe. Esto para evitar usar una zona que ya este en uso o creada.
Por último obtenemos la dirección de la memoria compartida y la guardamos en un puntero. para ello usamos shmat. El primer parametro es el id que obtuvimos con shmget. El resto de parámetros basta con colocarlos en cero :)
Con estos tres pasos ya podemos usar la memoria compartida.
En este ejemplo vemos a dos procesos:
* Restador: Decrementa el valor de la memoria compartida.
* Sumador: Incrementa el valor de la memoria compartida.
En la memoria compartida estará alojado un entero
Este es el código del sumador. El del restador es casi igual, solo que en vez de incrementar decrementa.
#include <iostream>#include <stdio.h>#include <unistd.h>#include <sys/shm.h>#include <stdlib.h>usingnamespace std;
intmain()
{
key_t clave=0;
int id_Memoria=0;
int*memoriaCompartida =NULL;
// Se crea la clave para la memoria compartida
clave = ftok ("/bin/ls", 34);
if (clave ==-1){
cout <<"No consigo clave para memoria compartida"<< endl;
exit(0);
}
// Creamos la memoria con la clave recién conseguida. Para ello llamamos// a la función shmget pasándole la clave, el tamaño de memoria que// queremos reservar (100 enteros en nuestro caso) y unos flags.// Los flags son los permisos de lectura/escritura/ejecucion// para propietario, grupo y otros (es el 777 en octal) y el// flag IPC_CREAT para indicar que cree la memoria.// La función nos devuelve un identificador para la memoria recién// creada.//
id_Memoria = shmget (clave, sizeof(int)*1, 0777| IPC_CREAT);
if (id_Memoria ==-1) {
cout <<"No consigo Id para memoria compartida"<< endl;
exit (0);
}
//// Una vez creada la memoria, hacemos que uno de nuestros punteros// apunte a la zona de memoria recién creada. Para ello llamamos a// shmat, pasándole el identificador obtenido anteriormente y un// par de parámetros extraños, que con ceros vale.//
memoriaCompartida = (int*)shmat (id_Memoria, (char*)0, 0);
if (memoriaCompartida ==NULL)
{
cout <<"No consigo memoria compartida"<< endl;
exit (0);
}
// Ya podemos utilizar la memoria.// Escribimos cosas en la memoria. Los números de 1 a 10 esperando// un segundo entre ellos. Estos datos serán los que lea el otro// proceso.//while (true)
{
// Incrementa el valor en memoria compartida
memoriaCompartida[0]++;
cout<<"Valor del entero en Memoria compartida = "<<memoriaCompartida[0]<<endl;
fflush(stdout);
sleep(1);
}
// shmdt desasocia una region de memoria compartida del espacio// de direcciones virtuales del proceso//
shmdt ((char*)memoriaCompartida);
// Marca el ID de la memoria para ser destruido:// NO DESTRUYE la memoria, solo la marca.// Cuando se muere el ultimo proceso asociado a esa// memoria entonces el sistema se encarga de borrar la// memoria
shmctl (id_Memoria, IPC_RMID, (struct shmid_ds *)NULL);
}
La función ftok crea identificadores "únicos" para ser usados por funciones IPC (semget, shmget, msgget). Recibe dos parámetros: * Una ruta a un fichero existente y accesible * Un número
ftok ( char *pathname, charproj );
Ejemplo: ftok ( "/bin/ls", 34 );
La función busca el inodo del fichero indicado y lo combina con el número del segundo parámetro.
Note que si se pasa el mismo fichero y el mismo número a dos procesos que estén corriendo en la misma máquina entonces e generará la misma clave. Esto puede ser indeseado en muchos casos. Para evitar colisiones de clave es habitual usar el directorio actual de trabajo (.) como primer parámetro. El directorio actual es representado por un punto.
#include <QtCore>#include <QtWidgets>#include <QObject>#include <QCoreApplication>/** @brief Representa una figura cuadrada **/classCuadrado : public QGraphicsWidget
{
public:/** Constructor * @param colorDeseado Color que el usuario desea * Puede ser "azul" o "rojo". En caso contrario * la pieza se dibujará gris **/
Cuadrado(QString colorDeseado):QGraphicsWidget(){
// Asigna el color segun el color deseado
color= colorDeseado=="rojo"? Qt::red : (colorDeseado=="azul"?
Qt::blue : Qt::gray);
}
~Cuadrado(){}
/** Pinta la pieza segun el color **/void paint(QPainter *painter, const QStyleOptionGraphicsItem *,
QWidget *){
painter->fillRect(rect(), color);
}
void mover(qreal xEnd,qreal yEnd){
// Aqui crea la animación
QPropertyAnimation *animation = new QPropertyAnimation(this,
"pos");
animation->setDuration(8000); // 8 Segundos
animation->setStartValue(QPointF(this->x(), this->y()));
animation->setEndValue(QPointF(xEnd, yEnd));
animation->start(QAbstractAnimation::DeleteWhenStopped);
}
private:/** Color del cuadrado **/
Qt::GlobalColor color;
};
intmain(int argc, char **argv)
{
QApplication app(argc, argv);
// Creamos un cuadro rojo y uno azul
Cuadrado *cuadroAzul=new Cuadrado("azul");
Cuadrado *cuadroRojo=new Cuadrado("rojo");
// Los movemos a la posición inicial
cuadroAzul->moveBy(60,10);
cuadroRojo->moveBy(10,10);
// Se crea la escena
QGraphicsScene scene(0, 0,600, 600);
// Se agrega los cuadros a la escena
scene.addItem(cuadroAzul);
scene.addItem(cuadroRojo);
// Se mueve con animación
cuadroAzul->mover(200,500);
cuadroRojo->mover(100,500);
QGraphicsView window(&scene);
window.show();
return app.exec();
}
Qt no integra ninguna solución que encapsule y simplifique la gestión de señales POSIX, puesto que éstas no están disponibles en sistemas operativos que no soporten dicha especificación. Aun así en la documentación de Qt se describe una forma de usar manejadores de señales POSIX. Realmente basta con que el manejador de señal haga algo que provoque que Qt emita una señal y después retorne. Al volver a la secuencia normal de ejecución del programa, se emitiría la señal de Qt invocando el slot al que está conectada, desde donde se ejecutarían de forma segura las operaciones que fueran necesarias. Para esta entrada he creado una clase en C++ llamada QPosixSignalListener que permite abstraer todo el proceso de gestión de señales. Me basé en el artículo de la documentación oficial que trata sobre manejo de señales. Diagrama de uso de la clase:
Interfaz de la clase:
classQPosixSignalListener:publicQObject
{
Q_OBJECT
public:
/**Constructor.Setealasseñalesycrea
*lossocketscorrespondientes
*
*@paramparentPadredelobjeto
***/
QPosixSignalListener(QObject*parent=0);
~QPosixSignalListener(){}
//Unixsignalhandlers.
staticvoidhupSignalHandler(int);
staticvoidtermSignalHandler(int);
staticvoidusr1SignalHandler(int);
staticvoidusr2SignalHandler(int);
//SettheUnixsignalhandlers
staticintsetup_unix_signal_handlers();
privateslots:
//Qtsignalhandlers.
voidhandlerQt_SIGHUP();
voidhandlerQt_SIGTERM();
voidhandlerQt_SIGUSR1();
voidhandlerQt_SIGUSR2();
signals:
//Signals
voidsigHUP();
voidsigTERM();
voidsigUSR1();
voidsigUSR2();
private:
//sockets
staticintsocketSIGHUP[2];
staticintsocketSIGTERM[2];
staticintsocketSIGUSR1[2];
staticintsocketSIGUSR2[2];
//SocketNotifiers
QSocketNotifier*skNotifierHUP;
QSocketNotifier*skNotifierTERM;
QSocketNotifier*skNotifierUSR1;
QSocketNotifier*skNotifierUSR2;
};
La clase puede manejar las señales SIGHUP, SIGTERM, SIGUSR1 y SIGUSR2. Puedes modificarla para que soporte otras. USO:
Cuando el proceso recibe una señal (soportada) el objeto emite una señal de Qt. Para usar la clase solo basta con instanciar un objeto y conectar una de sus señales a un slot cualquiera. Ejemplo: