16.2.05

Sincroniazación entre procesos

El siguiente porgrama crea 2 hijos. Uno de ellos pondrá los números del 1 al 5 en una variable compartida y el otro leerá esos valores y los mostrará por pantalla.

El primer procso pone el número 1 en la variable compartida, después, el segundo proceso lee el valor 1 y lo muestra por pantalla. Después el primer proceso pone el número 2 en la variable compartida, y así hasta el 5.

Para sincronizar ambos procesos se han utilizado dos grupos de un semáforo cada uno.



#include < errno.h>
#include < stdio.h>
#include < unistd.h>
#include < wait.h>
#include < sys/ipc.h>
#include < sys/sem.h>
#include < sys/stat.h>
#include < sys/types.h>

#include "sv.h"


union senum {
int val;
};

int main() {

pid_t pid;
int c, r;
int num;
int sem_prod, sem_cons;
struct sembuf arriba = {0,1,0};
struct sembuf abajo = {0,-1,0};
union senum arg;


sv_init();

sem_prod = semget(IPC_PRIVATE, 1, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
sem_cons = semget(IPC_PRIVATE, 1, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
if ((sem_prod == -1) || (sem_cons==-1)) {
perror("Error creando semáforos");
return -1;
}

arg.val = 1;
r = semctl(sem_prod, 0, SETVAL, arg);
if (r == -1) {
perror("Error incializando el semáforo del productor - ");
//Borrar los semáforos
return -1;
}

for (c = 0; c < 2; c++) {
pid = fork();
if (pid == -1) {
perror("Error creando procesos - ");
// Borrar semáforos.
return -1;
}

if (pid == 0) {

// Productor
if (c==0) {
for (num = 1; num < 6; num++) {
// Mi turno de producir
r = semop(sem_prod, &abajo, 1);
if (r == -1) {
perror("Error bajando el semáforo del productor - ");
// Eliminar semáforos
return -1;
}

printf("Valor producido: %i\n", num);
sv_set(num);

// El turno de consumir
r = semop(sem_cons, &arriba, 1);
if (r == -1) {
perror("Error subiendo el semáforo del consumidor - ");
// Eso
return -1;
}
}
exit(0);
}

// Consumidor
if (c==1) {
for (num = 0; num < 5; num++) {
// Voy a consumir 5 números
r = semop(sem_cons, &abajo, 1);
if (r == -1) {
perror("Error bajando semáforo del consumidor - ");
return -1;
}

printf("Valor consumido: %i\n", sv_get() );

// Hora de producir
r = semop(sem_prod, &arriba, 1);
if (r == -1) {
perror("Error subiendo semáforo del productos - ");
return -1;
}
}
exit(0);
}

}
}

pid = wait(&r);

while ( (pid != -1) ||
( (pid == -1) && (errno == EINTR)) )
pid = wait(&r);

r = semctl(sem_prod, 0, IPC_RMID);
if (r==-1) {
perror("Error eliminando semáforo productor.");
}
r = semctl(sem_cons, 0, IPC_RMID);
if (r == -1) {
perror("Error eliminando semáforo consumidor. ");
}

sv_finish();

printf("Fin.\n");
return 0;
}


Acceso a un recurso compartido

Saludos.

El siguinte código crea cuatro procesos hijos. Cada hijo incrementará una variable compartida en 5 unidades. Para garantizar la exclusión mútua sobre la variable compartida se utiliza un grupo de un semáforo que funciona como semáforo binario.

Hay que cabiar todas las " de los includes por mayor y menor excepto el include de sv.h.


/**
Implementa un semáforo binario
utilizando directamente las funciones del sistema
**/

#include "sv.h"

#include "stdio.h"
#include "wait.h>
#include "unistd.h"
#include "sys/types.h"

#include "sys/stat.h"
#include "sys/ipc.h"
#include "sys/sem.h"


// Incrementa en 5 la variable compartida
#define INC 5

void inc(int);


// Esta unión hay que declararla
union semun {
int val;
struct semid_ds *buf;
ushort *array;
};

int main() {
int c;
int hijos = 4;
pid_t pid;

int semaforo;
union semun arg;
int r;
// { semaforo, valor, banderas }
struct sembuf arriba = {0, 1, 0};
struct sembuf abajo = {0, -1, 0};


sv_init();
printf("Valor de inicio: %i\n", sv_val());

// Crear e inicializar un semaforo binario
// para garantizar la exclusion mutua.
semaforo = semget(42, 1, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
if (semaforo == -1) {
perror ("Error en la creación del semáforo - ");
return -1;
}

arg.val = 1;
r = semctl(semaforo, 0, SETVAL, arg);
if (r == -1) {
perror ("Error inicializando semáforo - ");
// Hay que eliminarlo.
return -1;
}


for (c = 0; c < hijos; c++) {
pid = fork();
if (pid == -1) {
perror("Error en fork - ");
exit(-1);
}
if (pid == 0) {

// Decrementar semaforo
r = semop(semaforo, &abajo, 1);
if (r == -1) {
perror("Error decrementando semáforo - ");
// Eliminar el semáforo
return -1;
}

inc(c);

// Incrementar semaforo
r = semop(semaforo, &arriba, 1);
if (r == -1) {
perror("Error incrementando semáforo - ");
// Es necesario eliminarlo
return -1;
}

exit(0);
}
}

while ( wait(NULL)!=-1 );

printf("-------------------------------\n");
printf("Valor esperado: %i\n", hijos * INC);
printf("Valor real: %i", sv_val());

// Borrar semaforo
r = semctl(semaforo, 0, IPC_RMID);
if (r == -1)
{
perror("Error elminando semáforo - ");
return -1;
}

sv_finish();
printf("\n");
}


//----------------------------
void inc(int id) {
int c;
int tmp;
for(c=0; c < INC; c++) {
tmp = sv_get();
if ( (id == 0) && (c < 2)) sleep(1);
tmp++;
sv_set(tmp);
}
}

14.2.05

Libería para implementar una variable compartida

A continuación se incluye el código de una librería que permite crear y manipular una variable compartida, esto es, una variable común para todos los procesos que utilicen esta librería. Así, los cambios que haga un proceso sobre esta variable, serán visibles para los demás procesos.

Esta librería es un buen ejemplo de como utilizar el mecanismo IPC de memoria compartida.

Esta librería se utilizará en ejemplos posteriores con semáforos.

sv.h


#ifndef _SV_
#define _SV_

/**
Gestiona una variable entera mediante memoria compartida.
La variable es común a todos los procesos creados
después de llamar a sv_init();
**/


/**
Reserva la memoria compartida e inicializa la variable
a 0.
-1 si error.
**/
int sv_init();

/**
Libera la memoria compartida.
-1 si error.
**/
int sv_finish();

/**
Devuelve el valor de la variable común
sv_get es un alias para sv_vsl
**/
int sv_val();
int sv_get();

/**
Incrementa la variable entera con el valor
indicado como parámetro y devuelve su nuevo valor.
**/
int sv_inc(int);

/**
Asigna un valor a la variable común
Devuelve el valor asignado.
**/
int sv_set(int);

#endif





sv.c


#include "stdio.h"
#include "sys/ipc.h"
#include "sys/shm.h"
#include "sys/stat.h"
#include "sys/types.h"

int s_id;
int *sv = NULL;

//---------------------------------
int sv_init() {
int tam = sizeof(int);
s_id = shmget(IPC_PRIVATE, tam, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
if (s_id == -1)
return -1;
sv = (int *)shmat(s_id, NULL, 0);
if (sv == (int *)-1) {
sv = NULL;
return -1;
}
(*sv)=0;
return 1;
}

//--------------------------------
int sv_inc(int val) {
int tmp;
if (sv == NULL)
return -1;
tmp = (*sv);
tmp += val;
(*sv)= tmp;
return (tmp);
}

//--------------------------------
int sv_val() {
if (sv == NULL)
return 0;
return (*sv);
}

//--------------------------------
int sv_get() {
return sv_val();
}

//--------------------------------
int sv_set(int val) {
(*sv) = val;
return val;
}

//--------------------------------
int sv_finish() {
if (s_id == -1)
return -1;
return shmctl(s_id, IPC_RMID, 0);
}

Mensaje con preaviso

Saludos.

El siguiente código crea un proceso hijo que enviará cuatro mensajes al proceso padre, el cual los mostrará por pantalla. Para evitar que cualquier de los dos procesos se quede esperando en la tubería, el padre, antes de leer un mensaje de la tubería, avisará al hijo con una señal SIGUSR1. El hijo, al recibir la señal, pondrá un mensaje en la tubería y esperará a la siguiente señal.


#include "stdio.h"
#include "unistd.h"
#include "signal.h"
#include "wait.h"
#include "sys/types.h"

#define SIZE 256

int tub[2];
char msg[SIZE];

void manejador_hijo(int);

int main() {

int c = 0;
int status;
pid_t pid;

pipe(tub);
pid = fork();
if (pid == -1) {
perror("Error bifurcando proceso - ");
return -1;
}

if (pid == 0) {
if (signal (SIGUSR1, manejador_hijo) == SIG_ERR) {
perror("Error instalando manejador - ");
exit(-1);
}
close(tub[0]);
while (1)
pause();

printf("Hijo, terminación inesperada.\n");
exit(-1);
}

close(tub[1]);
while(c < 4) {
sleep(1);
if ( kill(pid, SIGUSR1) == -1) {
perror("Error enviando señal al hijo.");
return -1;
}

if (read(tub[0], msg, SIZE) == -1) {
perror("Error leyendo de la tuberia - ");
return -1;
}

printf("%i: %s\n", c, msg);
c++;
}

close(tub[0]);

if (kill(pid, SIGTERM) == -1) {
perror("Error terminando hijo.");
return -1;
}

while( pid != wait(&status) );

printf("Fin\n");
return 0;
}

//--------------------------------------------------

void manejador_hijo(int sig)
{
if ( signal (SIGUSR1, manejador_hijo) == SIG_ERR) {
perror("Error reinstalando el controlador.");
exit(-1);
}
strcpy(msg, "Mensaje del hijo.");
if ( write(tub[1], msg, strlen(msg)) == -1) {
perror("Error escribiendo en la tuberia.");
exit(-1);
}
}

12.2.05

Mensaje repetitivo

El siguiente código muestra un mensaje por pantalla drante 2 segundos, pasado estetiempo recibe una señal SIGALRM y el mensaje cambia. Este proceso se repite infinitas veces, o hasta que alguna variable se desborde.

Hay que cambiar las " de los includes por los signos de mayor y menor.


#include "unistd.h"
#include "signal.h"
#include "string.h"
#include "sys/types.h"

void sig_alarm(int);

char men[50];
int num_alarm = 0;


int main()
{
if ( signal(SIGALRM, sig_alarm) == SIG_ERR) {
perror("Error instalando manejador - ");
return -1;
}

sprintf(men, "Alarma %i", num_alarm);
alarm(2);

while(1) {
printf("%s\n", men);
pause();
}

// Se ejcutará alguna vez esta línea ??
printf("Se acabó.\n");

return 0;
}

//---------------------------

void sig_alarm(int sig) {

if ( signal(SIGALRM, sig_alarm) == SIG_ERR) {
perror("Error instalando manejador - ");
}

alarm(2);
num_alarm++;
sprintf(men, "Alarma %i", num_alarm);
}

Matar al hijo perezoso

El siguiente programa lanza un proceso hijo, y si después de X segundos el proceso sige activo, lo mata con una señal SIGKILL.

Es conveniente jugar con el valor del sleep(..) del hijo para ver el comportamiento de este programa.


#include "unistd.h"
#include "signal.h"
#include "wait.h"
#include "sys/types.h"

int main()
{
pid_t pid, pid_hijo;
int status;
int sleep_padre = 2;
int sleep_hijo = 1;


if ( (pid_hijo=fork()) == -1) {
perror("Error en el fork - ");
return -1;
}

if (pid_hijo == 0) {
sleep(sleep_hijo);
printf("Termina hijo.\n");
exit(0);
}

sleep(sleep_padre);
pid = waitpid(pid_hijo, &status, WNOHANG);

printf("pid = %ld / pid_hijo = %ld / status = %i\n", pid, pid_hijo, status);

if (kill(pid_hijo,0)==0) {
printf("Hijo aún en activo. Lo termino\n");
kill(pid_hijo, SIGTERM);
waitpid(pid_hijo, NULL, 0);
}
printf("Termina padre.\n");

return 0;
}

Contar señales SIGUSR1

Saludos.

El siguiente programa queda a la espera de recibir una señal. Cunado recibe una señal SIGUSR1 incrementa un contador, y cuando recibe una señal SIGTERM muestra el contador y termina.

Una variante muy sencilla es añadirle un manejador para SIGUSR2 de tal manera que, al recibir esta señal, muestre el valor del contador pero continue su ejecución.

Para ejecutar un programa o comando en sgeundo plano, sin que deje la consola ocupada, hay que añadir & al final del nombre del programa o comando.


#include "signal.h"
#include "unistd.h"

void manejadorUSR1(int sig);
void manejadorTERM(int sig);

int cuenta = 0;

int main()
{

if ( signal(SIGUSR1, manejadorUSR1) == SIG_ERR) {
perror("Error instalando manejadorUSR1 - ");
return -1;
}

if ( signal(SIGTERM, manejadorTERM) == SIG_ERR) {
perror("Error instalando manejadorTERM - ");
return -1;
}

while(1)
sleep(5);

return 0;
}

//-------------------------
void manejadorUSR1(int sig) {

if ( signal(SIGUSR1, manejadorUSR1) == SIG_ERR) {
perror("Error instalamdno manejadorUSR1 - ");
exit(0);
}

cuenta++;
}

//---------------------------
void manejadorTERM(int sig) {

printf("Señales USR1 recibidas: %i \n", cuenta);
exit(0);
}

8.2.05

Sincronización padre-hijo con señales

El siguiente programa muestra los números del 1 al 6 en orden creciente por pantalla. Los números impares los muestra el padre y los números pares los muestra el hijo.


#include "signal.h>
#include "unistd.h>
#include "sys/types.h>

void manejadorPadre(int sig);
void manejadorHijo(int sig);

int cuenta = 0;

int main() {

pid_t pid;

if ( (pid = fork()) == -1) {
perror("Error en el fork - ");
return -1;
}

if (pid == 0) {
printf("Hijo instala manejador.\n");
if ( signal(SIGUSR1, manejadorHijo) == SIG_ERR) {
perror("Error instalando manejadorHijo - ");
exit(-1);
}

printf("Hijo entra en el bucle\n");
while(1)
pause();
exit(0);
}

// sleep(1);
printf("Uno\n");

if ( signal(SIGUSR1, manejadorPadre) == SIG_ERR) {
perror("Error instalando manejadorHijo - ");
exit(-1);
}

while(cuenta < 2) {
kill(pid, SIGUSR1);
pause();
}
kill(pid, SIGUSR1);


printf("Esperando al hijo.\n");
wait(NULL);
return 0;
}


//--------------------------
void manejadorHijo(int sig)
{

static int contador = 0;

if ( signal(SIGUSR1, manejadorHijo) == SIG_ERR) {
perror("Error instalando manejadorHijo - ");
exit(-1);
}

if (contador == 0){
printf("Dos\n");
contador++;
kill(getppid(), SIGUSR1);
} else if (contador == 1) {
printf("Cuatro\n");
contador++;
kill(getppid(), SIGUSR1);
} else {
printf("Seis\n");

// Restauro el manejador original
if (signal(SIGUSR1, SIG_DFL) == NULL)
perror("Error restaurando manejador original en el hijo - ");

exit(0);
};
}

//---------------------------------
void manejadorPadre(int sig)
{

if ( signal(SIGUSR1, manejadorPadre) == SIG_ERR) {
perror("Error instalando manejadorPadre - ");
exit(-1);
}

if (cuenta == 0) {
printf("Tres\n");
cuenta++;
} else if (cuenta == 1) {
printf("Cinco\n");
cuenta++;
// Restauro el manejador original
if (signal(SIGUSR1, SIG_DFL) == NULL)
perror("Error restaurando manejador original en el padre - ");
}
}


Listado ordenado.

El siguiente programa crea dos procesos hijos. El primer hijo ejecuta el comando "ls -l" y le pasa el resultado al segundo hijo. El segundo hijo ejecuta "sort -n +4" y redirecciona la salida al archivo "lsord.txt".

En otras palabras, el resultado de este programa es el mismo que el de ejecutar la siguiente expresión:

$ ls -l | sort -n +4 > lsord.txt


// Sustituir las "s por mayor y menos
#include "stdio.h"
#include "stdlib.h>
#include "unistd.h>
#include "fcntl.h>
#include "errno.h>
#include "sys/types.h>
#include "sys/wait.h>

int main()
{
int fd[2];
int status;
int fd_salida;

pipe(fd);

if ( (fd_salida=open("lsord.txt", O_WRONLY | O_CREAT | O_TRUNC, 0600)) == -1) {
perror("Error - ");
return -1;
}

if (fork()==0)
{
// El hijo
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
close(fd[1]);
execlp("ls", "ls ", "-l", NULL);
perror("Exec falló en ls");
exit(-1);
}
if (fork()==0) {
// El otro hijo
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
close(fd[1]);
dup2(fd_salida, 1);
close(fd_salida);
execlp("sort", "sort", "-n", "+4", NULL);
perror("Exec falló en sort");
exit(-1);
}

close(fd[0]);
close(fd[1]);

// El padre espera a que terminen los hijos
status = wait(NULL);
while ( (status != -1) ||
(status == -1 && errno == EINTR))
status = wait(NULL);

exit(0);
}

6.2.05

Familia numerosa.

El siguiente código crea tres procesos hijos y, cada proceso hijo, crea tres nuevos procesos nietos.
Este código se puede simplificar mucho utilizando exit(int).


#include "unistd.h"
#include "sys/types.h"

int main()
{
int i, padre, prim_padre;

prim_padre = padre = 1;

for(i=0; i<3; i++) {
if (padre == 1) {
if (fork() == 0) {
// Proceso hijo
if (fork()==0) {
// Proceso nieto
printf("Nieto %i con padre %ld\n", getpid(), getppid());
} else {
printf("Hijo %i con padre %ld\n", getpid(), (long)getppid() );
}
padre = 0;
} else {
sleep(2);
if (prim_padre) {
printf("Padre %i\n", getpid());
prim_padre = 0;
}
}
}
}
return 0;
}


Ejemplo de tuberías

En el siguiente código, un proceso padre envía un mensaje a un proceso hijo mediante una tubería y el proceso hijo lo muestra por pantalla.


#include "unistd.h"
#include "sys/types.h"

int main()
{
int tub[2];
char msg[256];
pid_t pid;
int status;

pipe(tub);
pid = fork();

if (pid !=0) {
// El padre
write(tub[0], "Soy padre.\n", 12);
}
else {
// El hijo
read(tub[1], msg, 256) ;
printf("Hijo > %s", msg);
}

}


Copiar entrada en salida.

El siguiente código espera una entrada por la consola y muestra dicha entrada en la salida estándar.


#include "stdio.h"
#include "stdlib.h"
#include "sys/file.h"

#define TAM_BUF 255


int main(int argc, char *argv[])
{
char buf[TAM_BUF];
int readed;

readed = read(0, buf, TAM_BUF);
while(readed > 0)
{
write(1, buf, readed);
readed = read(0, buf, TAM_BUF);
}


printf("Hello, world!\n");

return EXIT_SUCCESS;
}

Escribir un mensaje por la salida estándar

Saludos.

El siguiente código escribe un mensaje por la salida estándar utilizando el mecanismo de descriptores de archivos.


#include "stdio.h"
#include "stdlib.h"

#include "sys/file.h"

#define TAM_BUF 255


int main(int argc, char *argv[])
{
char buf[TAM_BUF];

strcpy(buf, "Soy tocon.\n");
write(1, buf, 10);

printf("Hello, world!\n");

return EXIT_SUCCESS;
}

5.2.05

Adivinanza 2

¿Cuántas veces aparece por pantalla la palabra "Proceso" y que valor tiene en cada aparición la variable p?


#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"

int main(void) {

int c;
int p=0;
for (c = 0; c < 3 ; c++ )
{
fork();
}
p++;
printf("Proceso %d\n", p);
}

Adivinanza 1

¿Cuantas veces aparece por pantalla la palabra "Proceso"?.


// Cambiar las " por mayor y menor.
#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"

int main(void) {

int c;
for (c = 0; c < 3 ; c++ )
{
fork();
}
printf("Proceso\n");
}

Redireccionar salia de errores

Saludos.

El siguiente programa redirecciona la salida estándar de errores de la pantalla hasta un fichero y después simula un error.


// Cambiar las " de los include por mayor y menos.
#include "fcntl.h"
#include "stdio.h"
#include "unistd.h"
#include "sys/file.h"
#include "sys/types.h"

int main()
{
int fp;
int fpdup;

fp = open("errores.txt", O_WRONLY | O_CREAT | O_TRUNC, 00600);
if (fp == -1)
{
perror("Error abriendo fichero.\n");
return -1;
};

fpdup=dup2(fp, 2);
printf("%i / %i\n", fp, fpdup);

close(fp);

perror("Error simulado.\n");

return 0;
}

Mostrar argumentos de la línea de comandos.

Saludos.

El siguiente código muestra uno a uno y numerados los argumentos de la línea de comandos. También podría hacerse con un bucle for hasta el valor de la variable argc.


#include "stdio.h"

int main(int argc, char *argv[])
{
int i = 0;
while(argv[i]!=NULL) {
printf("%i : %s \n", i, argv[i]);
i++;
}
return 0;
}

2.2.05

Listar la línea de argumentos

Saludos.

El siguiente código lista todos los argumentos introducidos en la línea de comandos a continuación del nombre del programa y los numera. También podría hacerse mediante un bucle for con el valor de argc.



/**
* Este programa prueba si argv termina en NULL
**/
#include

int main(int argc, char *argv[])
{
int i = 0;
while(argv[i]!=NULL) {
printf("%i : %s \n", i, argv[i]);
i++;
}
return 0;
}