Saltar al contenido principal

Aplicación, procesos y threads

nota

Este curso está basado en el excelente libro Operating Systems: Three Easy Pieces de Remzi H. Arpaci-Dusseau y Andrea C. Arpaci-Dusseau.

Aplicación y proceso​

El término aplicación es un concepto general que designa un programa ejecutado por un usuario. No es un término técnico preciso, pero a menudo se utiliza para referirse a un programa que interactúa con el usuario, como un editor de texto, un navegador web o un juego.

Un proceso es una instancia de un programa en ejecución. Representa el entorno de ejecución de la aplicación, incluyendo sus recursos y su estado. Un proceso se crea cuando se inicia una aplicación y puede finalizar cuando la aplicación se cierra. Un mismo programa puede ser ejecutado varias veces, creando así varios procesos distintos.

De forma más formal, al principio un programa no es más que un archivo binario ejecutable almacenado en el disco. Este archivo binario contiene:

  • El código máquina de la aplicación, que es el código ejecutable por el procesador.
  • Los datos estáticos, que son variables globales y constantes utilizadas por la aplicación.
  • Los recursos necesarios para la ejecución de la aplicación, como las bibliotecas compartidas y los archivos de configuración.

Un proceso se crea cuando el sistema operativo carga el archivo binario en memoria e inicializa el entorno de ejecución. Así que, de manera figurada, un proceso es una copia del programa en memoria, con su propio espacio de direcciones, sus propios recursos y su propio estado.

Entorno de ejecución​

El entorno de ejecución de un proceso incluye:

  • Un identificador de proceso (PID), que es un número único asignado por el sistema operativo para identificar el proceso.
  • El espacio de direcciones, que es la memoria asignada al proceso para almacenar su código, sus datos y su pila de ejecución.
  • Los descriptores de archivos, que son referencias a los archivos abiertos por el proceso.
  • Las variables de entorno, que son pares clave-valor utilizadas para configurar el comportamiento del proceso.
  • Las señales, que son notificaciones enviadas al proceso para indicarle que ha ocurrido un evento, como una interrupción o un error.
  • Los threads, que son unidades de ejecución ligeras dentro de un proceso. Un proceso puede tener varios threads que comparten el mismo espacio de direcciones y los mismos recursos, pero que se ejecutan independientemente unos de otros.

Espacio de direcciones​

Cuando se crea un proceso, el sistema operativo le asigna un espacio de direcciones, que es un rango de direcciones de memoria que el proceso puede utilizar para almacenar su código, sus datos y su pila de ejecución. Este espacio de direcciones está aislado de los demás procesos, lo que significa que un proceso no puede acceder directamente a la memoria de otro proceso.

De hecho, desde el punto de vista del programa, este dispone de la memoria completamente a su disposición, sin preocuparse por los demás procesos. La memoria se divide en algunos grandes segmentos:

  • El segmento de código, que contiene el código ejecutable del programa.
  • El segmento de datos, que contiene las variables globales y las estructuras de datos estáticas.
  • El segmento de pila, que se utiliza para almacenar las variables locales y los parámetros de las funciones.
  • El segmento de heap, que se utiliza para la asignación dinámica de memoria (por ejemplo, con malloc en C o new en C++).

Compartición de la CPU​

Es muy probable que tenga muchos más procesos activos que núcleos de procesador en su máquina. Por lo tanto, es necesario encontrar una manera de compartir la CPU entre los diferentes procesos de forma transparente para el usuario.

La idea principal es hacer creer a cada proceso que tiene la CPU completamente para sí mismo. Para ello, el sistema operativo utiliza un mecanismo llamado planificación (o scheduling en inglés). La planificación consiste en asignar intervalos de tiempo (o quanta) a cada proceso, de modo que cada proceso pueda ejecutarse durante un cierto tiempo antes de ser interrumpido y dar paso a otro proceso.

Cuando un proceso es interrumpido, su estado se guarda en el sistema operativo, de modo que pueda reanudar su ejecución más tarde desde el mismo punto. El sistema operativo utiliza un algoritmo de planificación para decidir qué proceso debe ejecutarse en un momento dado. Si alguna vez ha escuchado el término "cambio de contexto" ("context switch"), eso es exactamente lo que significa: el sistema operativo guarda el estado del proceso actual y lo restaura más tarde para reanudar su ejecución.

También es el sistema operativo quien gestiona qué proceso se ejecuta en qué núcleo de procesador. Puede mover un proceso de un núcleo a otro para optimizar el uso de los recursos del sistema. Esto permite distribuir la carga de trabajo entre los diferentes núcleos y mejorar el rendimiento del sistema.

Como usuario, no tiene que preocuparse por la planificación de los procesos, ya que el sistema operativo se encarga de ello automáticamente. Sin embargo, puede influir en la planificación modificando la prioridad de los procesos o su afinidad (es decir, el núcleo de procesador en el que deben ejecutarse).

En Windows, puede utilizar el administrador de tareas (pestaña Detalles) para modificar la prioridad de un proceso o su afinidad.

Administrador de tareas de Windows

Procesos y threads​

Su sistema operativo trabaja arduamente para aislar los procesos unos de otros, pero hay casos en los que es deseable que un mismo programa pueda ejecutar varias tareas en paralelo. También es más sencillo a veces que ciertas tareas compartan recursos, como la memoria, en lugar de duplicar estos recursos para cada tarea. Es aquí donde entran en juego los threads.

Un thread es una unidad de ejecución ligera dentro de un proceso. Un proceso puede tener varios threads que comparten el mismo espacio de direcciones (memoria) a excepción de la pila, pero que se ejecutan independientemente unos de otros. Así que si tiene un proceso con varios threads, estos threads pueden ejecutarse en paralelo en varios núcleos de procesador, lo que permite mejorar el rendimiento del programa. Incluso sin tener varios núcleos de procesador, los threads permiten un mejor uso de la CPU al evitar tiempos de espera innecesarios, por ejemplo, al esperar una respuesta de la red o al realizar operaciones de entrada/salida.

Así que se puede ver un thread como un proceso ligero que comparte la memoria del proceso padre. También está gestionado por la planificación del sistema operativo. Así, como los procesos, generalmente es difícil predecir exactamente cuándo se ejecutará un thread, ya que esto depende de la planificación del sistema operativo y de la carga de trabajo actual del sistema.

Kernel threads y user threads​

De manera un poco confusa, existe otro concepto de thread. De hecho, se pueden distinguir los threads gestionados por el sistema operativo (llamados kernel threads) y los threads gestionados por la aplicación (llamados user threads). Los kernel threads son gestionados por el sistema operativo y son planificados por el núcleo. Corresponden a la descripción anterior. Pueden ejecutarse en paralelo en varios núcleos de procesador y están aislados unos de otros (excepto por la memoria).

Los user threads (green threads, coroutines en Go, fibers en Rust, etc.) son gestionados por la aplicación y son planificados por la propia aplicación. Generalmente son más ligeros que los kernel threads, ya que no requieren gestión por parte del sistema operativo. Sin embargo, no pueden ejecutarse en paralelo en varios núcleos de procesador, ya que son planificados por la aplicación y no por el sistema operativo. Es un poco como si el proceso dividiera él mismo su tiempo e hiciera su propia planificación. Pero todo esto dentro de un solo proceso o thread.

Algunos lenguajes como Go mezclan ambos conceptos. Permiten tener M corutinas (user threads) que se ejecutan sobre N threads (kernel threads). Es el entorno de ejecución de Go quien gestiona la planificación de las corutinas sobre los threads. Esto permite una gestión fina de los recursos y al mismo tiempo aprovechar la planificación del sistema operativo y el paralelismo multinúcleo.