En esta práctica vas a comparar el modelo de procesos de Unix con el modelo de hilos POSIX, observando cómo afectan a la memoria, a la planificación, a la sincronización y a la salida por pantalla. Trabajarás con dos programas base (procesos.c y hilos.c) y, a partir de ellos, realizarás pequeñas modificaciones y ejercicios guiados.

Procesos en Unix/C

Los procesos son instancias independientes de un programa en ejecución. Cada proceso tiene su propio espacio de memoria, por lo que no comparten variables ni datos directamente.

fork():

  • Crea un nuevo proceso hijo duplicando el proceso padre.
  • Ambos procesos continúan la ejecución desde el mismo punto.
  • Devuelve:
    • 0 en el hijo.
    • El PID del hijo en el padre.
    • -1 si ocurre un error.

wait():

  • El proceso padre se bloquea hasta que uno de sus hijos termina.
  • Permite obtener el estado de salida del hijo (valor devuelto con exit).
  • Se usa para coordinar procesos y evitar procesos zombies.

Ejercicio 1

Estudia el siguiente programa:

/*
 * procesos.c
 * Compilación: cc -o procesos procesos.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#define NUM_PROCESOS 5

int I = 0;

void codigo_del_proceso(int id) {
  int i;

  for (i = 0; i < 50; i++)
    printf("Proceso(id=%d): i = %d, I = %d\n", id, i, I++);

  // el id se almacena en los bits 8 al 15 antes de devolverlo al padre
  exit(id);
}

int main() {
  int p;
  int id[NUM_PROCESOS] = {1, 2, 3, 4, 5};
  int pid;
  int salida;

  for (p = 0; p < NUM_PROCESOS; p++) {
    pid = fork();
    if (pid == -1) {
      perror("Error al crear un proceso: ");
      exit(-1);
    } else if (pid == 0) // Codigo del hijo
      codigo_del_proceso(id[p]);
  }

  // Codigo del padre
  for (p = 0; p < NUM_PROCESOS; p++) {
    pid = wait(&salida);
    printf("Proceso(pid=%d) con id = %x terminado y status = %d \n", pid,
           salida >> 8, WEXITSTATUS(salida));
  }
  return 0;
}
  1. Comenta qué se espera que ocurra en cada porción de código y la salida. ¿Qué crees que hace WEXITSTATUS? ¿Es una función o una macro del preprocesador de C?
  2. ¿Que hace la expresión salida >> 8? ¿Crees que le puede faltar algo? ¿Por qué?
  3. Ahora edita y ejecuta un programa de ejemplo con dos procesos concurrentes que impriman en pantalla 1 y 2 respectivamente.
  4. Transforma el código anterior para que definamos una única función a la que se le pase como parámetro el valor entero que se desea imprimir. Instancia a continuación dos procesos que ejecuten dicha función a la que le pasaremos como parámetro un 1 y un 2 respectivamente.
  5. Implementa un programa que contenga una función imprimir que imprima un carácter cualquiera 5 veces. En el programa principal debemos crear 3 procesos concurrentes que impriman, utilizando la función imprimir, una ‘A’, una ‘B’ y una ‘C’ respectivamente.

Hilos C (POSIX threads)

Los hilos son unidades de ejecución más ligeras dentro de un proceso. Todos los hilos de un mismo proceso comparten memoria, por lo que es más fácil comunicarlos, aunque también más peligroso si no se sincronizan bien.

pthread_create():

  • Crea un nuevo hilo dentro del proceso actual.
  • Necesita:
    • Una variable pthread_t para guardar el identificador del hilo.
    • Atributos (pueden ser NULL).
    • La función que ejecutará el hilo.
    • Un puntero a los argumentos que se le pasan a esa función.

pthread_join():

  • Hace que un hilo espere a que otro termine.
  • Recupera el valor de salida de la función que ejecutaba ese hilo.
  • Es útil para coordinar el final de la ejecución y recoger resultados.

Ejercicio 2

Estudia el siguiente programa:

/*
 * hilos.c
 * Compilación: cc -o hilos hilos.c -lpthread
 */

#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define NUM_HILOS 5

int I = 0;

void *codigo_del_hilo (void *id){
   int i;
   for( i = 0; i < 50; i++)
      printf("Hilo %d: i = %d, I = %d\n", *(int *)id, i, I++);
   pthread_exit (id);
}

int main(){
   int h;
   pthread_t hilos[NUM_HILOS];
   int id[NUM_HILOS] = {1,2,3,4,5};
   int error;
   int *salida;

   for(h = 0; h < NUM_HILOS; h++){
      error = pthread_create( &hilos[h], NULL, codigo_del_hilo, &id[h]);
      if (error){
        fprintf (stderr, "Error: %d: %s\n", error, strerror (error));
        exit(-1);
      }
   }
   for(h =0; h < NUM_HILOS; h++){
      error = pthread_join(hilos[h], (void **)&salida);
      if (error)
         fprintf (stderr, "Error: %d: %s\n", error, strerror (error));
      else
         printf ("Hilo %d terminado\n", *salida);
   }
}

Si no comprendes bien lo que son los punteros void* presta atención a este vídeo.

  1. Comenta qué se espera que ocurra en cada porción de código y la salida. Comenta a continuación las diferencias más importantes entre este programa y el equivalente con procesos de la sesión 1.
  2. Implementa un programa que contenga una función imprimir que imprima un carácter n veces. A la función se le debe pasar como parámetro una estructura en la que deben ir encapsulados el carácter y n. En el programa principal debemos crear 3 hilos concurrentes que impriman una ‘A’ 50 veces, una ‘B’ 100 veces y una ‘C’ 150 veces respectivamente.
  3. ¿Qué diferencias observas entre la versión que usa procesos y esta versión que hace uso de hilos?

Entrega

  • Se realiza en pracdlsi en las fechas allí indicadas. Puedes entregar tantas veces como quieras, solo se corrige la ultima entrega.
  • Crea una carpeta llamada p1 y dentro de ella estarán el código y archivos de texto o PDF donde contestas a las preguntas. Esta carpeta la comprimes en un archivo llamado p1.tgz p.e. así usando el terminal: tar cfz p1.tgz p1