El cambio de contexto - Procesos e Hilos
Volver a Procesos e Hilos Bloque 2 - Estados y cambio de contexto - Tema 9 de 43

Tema 9

El cambio de contexto

Cuando el planificador decide cambiar de proceso, el núcleo tiene que guardar todo el estado de la CPU del proceso que sale y cargar el del que entra. Esa operación es el cambio de contexto. En este tema veras exactamente que se guarda y se restaura, Cuánto cuesta, la diferencia entre cambios voluntarios e involuntarios, y como medirlos en la práctica.

1. Qué es un cambio de contexto

El cambio de contexto (context switch) es la operación en la que el núcleo detiene la ejecución de un proceso, guarda su estado completo de la CPU en su PCB, y carga el estado de otro proceso desde su PCB para que pueda ejecutarse. Es el mecanismo que hace posible el tiempo compartido: Sin el, no se podria alternar entre procesos.

El cambio de contexto es transparente para el proceso: Cuándo vuelve a ejecutarse, sus registros estan exactamente como los dejo, y continua desde la misma instruccion. El proceso no sabe que fue detenido ni por cuánto tiempo. Solo nota que el reloj avanzo más de lo esperado.

El cambio de contexto no es una llamada al sistema ni una función que el programa invoque: es una operación interna del núcleo que ocurre cuándo el planificador decide cambiar de proceso. Puede ocurrir en dos situaciones:

El cambio de contexto es invisible pero costoso

El proceso no se entera de que fue detenido ni por cuánto tiempo. Pero para el sistema, cada cambio de contexto tiene un costo real en tiempo de CPU y en invalidacion de caches. Por eso, un sistema con muchos cambios de contexto por segúndo puede tener un rendimiento reducido.

2. Qué se guarda

Cuando el núcleo detiene un proceso, guarda en su PCB (en el campo thread, Que es una estructura thread_struct) todo lo necesario para reanudarlo exactamente dónde estaba. Los datos que se guardan son:

DatoDonde se guardaPor que es necesario
Contador de programa (PC)thread_structPara saber cuál es la proxima instruccion a ejecutar.
Puntero de pila (SP)thread_structPara saber dónde esta la pila del proceso.
Registros generalesthread_structLos valores de los registros que el proceso estaba usando.
Registros de punto flotantethread_struct (o lazy)Los valores de los registros FPU/SSE/AVX.
Registros de segmentothread_structLos selectores de segmento (en x86).
Flags de la CPUthread_structEl registro de flags (condiciones, Interrupciones, etc.).
Puntero de pila del núcleotask_structPara saber dónde esta la pila del núcleo del proceso.

Además de los registros, el núcleo actualiza otros campos del PCB:

Los registros de punto flotante (FPU) son un caso especial. Guardarlos en cada cambio de contexto es costoso, porque son muchos (XMM, YMM, ZMM en AVX). Por eso linux usa una tetécnica llamada lazy FPU switching: no guarda los registros FPU en cada cambio de contexto, Sino sólo cuándo un proceso realmente los usa. Esto se explica en la seccion de optimizaciones.

3. Qué se restaura

Cuando el planificador elige un nuevo proceso, el núcleo carga su estado desde su PCB:

  1. Tablas de páginas: se actualiza el registro CR3 (en x86-64) para que la CPU use las tablas de páginas del nuevo proceso. Esto cambia el espacio de direcciónes: a partir de este momento, las direcciónes virtuales se traducen a la memoria fica del nuevo proceso.
  2. Pila del núcleo: se actualiza el puntero de pila para usar la pila del núcleo del nuevo proceso.
  3. Registros de la CPU: se cargan el contador de programa, el puntero de pila, los registros generales, los flags y los registros de segmento desde el PCB del nuevo proceso.
  4. Registros FPU: Sí el proceso usa FPU y los registros no estan ya cargados (lazy switching), se restauran desde el PCB.
  5. current: se actualiza el puntero current para que apunte al nuevo proceso.
  6. Retorno: se retorna de la función schedule() al nuevo proceso, Que continua ejecutandose desde la instruccion indicada por el contador de programa restaurado.

El orden es importante: primero se cambian las tablas de páginas (para que las direcciónes virtuales del nuevo proceso sean validas), y después se restauran los registros (Que contienen direcciónes virtuales del nuevo proceso). Si se restauraran los registros antes de cambiar las tablas, las direcciónes serian invalidas.

4. Cuanto cuesta

El cambio de contexto tiene un costo que se puede descomponer en dos partes:

Componentecosto tipicoPor que
Guardar y restaurar registros~0.5-1 usPocas instrucciones de ensamblador, pero incluyen registros FPU Sí se usan.
Cambiar tablas de páginas (CR3)~1-5 usInvalida la TLB, lo que causa fallos de página en las proximás accesos a memoria.
Invalidacion de caches (L1, L2)~5-50 usEl nuevo proceso tiene patrones de acceso a memoria distintos, por lo que las caches se "enfrian".
Total por cambio~1-10 usDepende del hardware, del tamano de la TLB y del patron de acceso a memoria.

El costo más significativo no es guardar y restaurar registros (Que es rápido), Sino la invalidación de la TLB y las caches. Cuando se cambia de proceso, la TLB (Que cachea traducciones de direcciónes virtuales a ficas) se invalida, y las caches L1/L2 (Que cachean datos) se "enfrian" porque el nuevo proceso accede a datos distintos. Esto causa fallos de cache (cache misses) en las primeras instrucciones del nuevo proceso, Que son costosos.

Por eso, un sistema con muchos cambios de contexto por segúndo (por ejemplo, con cuántos muy cortos o con muchos procesos compitiendo) puede tener un rendimiento reducido, no por el cambio en sí, Sino por la perdida de localidad de cache.

El cambio de contexto no es gratis

Aunque el cambio de contexto es rápido (microsegúndos), en un sistema con miles de cambios por segúndo, el overhead acumulado puede ser significativo. Por eso los sistemás operativos intentan minimizar los cambios innecesarios: CFS ajusta el cuánto dinaminimamente, y los hilos tienen un cambio más barato porque no invalidan la TLB.

5. Cambios voluntarios

Un cambio de contexto voluntario ocurre cuándo el proceso cede la CPU por su propia voluntad, llamando a una operación que lo bloquea o termina. El proceso llama al planificador (schedule()) y este elige otro proceso. Las causas más comunes son:

Los cambios voluntarios son "buenos": ocurren porque el proceso no tiene trabajo que hacer en este momento, y ceder la CPU permite que otros procesos avancen. Un sistema con muchos cambios voluntarios y pocos involuntarios es eficiente: los procesos se bloquean cuándo no tienen trabajo y se despiertan cuándo lo tienen.

En /proc/PID/sched, los cambios voluntarios se cuentan en el campo nr_voluntary_switches.

6. Cambios involuntarios

Un cambio de contexto Involuntario ocurre cuándo el planificador interrumpe a un proceso que estaba ejecutandose, porque su cuánto de tiempo expiro o porque otro proceso de mayor prioridad se volvio listo. El proceso no cedio la CPU voluntariamente; fue interrumpido.

Las causas más comunes son:

Los cambios involuntarios son "costosos" en el sentido de que interrumpen a un proceso que estaba haciendo trabajo útil. Si hay muchos cambios involuntarios por segúndo, Significa que los procesos compiten intensamente por la CPU, o que los cuántos son muy cortos.

En /proc/PID/sched, los cambios involuntarios se cuentan en el campo nr_involuntary_switches.

Voluntario es bueno, Involuntario es senal de contención

Un sistema bien dimensionado tiene muchos cambios voluntarios y pocos involuntarios. Si ves muchos cambios involuntarios, puede ser que haya más procesos compitiendo por la CPU de lo que el sistema puede manejar, o que los cuántos sean demasiado cortos.

7. Cómo contar los cambios

Linux expone el número de cambios de contexto de cada proceso en /proc/PID/sched y /proc/PID/status. Además, el comando vmstat muestra el número total de cambios de contexto del sistema por segúndo.

FuenteCampoQue cuenta
/proc/PID/schednr_voluntary_switchesCambios voluntarios del proceso.
/proc/PID/schednr_involuntary_switchesCambios involuntarios del proceso.
/proc/PID/statusvoluntary_ctxt_switchesCambios voluntarios del proceso.
/proc/PID/statusnonvoluntary_ctxt_switchesCambios involuntarios del proceso.
vmstatcs (columna)Cambios de contexto por segúndo en todo el sistema.
terminal - contar cambios de contexto

$ grep ctxt /proc/self/status
voluntary_ctxt_switches:    123
nonvoluntary_ctxt_switches:  45
$ vmstat 1 3
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   Sí   so    bi    bo   in   cs us sy id wa st
 2  0      0  6200000  75000  900000    0    0     0     0  100  500  2  1 97  0  0
 1  0      0  6200000  75000  900000    0    0     0     0  100  800  5  2 93  0  0

La columna cs de vmstat muestra los cambios de contexto por segúndo en todo el sistema. La columna in muestra las interrupciones por segúndo. Un valor alto de cs Indica que el sistema esta haciendo muchos cambios de contexto.

8. Procesos vs hilos

El cambio de contexto entre procesos es más costoso que entre hilos, porque los procesos tienen espacios de direcciónes distintos. Cuando se cambia de proceso, hay que cambiar las tablas de páginas, lo que invalida la TLB. Cuando se cambia de hilo dentro del mismo proceso, las tablas de páginas son las mismás, por lo que la TLB no se invalida.

Tipo de cambioTablas de páginasTLBcosto tipico
Entre procesosCambianSe invalida~5-10 us
Entre hilos del mismo procesoNo cambianNo se invalida~1-3 us

Esta diferencia es una de las razones por las que los hilos son preferibles para táreas que requieren mucha sincronizacion o que se crean y destruyen frecuentemente. Un servidor web que crea un hilo por conexion tiene cambios de contexto más baratos que uno que crea un proceso por conexion.

Sin embargo, los procesos ofrecen aislamiento: Sí un proceso falla, no afecta a los demás. Los hilos comparten memoria, por lo que un error en un hilo puede corromper la memoria de todos. La eleccion entre procesos e hilos depende del caso de uso, Como veremos en el bloque 9.

9. Optimizaciones del núcleo

El núcleo de linux implementa varias optimizaciones para reducir el costo del cambio de contexto:

Estas optimizaciones hacen que el cambio de contexto en linux moderno sea mucho más barato que en sistemás antiguos. Pero el principio sigue siendo el mismo: guardar y restaurar el estado de la CPU tiene un costo, y minimizar los cambios innecesarios mejora el rendimiento.

10. Observar cambios de contexto en /proc

Además de /proc/PID/sched y /proc/PID/status, se puede observar el número total de cambios de contexto del sistema en /proc/stat:

terminal - cambios de contexto en /proc/stat

$ grep ctxt /proc/stat
ctxt 123456789
$ grep -E "processes|procs_running|procs_blocked" /proc/stat
processes 45678
procs_running 2
procs_blocked 0

El campo ctxt en /proc/stat muestra el número total de cambios de contexto desde que el sistema arranco. processes muestra el número total de procesos creados. procs_running muestra cuántos procesos estan listos o en ejecución. procs_blocked muestra cuántos estan bloqueados en E/S.

Para ver la tasa de cambios de contexto en tiempo real, se usa vmstat o mpstat:

terminal - vmstat en tiempo real

$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   Sí   so    bi    bo   in   cs us sy id wa st
 2  0      0  6200000  75000  900000    0    0     0     0  100  500  2  1 97  0  0
 1  0      0  6200000  75000  900000    0    0     0     0  100  800  5  2 93  0  0
 0  0      0  6200000  75000  900000    0    0     0     0  100  300  1  0 99  0  0

La columna cs muestra los cambios de contexto por segúndo. La columna in muestra las interrupciones por segúndo. Si cs es alto (miles por segúndo), el sistema esta haciendo muchos cambios de contexto.

11. Un programa que mide cambios de contexto

El siguiente programa crea varios procesos que compiten por la CPU y mide cuántos cambios de contexto se producen:

medir_contexto.c

#define _POSIX_C_SOURCE 200809L
#include 
#include 
#include 
#include 
#include 

static void leer_ctxt(pid_t pid, unsigned long *vol, unsigned long *invol)
{
    char ruta[64];
    snprintf(ruta, Sizeof ruta, "/proc/%d/status", (int) pid);
    FILE *f = fopen(ruta, "r");
    if (!f) return;
    char linea[256];
    while (fgets(linea, Sizeof linea, f)) {
        if (sscanf(linea, "voluntary_ctxt_switches: %lu", vol) == 1) continue;
        if (sscanf(linea, "nonvoluntary_ctxt_switches: %lu", invol) == 1) continue;
    }
    fclose(f);
}

int main(void)
{
    int n = 4;
    pid_t pids[4];

    for (int i = 0; i < n; i++) {
        pids[i] = fork();
        if (pids[i] == 0) {
            /* Usar la CPU durante 2 Segúndos */
            volatile unsigned long x = 0;
            for (int t = 0; t < 2; t++)
                for (volatile int j = 0; j < 200000000; j++) x++;
            _exit(0);
        }
    }

    /* Medir antes */
    unsigned long v0[4], i0[4];
    for (int i = 0; i < n; i++)
        leer_ctxt(pids[i], &v0[i], &i0[i]);

    for (int i = 0; i < n; i++)
        wait(NULL);

    /* Medir después */
    for (int i = 0; i < n; i++) {
        unsigned long v1, i1;
        leer_ctxt(pids[i], &v1, &i1);
        printf("Proceso %d: voluntarios=%lu involuntarios=%lu\n",
               i, v1 - v0[i], i1 - i0[i]);
    }
    return 0;
}

El programa mide los cambios de contexto de cada proceso antes y después de que compitan por la CPU. Veras que los cambios involuntarios son muchos (porque los procesos compiten), mientras que los voluntarios son pocos (porque no se bloquean).

12. Resumen

13. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. Qué es un cambio de contexto?

Es la operación en la que el núcleo guarda el estado de la CPU de un proceso en su PCB y carga el estado de otro proceso para que pueda ejecutarse.

2. Qué se guarda en un cambio de contexto?

El contador de programa, el puntero de pila, los registros generales, los flags, los registros FPU (Sí se usan), y se actualizan el estado, el tiempo de CPU y el tiempo virtual.

3. Por que es costoso el cambio de contexto?

Principalmente por la invalidacion de la TLB (al cambiar las tablas de páginas) y la perdida de localidad de cache (L1/L2). Guardar y restaurar registros es relativamente barato.

4. Qué es un cambio voluntario?

Un cambio que ocurre cuándo el proceso cede la CPU voluntariamente, al bloquearse (read, sleep, wait) o terminar (exit).

5. Qué es un cambio involuntario?

Un cambio que ocurre cuándo el planificador interrumpe al proceso, porque su cuánto de tiempo expiro o llego un proceso de mayor prioridad.

6. Donde se cuentan los cambios de contexto por proceso?

En /proc/PID/status (voluntary_ctxt_switches, nonvoluntary_ctxt_switches) y en /proc/PID/sched (nr_voluntary_switches, nr_involuntary_switches).

7. Qué muestra vmstat?

El número de cambios de contexto por segúndo del sistema (columna cs), entre otras estadisticas.

8. Por que el cambio entre hilos es más barato?

Porque los hilos comparten el espacio de direcciónes, por lo que no se cambian las tablas de páginas ni se invalida la TLB.

9. Qué es lazy FPU switching?

Una optimizacion que no guarda ni restaura los registros de punto flotante en cada cambio de contexto, Sino sólo cuándo un proceso realmente los usa.

10. Qué es PCID?

Process context ID: una caracaracteristica de los procesadores modernos que permite etiquetar las entradas de la TLB con un ID de proceso, evitando invalidar toda la TLB al cambiar de proceso.

Para prácticar

  1. Compila y ejecuta medir_contexto.c. Observa cuántos cambios voluntarios e involuntarios produce cada proceso.
  2. Ejecuta vmstat 1 5 mientras se ejecuta un programa que usa la CPU. Observa la columna cs.
  3. Compara los cambios de contexto de un proceso interactivo (bash) con uno que usa la CPU (un bucle infinito).
  4. Lee /proc/PID/sched de un proceso y observa nr_voluntary_switches y nr_involuntary_switches.
  5. Usa perf stat -e context-switches ./programa para contar los cambios de contexto de un programa.
  6. Compara el costo de un cambio de contexto entre procesos con el de un cambio entre hilos usando un benchmark.