Memoria compartida
en C para Linux
La memoria compartida, junto con los semáforos
y las colas de mensajes, son los recursos compartidos
que pone unix a disposición de los programas para que puedan intercambiarse
información.
En C para unix es posible hacer que dos procesos (dos programas) distintos
sean capaces de compartir una zona de memoria común y, de esta manera,
compartir o comunicarse datos.
La forma de conseguirlo en un programa es la siguiente:
- En primer lugar necesitamos conseguir una clave, de
tipo key_t, que sea común para todos los programas que quieran
compartir la memoria. Para ello existe la función
key_t ftok (char *, int). A dicha función se le
pasa un fichero que exista y sea accesible y un entero. Con ellos construye
una clave que nos devuelve. Si todos los programas utilizan el mismo fichero
y el mismo entero, obtendrán la misma clave.
Es habitual como primer parámetro pasar algún fichero del
sistema que sepamos seguro de su existencia, como por ejemplo "/bin/ls",
que es el "ls" del unix.
Para el entero, bastaría con poner un #define en
algún fichero.h de forma que todos los programas que vayan a utilizar
la memoria compartida incluyan dicho fichero y utilicen como entero el
del #define
- Una vez obtenida la clave, se crea la zona de memoria.
Para ello está la función int shmget (key_t, int,
int). Con dicha función creamos la memoria y nos devuelve
un identificador para dicha zona.
Si la zona de memoria correspondiente a la Clave key_t ya estuviera creada,
simplemente nos daría el identificdor de la memoria (siempre
y cuando los parámetros no indiquen lo contrario).
- El primer parámetro es la clave key_t
obtenida anteriormente y que debería ser la misma para todos
los programas.
- El segundo parámetro es el tamaño
en bytes que deseamos para la memoria.
- El tercer parámetro son unos flags. Aunque
hay más posibilidades, lo imprescindible es:
- 9 bits menos significativos, son permisos
de lectura/escritura/ejecución para propietario/grupo/otros,
al igual que los ficheros. Para obtener una memoria con todos
los permisos para todo el mundo, debemos poner como parte de los
flags el número 0777. Es importante el cero delante, para
que el número se interprete en octal y queden los bits
en su sitio (En C, cualquier número que empiece por cero,
se considera octal). El de ejecución no tiene sentido y
se ignora.
- IPC_CREAT. Junto con los bits anteriores, este
bit indica si se debe crear la memoria en caso de que no exista.
Si está puesto, la memoria se creará si no lo está
ya y se devolverá el identificador.
Si no está puesto, se intentará obtener el identificador
y se obtendrá un error si no está ya creada.
En resumen, los flags deberían ser algo así como 0777
| IPC_CREAT
- El último paso poder usar la memoria consiste en obtener un puntero
que apunte la zona de memoria, para poder escribir o leer en ella. Declaramos
en nuestro código un puntero al tipo que sepamos que va a haber
en la zona de memoria (una estructura, un array, tipos simples, etc) y
utilizamos la función char * shmat (int, char *, int).
- El primer parámetro es el identificador
de la memoria obtenido en el paso anterior.
- Los otros dos bastará rellenarlos con ceros.
- El puntero devuelto es de tipo char *. Debemos hacerle un "cast"
al tipo que queramos, por ejemplo, (mi_estructura *)shmat (...);
Esta función lo que en realidad hace, además de darnos
el puntero, es asociar la memoria compartida a la zona de datos de
nuestro programa, por lo que es necesario llamarla sólo una
vez en cada proceso. Si queremos más punteros a la zona de
memoria, bastará con igualarlos al que ya tenemos.
- Ya estamos en condiciones de utilizar la memoria. Cualquier cosa que
escribamos en el contenido de nuestro puntero, se escribirá en
la zona de memoria compartida y será accesible para los demás
procesos.
- Una vez terminada de usar la memoria, debemos liberarla. Para ello utlizamos
las funciones int shmdt (char *) e int shmctl
(int, int, struct shmid_ds *)
La primera función desasocia la memoria compartida de la zona de
datos de nuestro programa. Basta pasarle el puntero que tenemos a la zona
de memoria compartida y llamarla una vez por proceso.
La segunda función destruye realmente la zona de memoria compartida.
Hay que pasarle el identificador de memoria obtenido con shmget(), un
flag que indique que queremos destruirla IPC_RMID, y un tercer parámetro
al que bastará con pasarle un NULL A esta función sólo
debe llamarla uno de los procesos.
Hasta ahora se indicado lo mínimo y más básico para poder
utilizar memoria compartida. Hay más funciones que permiten hacer más
cosas con la memoria compartida. Además, para un uso más "serio"
y "correcto", sería necesario añadir algo como
semáforos.
Por ejemplo, si los datos que deseamos compartir son muy grandes, es bastante
probable que mientras un proceso intenta escribir en la memoria, otro esté
leyendo. Si lo intentan de forma totalmente descontrolada y a la vez, los
datos que leen pueden ser incoherentes (hay otro que está escribiéndolos
en ese momento y todavía no ha terminado).
Para evitar esto existen los semáforos. Se pondría un semáforo
para acceder a la memoria, de forma que cuando un proceso lo está
haciendo, el semáforo se pone "rojo" y ningún otro proceso
puede acceder a ella. Cuando el proceso termina, el semáforo se pone
"verde" y ya podría acceder otro proceso.
Comandos de Unix útiles
Unos comandos de UNIX que nos pueden resultar de utilidad son ipcs y ipcrm.
ipcs nos da un listado de recursos compartidos que están
creados en ese momento, es decir, listado de memorias compartidas como las
que hemos tratado en esta página, de semáforos
y de colas.
ipcrm nos permite eliminar algunos de estos recursos.
Si paramos el programa con un Ctrl-C o simplemente sale de forma anormal,
el recurso (la memoria compartida) no se libera y queda en el sistema. La
forma de borrarla sería con este comandos.
Es bastante normal mientras desarrollamos y depuramos nuestro programa
que se nos caiga, lo abortemos, etc. El número de memorias compartidas
que podemos crear está limitado en UNIX, así que a la cuarta
o quinta prueba empezaremos a obtener errores de que no se pueden crear
las memorias. ipcrm nos permite eliminar las memorias que se nos han
quedado "pendientes" en nuestras pruebas.
El Ejemplo
Aquí hay dos fuentes de ejemplo en C sobre UNIX de memoria compartida
p1.c y p2.c. Son dos programas que comparten memoria. El primero crea
la memoria de tamaño 100 int y empieza a escribir en el primero entero,
con espera de un segundo, los números del 1 al 10. Es segundo programa
lee, a intervalos de un segundo, el número guardado en el primer
entero y lo muestra en pantalla.
Para ejecutarlos, compilarlos en primer lugar con
make p1
p2
(no es necesario fichero Makefile) o bien con g++ p1.c
-o p1
y g++ p2.c -o p2. Luego en
dos shell distintas, ejecutar en la primera p1 y en la segunda p2 Desde que
arrancamos
p1 tenemos unos diez segundos para arrancarp2
y ver el efecto deseado.
Estadísticas y comentarios
Numero de visitas desde el 4 Feb 2007:
- Esta pagina este mes: 74
- Total de esta pagina: 125741
- Total del sitio: 43568719