identificar procesos - Procesos e Hilos
← Procesos e Hilos Bloque 1 · El concepto de proceso · Tema 3 de 43

Tema 3

identificar procesos

Cada proceso que corre en el sistema tiene un número que lo identifica de manera única mientras vive: el PID. Con ese número el sistema operativo lo nombra, lo busca, le envía señales y le asigna recursos. En este tema verás que es el PID y el PPID, como obtenerlos desde un programa en C con getpid y getppid, como el núcleo los asigna y los reutiliza, y como observarlos y encontrarlos con ps, pstree, top, pgrep y /proc.

1. Qué es un PID

El PID (process identifier) es un número entero que el núcleo asigna a cada proceso en el momento de crearlo. Mientras el proceso vive, ese número lo identifica de forma única en el sistema: con él se lo nombra en las herramientas de observación, se le envían señales, se consultan sus recursos y se lo espera. Cuando el proceso termina y su padre recoge su estado, el número queda libre y puede reutilizarse para un proceso nuevo.

El PID es, en cierto sentido, el nombre público del proceso. Todo lo que el sistema hace con un proceso pasa por su PID: la shell usa el PID para esperar a un hijo, kill lo usa para enviar una señal, ps lo muestra en su primera columna, y el propio proceso puede consultarlo para saber quién es. Por eso, en un programa que crea procesos, el primer paso suele ser preguntar el PID, para saber si el código que se está ejecutando es el proceso original o uno nuevo.

Cada proceso tiene también un PPID (parent process identifier): el PID del proceso que lo creó. El PPID no cambia durante la vida del proceso, salvo en un caso especial que veremos en el bloque 6: cuando el padre termina antes que el hijo, el hijo queda huérfano y el núcleo le asigna un nuevo padre, cuyo PID se convierte en su nuevo PPID.

PID y PPID son la identidad mínima

Con saber el PID de un proceso y el de su padre ya se puede ubicar en el árbol del sistema: todo proceso cuelga de otro, y la cadena de padres lleva hasta el proceso 1. El tema 4 profundiza en el bloque de control de proceso, donde el núcleo guarda muchos más datos; pero PID y PPID son los que se usan todos los días.

2. getpid y getppid en C

POSIX ofrece dos funciones, declaradas en <unistd.h>, para consultar la identidad del proceso que las llama:

FunciónDevuelvesignificado
pid_t getpid(void)El PID del proceso que la llama«¿Quién soy yo?»
pid_t getppid(void)El PID del padre del proceso que la llama«¿Quién me creó?»

Ambas devuelven un pid_t, un tipo entero cuyo tamaño exacto no fija el estándar POSIX. En linux es un int de 32 bits, pero el estándar solo garantiza que puede representar cualquier PID válido. Por eso, al imprimirlo con printf, se convierte a int y se usa %d. Las funciones no fallan: no tienen caso de error, y siempre devuelven un valor válido.

identidad.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    printf("Soy el proceso %d\n", (int) getpid());
    printf("Mi padre es el proceso %d\n", (int) getppid());
    return 0;
}
terminal · ejecutar identidad.c
$ gcc -std=c17 -Wall -Wextra -o identidad identidad.c
$ ./identidad
Soy el proceso 612
Mi padre es el proceso 402
$ echo $$
402

El PID del proceso es 612, y su padre es 402, que coincide con el PID de la shell actual. Eso confirma lo que se vio en el tema 1: cuando lanzas un programa desde la terminal, la shell crea un proceso hijo para ejecutarlo, y ese proceso tiene como padre a la propia shell.

La variable especial $$ de la shell expande al PID de la shell, y $! al PID del último proceso lanzado en segundo plano. Son atajos útiles para scripts:

terminal · variables de la shell
$ echo "shell: $$"
shell: 402
$ sleep 100 &
[1] 615
$ echo "último hijo: $!"
último hijo: 615
$ kill $!

3. Cómo asigna el núcleo los PIDs

El núcleo no elige los PIDs al azar. Los asigna en orden creciente, empezando por el 1, y cuando llega al máximo configurado vuelve a empezar por el número más bajo que esté libre. Cada vez que se crea un proceso (con fork, clone o las llamadas equivalentes) el núcleo busca el próximo número libre y se lo asigna.

Esto tiene varias consecuencias que conviene tener presentes:

El algoritmo exacto de asignación está en el código del núcleo (la función alloc_pid), y optimiza para no reutilizar un número recién liberado demasiado pronto, para evitar confusiones con procesos que aún puedan estar referenciados. En la práctica, en un sistema normal, los PIDs suben hasta el límite y luego vuelven a empezar.

No confíes en el valor del PID

Un programa no debería tomar decisiones basándose en su propio PID ni en el de otro proceso. El número es un identificador, no un dato semántico. Si necesitas saber que programa es un proceso, mira su nombre en /proc/PID/comm o la línea de comandos en /proc/PID/cmdline, no el número.

4. El árbol de procesos y el proceso 1

Cómo cada proceso tiene un padre, el conjunto de procesos del sistema forma un árbol: la raíz es el proceso 1, y cada proceso cuelga de su padre. Ese árbol es la estructura fundamental del sistema, y se puede dibujar con pstree.

Árbol de procesos. En la raíz, el proceso 1, systemd. De él cuelgan procesos del sistema como cron, dbus-daemon y login. De login cuelga bash, la shell del usuario, y de bash cuelgan los programas que el usuario lanza, como gcc, un editor y un servidor web. El servidor web, a su vez, creó varios procesos hijos trabajadores. systemd (1) cron (151) dbus (152) login (332) bash (402) gcc (500) vim (501) servidor (502) 503 504 505
Cada proceso tiene un padre, y la cadena de padres lleva hasta el proceso 1. Un servidor web que crea procesos trabajadores forma una rama del árbol.

El proceso 1 es el primero que crea el núcleo al arrancar. En los sistemas modernos basados en systemd, ese proceso es systemd; en sistemas más antiguos o minimalistas, puede ser init. Su PID es siempre 1, y es el antepasado de todos los demás procesos. Cuando un proceso queda huérfano (su padre termina antes que él), el núcleo le reasigna como padre al proceso 1, o a un subreaper que haya tomado ese rol. El proceso 1 tiene la responsabilidad de recoger los procesos huérfanos que terminan, para que no queden como zombis.

El proceso 0, que no aparece en las herramientas habituales, es el proceso inactivo (idle) del núcleo: no es un proceso normal, sino el contexto que se ejecuta cuando no hay ningún proceso listo para correr. Por eso los PIDs de usuario empiezan en 1.

5. Observar procesos con ps

ps (process status) es la herramienta clásica para listar procesos. Por omisión muestra solo los procesos de la terminal actual, pero con opciones se puede ver todo el sistema. Las dos formas más usadas son:

terminal · ps básico
$ ps
    PID TTY          TIME CMD
    402 pts/0    00:00:00 bash
    612 pts/0    00:00:00 ps

Para ver todos los procesos del sistema con información detallada se usa ps aux o ps -ef. Las dos muestran PID y PPID, pero con columnas distintas:

terminal · ps aux (salida recortada)
$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1 168960 12832 ?        Ss   19:30   0:01 /sbin/init
root       151  0.0  0.0   8500  3900 ?        Ss   19:30   0:00 /usr/sbin/cron
ana        402  0.0  0.1   8500  4500 pts/0    Ss   19:40   0:00 -bash
ana        613  0.0  0.0   9200  3200 pts/0    R+   19:45   0:00 ps aux
terminal · ps -ef (salida recortada)
$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 19:30 ?        00:00:00 /sbin/init
root       151     1  0 19:30 ?        00:00:00 /usr/sbin/cron
ana        402   332  0 19:40 pts/0    00:00:00 -bash
ana        614   402  0 19:45 pts/0    00:00:00 ps -ef

La diferencia clave: ps -ef muestra el PPID de forma explícita, lo que permite ver la relación padre-hijo de un vistazo. ps aux muestra el uso de CPU y memoria, pero no el PPID directamente (aunque se puede pedir con ps auxo pid,ppid,...).

Para pedir columnas específicas se usa la opción -o, con una lista separada por comas. Es la forma más flexible y la que más se usa en scripts:

terminal · columnas a medida
$ ps -o pid,ppid,stat,etime,comm -p 1,402
    PID    PPID STAT     ELAPSED COMMAND
      1       0 Ss       00:15:32 systemd
    402     332 Ss       00:05:12 bash

Las columnas más útiles para identificar procesos:

Columnasignificado
PIDidentificador del proceso.
PPIDidentificador del padre.
UID / USERUsuario que ejecuta el proceso.
STATEstado: R ejecutándose, S dormido, D espera de disco, T detenido, Z zombi. El tema 7 los detalla.
TTYTerminal de control del proceso, o ? si no tiene.
TIME / TIME+Tiempo de CPU acumulado.
ELAPSED / etimeTiempo transcurrido desde que se creó el proceso.
COMMAND / comm / cmdNombre del programa o línea de comandos completa.
STARTHora en que se creó.

Para filtrar por nombre de programa se usa -C, y para filtrar por PID exacto, -p. Combinado con --no-headers y wc -l, sirve para contar:

terminal · filtrar y contar
$ ps -C bash -o pid,ppid,comm
    PID    PPID COMMAND
    402     332 bash
    520     402 bash
$ ps -e --no-headers | wc -l
47

6. pstree: el árbol completo

pstree dibuja el árbol de procesos del sistema, con cada proceso colgando de su padre. Es la herramienta ideal para entender la estructura del sistema de un vistazo, y para ver que procesos creó un servidor o un demonio.

terminal · pstree (salida recortada)
$ pstree -p
systemd(1)─┬─cron(151)
           ├─dbus-daemon(152)
           ├─login(332)───bash(402)─┬─gcc(500)
           │                         ├─vim(501)
           │                         └─servidor(502)─┬─worker(503)
           │                                           ├─worker(504)
           │                                           └─worker(505)
           └─systemd-journal(44)

Las opciones más útiles:

terminal · ancestros de un proceso
$ pstree -s -p 503
systemd(1)───login(332)───bash(402)───servidor(502)───worker(503)

Los nombres entre llaves, como {rsyslogd}, que viste en el tema 1, no son procesos sino hilos dentro de un proceso. pstree los muestra por omisión; con -T se ocultan y solo se ven los procesos.

7. top y htop: monitoreo en tiempo real

Mientras ps toma una fotografía instantánea, top muestra los procesos en tiempo real, actualizándose cada pocos segundos, ordenados por uso de CPU por omisión. Es la herramienta que se usa para ver que está consumiendo la máquina en este momento.

terminal · top (salida recortada)
$ top
top - 19:50:00 up 20 min, 1 user, load average: 0.05, 0.02, 0.01
Tasks:  47 total,   1 running,  46 sleeping,   0 stopped,   0 zombie
%Cpu(s):  2.3 us,  0.7 sy,  0.0 ni, 96.8 id,  0.2 wa,  0.0 hi,  0.2 si
MiB Mem :   7850.0 total,   6200.0 free,    900.0 used,    750.0 buff/cache

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    618 ana       20   0    9000   3200   2800 R   0.3   0.0   0:00.01 top
    402 ana       20   0    8500   4500   3900 S   0.0   0.1   0:00.05 bash

Las teclas más útiles dentro de top:

htop es una versión mejorada, con colores, barras de uso por núcleo y navegación con el teclado y el ratón. No viene instalado por omisión en todos los sistemas, pero se instala con apt install htop. Ambas herramientas leen los mismos datos de /proc que ps, así que los PIDs coinciden.

8. /proc y los identificadores

Cómo viste en los temas anteriores, el núcleo publica los datos de cada proceso en /proc/PID. Para identificar un proceso, los archivos más directos son:

ArchivoQue contiene
/proc/PID/statusEntre otras cosas, las líneas Name, Pid, PPid, Tgid y Threads.
/proc/PID/statUna sola línea con todos los campos del proceso, incluido el PID y el PPID, en formato compacto para programas.
/proc/PID/cmdlineLa línea de comandos con la que se lanzó, con los argumentos separados por bytes nulos.
/proc/PID/commEl nombre corto del programa (hasta 15 caracteres).
/proc/PID/exeEnlace al archivo ejecutable.
/proc/selfEnlace al directorio del proceso que lo lee.
terminal · identificar un proceso en /proc
$ grep -E '^(Name|Pid|PPid|Tgid|Threads)' /proc/402/status
Name:	bash
Pid:	402
PPid:	332
Tgid:	402
Threads:	1
$ cat /proc/402/comm
bash
$ tr '\0' ' ' < /proc/402/cmdline; echo
-bash 

El campo Tgid (thread group id) es el identificador del grupo de hilos al que pertenece el proceso. En un proceso con un solo hilo, el tgid coincide con el PID. En un proceso con varios hilos, todos los hilos comparten el mismo tgid, que es el PID del proceso. Esta distinción es la que permite diferenciar procesos de hilos, y la veremos en el apartado siguiente.

9. Encontrar procesos: pgrep y pidof

Cuando se necesita el PID de un proceso por su nombre, en lugar de leer ps y filtrar, se usan pgrep y pidof:

terminal · pgrep y pidof
$ pgrep bash
402
520
$ pgrep -a bash
402 -bash
520 bash
$ pidof bash
520 402

pgrep busca procesos por nombre (o por otros criterios con opciones como -u para usuario, -f para buscar en la línea de comandos completa, -x para coincidencia exacta) y devuelve solo los PIDs, uno por línea. Es ideal para usar en scripts:

terminal · pgrep en un script
$ for p in $(pgrep -f "sleep 100"); do echo "matando $p"; kill "$p"; done

pidof es más simple: busca por nombre exacto del programa y devuelve todos los PIDs en una sola línea. Es útil cuando se conoce el nombre exacto y se quiere pasar la lista a otra herramienta, como kill.

10. El límite de PIDs y pid_max

El número máximo de PIDs que el núcleo puede asignar está en /proc/sys/kernel/pid_max. En un sistema de 64 bits el valor por omisión suele ser 4194304 (222); en sistemas de 32 bits, 32768. Se puede consultar y cambiar (con privilegios):

terminal · pid_max
$ cat /proc/sys/kernel/pid_max
4194304
$ echo 100000 | sudo tee /proc/sys/kernel/pid_max

Cuando el núcleo llega a pid_max, vuelve a empezar por el número más bajo libre. Si todos los números están en uso (algo que solo ocurre en sistemas con millones de procesos o con un límite muy bajo), la creación de nuevos procesos falla con EAGAIN. Es el mismo error que aparece en una fork bomb, que veremos en el tema 15.

Hay también un límite por usuario: ulimit -u muestra el número máximo de procesos que un usuario puede tener a la vez. Es una protección contra que un usuario agote los recursos del sistema:

terminal · límite por usuario
$ ulimit -u
31263

11. Hilos y PIDs: TID frente a PID

En linux, los hilos se implementan como procesos ligeros que comparten memoria. Cada hilo tiene su propio identificador, el TID (thread identifier), que el núcleo asigna con la misma secuencia que los PIDs. Pero todos los hilos de un mismo proceso comparten el mismo PID desde el punto de vista del usuario: ese PID es el del proceso que los contiene, y se llama TGID (thread group id).

Por eso, cuando un programa con varios hilos llama a getpid(), todos los hilos obtienen el mismo número: el PID del proceso. En cambio, si un hilo quiere saber su propio TID, debe usar una llamada específica del sistema, gettid(), que no tiene envoltura en la biblioteca de C y se invoca con syscall(SYS_gettid):

hilos_id.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <pthread.h>

void *trabajo(void *arg)
{
    printf("hilo:  PID=%d  TID=%ld\n",
           (int) getpid(), (long) syscall(SYS_gettid));
    return NULL;
}

int main(void)
{
    pthread_t t;
    printf("main:  PID=%d  TID=%ld\n",
           (int) getpid(), (long) syscall(SYS_gettid));
    pthread_create(&t, NULL, trabajo, NULL);
    pthread_join(t, NULL);
    return 0;
}
terminal · PID y TID
$ gcc -std=c17 -Wall -Wextra -pthread -o hilos_id hilos_id.c
$ ./hilos_id
main:  PID=701  TID=701
hilo:  PID=701  TID=702

El hilo principal tiene TID igual al PID del proceso (701). El hilo creado tiene un TID distinto (702), pero el mismo PID. Por eso, en /proc, cada hilo aparece como un directorio numerado con su TID, pero todos comparten el mismo espacio de memoria y el mismo PID desde el punto de vista del usuario. El tema 29 profundiza en los modelos de hilos y en como linux los implementa con clone.

Por eso, al listar procesos con ps, por omisión solo se ven los hilos principales. Para ver todos los hilos se usa ps -L o top -H:

terminal · ver hilos
$ ps -L -p 701 -o pid,tid,comm
    PID     TID COMMAND
    701     701 hilos_id
    701     702 hilos_id

12. Un programa que se identifica

El siguiente programa muestra como un proceso puede consultar su identidad, la de su padre, y verificarla contra /proc. Es útil para entender la relación entre lo que el programa ve y lo que el sistema muestra:

identidad_completa.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    pid_t pid = getpid();
    pid_t ppid = getppid();

    printf("PID propio:   %d\n", (int) pid);
    printf("PID del padre:%d\n", (int) ppid);

    /* verificar contra /proc */
    char ruta[64];
    snprintf(ruta, sizeof ruta, "/proc/%d/status", (int) pid);
    FILE *f = fopen(ruta, "r");
    if (f) {
        char linea[256];
        while (fgets(linea, sizeof linea, f)) {
            if (strncmp(linea, "Name:", 5) == 0 ||
                strncmp(linea, "Pid:", 4) == 0 ||
                strncmp(linea, "PPid:", 5) == 0) {
                fputs(linea, stdout);
            }
        }
        fclose(f);
    }

    printf("\ncomprueba con: ps -p %d -o pid,ppid,comm\n", (int) pid);
    return 0;
}

Al ejecutarlo, la salida del programa coincide con lo que /proc reporta, y el comando sugerido al final permite verificarlo desde otra terminal. Es un buen ejercicio para confirmar que el proceso ve la misma identidad que el sistema le asigna.

13. Resumen

14. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Que es el PID de un proceso?

Es el número entero que el núcleo asigna a un proceso al crearlo y que lo identifica de forma única mientras vive. Cuando el proceso termina y su padre recoge su estado, el número se libera y puede reutilizarse.

2. ¿Que devuelve getppid()?

El PID del proceso que creó al proceso que la llama: su padre. No cambia durante la vida del proceso, salvo cuando el padre termina antes y el hijo queda huérfano, en cuyo caso el núcleo le asigna un nuevo padre.

3. ¿Por que no conviene tomar decisiones basándose en el valor del PID?

Porque el PID es solo un identificador numérico: no codifica el tipo de programa, el usuario ni la prioridad, y se reutiliza. Para saber que programa es un proceso hay que mirar su nombre o su línea de comandos en /proc.

4. ¿Que proceso tiene PID 1, y por que es especial?

El proceso 1 es el primero que crea el núcleo al arrancar (systemd o init). Es la raíz del árbol de procesos, antepasado de todos los demás, y el encargado de recoger los procesos huérfanos que terminan.

5. ¿Que opción de ps muestra el PPID de cada proceso?

ps -ef lo muestra en la columna PPID. También se puede pedir con ps -o pid,ppid,comm o agregarlo a ps aux con ps axo user,pid,ppid,...,comm.

6. ¿Para que sirve pstree -s PID?

Muestra la cadena de ancestros del proceso indicado: desde el proceso 1 hasta ese proceso, pasando por cada padre. Es útil para entender de dónde vino un proceso.

7. ¿Que diferencia hay entre PID y TID?

El PID identifica al proceso (el grupo de hilos) y es el que devuelven getpid() y las herramientas de usuario. El TID identifica a un hilo individual dentro del proceso. Todos los hilos de un proceso comparten el mismo PID (TGID) pero tienen TID distintos.

8. ¿Como se obtiene el TID de un hilo en C?

Con syscall(SYS_gettid), incluyendo <sys/syscall.h>, porque la biblioteca de C no ofrece una envoltura para gettid. El hilo principal tiene TID igual al PID del proceso.

9. ¿Que es pid_max y dónde se consulta?

Es el número máximo de PIDs que el núcleo puede asignar. Se consulta en /proc/sys/kernel/pid_max, y cuando se alcanza, el núcleo vuelve a empezar por el número más bajo libre.

10. ¿Que muestra pgrep -a que no muestra pgrep solo?

La línea de comandos completa de cada proceso encontrado, además del PID. Sin -a, pgrep devuelve solo los números.

Para practicar

  1. Ejecuta identidad.c y verifica con ps -p PID -o pid,ppid,comm que el PPID coincide con el de la shell.
  2. Lanza un programa en segundo plano y usa echo $! para obtener su PID; luego míralo con ps y /proc/PID/status.
  3. Dibuja el árbol de procesos de tu sistema con pstree -p y encuentra el camino desde el proceso 1 hasta la shell en la que trabajas.
  4. Usa pgrep -f para encontrar todos los procesos que ejecutan un comando que contenga una palabra determinada.
  5. Compila y ejecuta hilos_id.c, y mientras corre, mira ps -L -p PID y /proc/PID/task.
  6. Consulta cat /proc/sys/kernel/pid_max y ulimit -u en tu sistema, y compáralos con los valores del texto.