IPC, Zombies, FIFO & Shared Memory

(Threads a titulo informativo)

Ing. Ignacio Javier Bonelli

IPCs

Inter Process Communication


O como comunicar programas o procesos
dentro de una misma máquina





Nota: Esto lo veremos desde un punto de vista Unix clásico.
Hay librerías más modernas como D-Bus.

Links: D-Bus y otros IPCs // D-Bus Tutorial

Tipos de IPCs


  • Signals
  • Semaphores
  • Shared Memory
  • Transmisión de datos:
    • Sockets
    • Message Queues
    • Pipes (p/ej FIFO)

Versiones y estándards


  • POSIX (Portable Operating System Interface junto con la X que viene de UNIX) : Identifica a la API y es una norma escrita por la IEEE. Define una interfaz estándar del sistema operativo y el entorno.
  • BSD (Berkeley Software Distribution) : Esta linea de Unix con código abierto ha generado muchos decendientes como FreeBSD, OpenBSD, NetBSD y DragonFly BSD.
  • UNIX System V : Fue la versión más popular de Unix comercial inicialmente desarrollada por AT&T


Gráfico con la historia de Unix y sus versiones

IPCs


Missing man package?

apt-get install glibc-doc



¿Mas info?

IPCs - Signals (I)


Macros definidas en el archivo header <signal.h>
para las señales mas comunes:


man 7 signal

Signal     Value     Action   Comment
----------------------------------------------------------------------
SIGHUP        1       Term    Hangup detected on controlling terminal
                              or death of controlling process
SIGINT        2       Term    Interrupt from keyboard
SIGQUIT       3       Core    Quit from keyboard
SIGILL        4       Core    Illegal Instruction
SIGABRT       6       Core    Abort signal from abort(3)
SIGFPE        8       Core    Floating-point exception
SIGKILL       9       Term    Kill signal
...
SIGALRM      14       Term    Timer signal from alarm(2)
SIGTERM      15       Term    Termination signal

IPCs - Signals (II)



SIGUSR1   30,10,16    Term    User-defined signal 1
SIGUSR2   31,12,17    Term    User-defined signal 2
SIGCHLD   20,17,18    Ign     Child stopped or terminated
SIGCONT   19,18,25    Cont    Continue if stopped
SIGSTOP   17,19,23    Stop    Stop process
...

SIGALRM 14 /* alarm clock */
SIGCONT 19 /* continue a stopped process */
SIGCHLD 20 /* to parent on child stop or exit */

POSIX.1-1990, POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD, ...

Signals can be numbered from 0 to 31.

IPCs - Signals

Ejemplo



#include <signal.h>
#include <sys/types.h>

sighandler_t signal(int signum, sighandler_t handler);

int kill(pid_t pid, int sig);

Código:



Más información de "GNU C Library" sección "Signal Handling"

Zombies


Ver ejemplo TCP/IP con fork : Servidor y Cliente


¿Que pasa si corremos 3 hijos y el padre
cuando terminan los hijos?


Ver con:


watch -n 1 "ps elf | grep tcp | grep -v grep"

tcp/ip + fork (sin wait) => ¡hijos zombies!

Zombies


Proceso Zombie : Muere el hijo y el padre no se entera


  • fopen() / fclose()
  • malloc() / free()
  • fork() -> Opciones:
    • Usando la función de wait() en el padre.
    • Ignorando la señal SIGCHLD en el padre.
    • Usamos un signal handler.

Ignorando la señal SIGCHLD



man 2 signal

Que opciones tenemos:

  • handler pre-definido SIG_IGN que ignora la señal.
  • handler pre-definido SIG_DFL que le pasa la señal al manejador del sistema.
  • Definir un handler mio que la ignore (o mejor haga algo con ella).

Zombies


No nos ocupamos y nos queda un proceso Zombie: zombie1.c


Formas de evitarlo:

  1. Usando la función de wait() en el padre.
  2. zombie2.c
  3. Ignorando la señal SIGCHLD en el padre.
  4. zombie3.c
  5. Usamos un signal handler.
  6. zombie4.c


Bonus: Usamos un signal handler, y hacemos algo: zombie5.c

Función de manejo de señales



typedef void (*sighandler_t)(int);


Más información de "GNU C Library" sección "Signal Handling"

Named Pipes / FIFO




#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

Es como crear un archivo. Solo que un proceso lo escribe y el segundo proceso lo lee. Siempre en orden FIFO.


Luego para usarlo es igual que con archivos/sockets, usaremos: open(), read() y write().

Named Pipes / FIFO





Una sola dirección, lectura "destructiva".

Named Pipes / FIFO



Ejemplo 1:

  • fifo_speak.c : Este programa permitirá escribir en un "extremo" de la FIFO.
  • fifo_listen.c : Este programa recibe y lee lo que hay en la FIFO.


Ejemplo 2:

Named Pipes / FIFO

Datos curiosos...



  • Se puede usar "cat" para ver la salida de una fifo (Captura).
  • Se puede tener 2 escritores simultaneos, pero solo una salida activa (Captura).



Para leer mas...

Ejercicio FIFO

Implemente una función que lea un archivo de texto desde el final hacia el principio y lo escriba en una FIFO, que debe ser creada por la función.

El prototipo de la función es el siguiente:


	int enviarInvertido (char *fifoName, char *fileName);
	

Donde:

  • fifoName: Es el nombre de la fifo que debe crear la función y donde debe escribir el texto cifrado.
  • fileName: El nombre del archivo de texto a enviar.

Y devuelve un número positivo indicando la cantidad de caracteres escritos en la fifo y un número negativo en caso de error.

  • -1 : Si no puede crear la fifo.
  • -2 : Si no puede abrir el archivo.
  • -3 : Si no puede leer el archivo.
  • -4 : Si no puede escribir en la fifo.

Shared Memory


Dos opciones:

  • La forma clásica (System V):
    • shmget(): Allocates shared memory segment
    • shmctl(): Shared memory control
    • shmat(): Shared memory Attach operation
    • shmdt(): Shared memory Detach operation
  • La forma mas moderna: mmap()/munmap()
  • map or unmap files or devices into memory

    shared_memory2.c

Shared Memory





mmap: Todos pueden leer y escribir al mismo tiempo...
¡Falta coordinación!

Semáforos y Memoria compartida




From https://www.softprayog.in/programming/interprocess-communication-using-posix-shared-memory-in-linux

Threads


Corriendo procesos en paralelo que son mas livianos y flexibles que los creados por fork.

Fork

Nos da un nuevo proceso que es una copia del actual. Tiene una "copia" de la memoria original. A medida que los nuevos procesos avanzan los valores de cada uno en memoria cambian, pero el ejecutable es el mismo. Los procesos/tareas no comparten memoria, la única forma de compartir algo es usar IPCs.


Threads

Un proceso puede tener multiples threads, cada una ejecutandose en paralelo dentro del mismo contexto (memoria). Todos los recursos son compartidos, usan la misma memoria. Se deben usar mecanismos de lock y sincronización para evitar problemas.

Threads



Conclusión: Se comparte la memoria de datos...
¡Pero no comparten la pila!

Threads

Diferencias


  • Los procesos no comparten memoria, los threads si.
  • Los procesos corren independientemente unos de otros y la sincronización la hace el kernel. Pero con los threads la sincronización la tenemos que hacer nosotros.
  • La conmutación entre threads es mas rápida que entre procesos.
  • Para comunicar procesos necesitamos IPCs, pero los threads se pueden comunicar mediante la memoria compartida. Igual esto puede producir problemas para lo que es necesario coordinar el acceso a los recursos.

Threads


Threads

(ejemplo sencillo)

threads.c (v2)



Formas de sincronizar threads:

  • Mutual Exclusion (Mutex) Locks
  • Condition Variables (muy similar a semáforos, pero sin IPCs)
  • Semáforos

Threads

Prototipos <pthread.h>


/* Creando un thread */
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
                   void *(*start_routine) (void *), void *arg);

/* Volviendo a unirse */
int pthread_join(pthread_t thread, void **retval);

/* Retornar el pthread ID actual */
pthread_t pthread_self(void);

/* ¿Son tid1 y tid2 el mismo thread?                  */
/* pthread_t no tiene forma portable de ser comparado */
int pthread_equal(pthread_t tid1, pthread_t tid2);

/* Salir del thread/función en el que estamos */
void pthread_exit(void *retval);

Threads

  • pthread_t : Estructura que describe a un Thread
  • pthread_create():

    • En thread nos devolveran el ID del thread creado
    • Atributos que puede tener thread el (ya sea prioridad u otros). Puede ser NULL
    • Puntero a la función que debe ejecutar el thread
    • Parametros de la funcion que ejecutar el thread. Si toma void, puede ser NULL
  • pthread_attr_init() : Nos permite crear los atributos de un thread.
  • pthread_join() : Es bloqueante como el wait() del fork().

Threads Mutex


Los mutual exclusion locks (mutexes) son uno de los métodos usados para serializar la ejecución de threads. Nos permiten asegurar que en un momento solo un thread se estará ejecutando.


En ciertas situaciones esto es importante y permite preservar la parte del programa que no puede trabajar en paralelo.


Su función principal es evitar el acceso a recursos compartidos.

Threads Mutex

Prototipos <pthread.h>


pthread_mutex_t : Puntero a extructura mutex

/* Inicia y destruye un registro de mutex */
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

/* Bloquea y desbloquea un/los threads */
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex); 

Threads Mutex

(primer ejemplo)

threads_mutex_v1.c


Salida:


Job 1 started
Job 2 started
Job 2 finished
Job 2 finished

"Job 2 finished" se repite dos veces... ¿Que pasó con count?

Se "serializan" los procesos en la etapa Mutex.

Solo uno ejecuta el bloque mutex al mismo tiempo.

Threads Mutex

(evitando problemas...)

threads_mutex_v2.c


Salida:


Job 1 started
Job 1 finished
Job 2 started
Job 2 finished

Ahora si funciona sin que se sobre-escriban variables.

Threads Mutex


En threads_mutex_v3.c tenemos algo más que el mutex en el proceso. Hay cosas que si corren en paralelo.


Mutex se podría ver como un hermano menor de los semáforos.

Threads & LWP ID


FORK & PID


ps elf | head -n1
ps elf | grep tcp | grep -v grep
watch -n 1 "ps elf | grep tcp | grep -v grep"

F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY     TIME COMMAND
0  1000 19398 17451  20   0  84348  1032 -      Sl+  pts/7   0:00 ./threads_m2

Threads & LWP


ps -eLf | head -n1
ps -eLf | grep threads_m2 | grep -v grep
watch -n 1 "ps -eLf | grep threads | grep -v grep"

UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
ignacio  19926 17451 19926  0    3 09:30 pts/7    00:00:00 ./threads_m2
ignacio  19926 17451 19927  1    3 09:30 pts/7    00:00:00 ./threads_m2
ignacio  19926 17451 19928  0    3 09:30 pts/7    00:00:00 ./threads_m2

Threads Ejemplo Cola Circular

threads_colacircular.c


  1. Main lee la cola circular y la vacía.
  2. Hay un thread que genera valores aleatorios.
  3. Hay 3 threads que van agregando datos a la cola circular.



Nota: Este ejemplo usa pthread_detach(), explicado en el siguiente slide.

Threads Detach

¡No hace falta join!


int pthread_detach(pthread_t thread);


int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);


NOTA: Estas funciones también nos permiten utilizar threads y que se cierren adecuandamente sin el uso del join() que es bloqueante.

Threads Join

No bloqueantes


int pthread_tryjoin_np(pthread_t thread, void **retval);


int pthread_timedjoin_np(pthread_t thread, void **retval,
const struct timespec *abstime);


NOTA - These functions are nonstandard GNU extensions; hence the suffix "_np" (nonportable) in the names.

Programación paralela


Sumando y restando una matriz

parallel_example1.c




Para leer mas...

Semáforos

Prototipos <semaphore.h>


sem_t : Estructura del tipo semaforo

/* Inicializa un semaforo */
int sem_init(sem_t *sem, int pshared, unsigned int value);

/* Bloquea un semaforo */
int sem_wait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

/* Desbloquea un semaforo */
int sem_post(sem_t *sem);

/* Destruye/libera un semaforo */
int sem_destroy(sem_t *sem);

Semáforos: Productor / Consumidor

Semáforos



Ejemplo usando semaforos y threads: sem_thread.c

IPCs : Message Queue


Librerias a usar:

<sys/types.h> , <sys/ipc.h> y <sys/msg.h>


/* Convierte un nombre/ruta a un ID manejable en System V */
key_t ftok(const char *pathname, int proj_id);

/* Obtiene el ID de una message queue System V */
int msgget(key_t key, int msgflg);

/* Envia un mensaje de una cola de mensajes identificada por key */
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

/* Obtiene un mensaje de una cola de mensajes identificada por key */
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

/* Permite controlar una cola de mensajes System V */
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

IPCs : Message Queue

Ejemplo


Ejemplo sencillo IPC:

Mensajes por Beej: kirk.c y spock.c

El comando "ipcs" lista los IPC abiertos, si lo corremos mientras los procesos kirk/spock corren reporta sobre la actividad.

Otros ejemplos...


Más ejemplos de la catedra en:
./files/clase26/ejemplos_catedra/*


En recuperatorios y finales se tomó Named Pipes (o FIFOs).

Ejercicios Integradores


Librería Sockets Info1: sock-lib.h y sock-lib.c.


Más ejemplos de la catedra en: ./files/clases_integradoras/*