semaforos - Distinción entre procesos y hilos en Linux.
pthread c (3)
Después de leer esta respuesta y el "Desarrollo del Kernel de Linux" por Robert Love y, posteriormente, en la llamada al sistema clone()
, descubrí que los procesos y subprocesos en Linux son (casi) indistinguibles para el kernel. Hay algunos ajustes entre ellos (discutidos como "más compartir" o "menos compartir" en la pregunta SO citada), pero todavía tengo algunas preguntas por responder.
Recientemente trabajé en un programa que incluía un par de subprocesos POSIX y decidí experimentar con esta premisa. En un proceso que crea dos hilos, todos los hilos, por supuesto, obtienen un valor único devuelto por pthread_self()
, sin embargo , no por getpid()
.
Un programa de ejemplo que creé sigue:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <pthread.h>
void* threadMethod(void* arg)
{
int intArg = (int) *((int*) arg);
int32_t pid = getpid();
uint64_t pti = pthread_self();
printf("[Thread %d] getpid() = %d/n", intArg, pid);
printf("[Thread %d] pthread_self() = %lu/n", intArg, pti);
}
int main()
{
pthread_t threads[2];
int thread1 = 1;
if ((pthread_create(&threads[0], NULL, threadMethod, (void*) &thread1))
!= 0)
{
fprintf(stderr, "pthread_create: error/n");
exit(EXIT_FAILURE);
}
int thread2 = 2;
if ((pthread_create(&threads[1], NULL, threadMethod, (void*) &thread2))
!= 0)
{
fprintf(stderr, "pthread_create: error/n");
exit(EXIT_FAILURE);
}
int32_t pid = getpid();
uint64_t pti = pthread_self();
printf("[Process] getpid() = %d/n", pid);
printf("[Process] pthread_self() = %lu/n", pti);
if ((pthread_join(threads[0], NULL)) != 0)
{
fprintf(stderr, "Could not join thread 1/n");
exit(EXIT_FAILURE);
}
if ((pthread_join(threads[1], NULL)) != 0)
{
fprintf(stderr, "Could not join thread 2/n");
exit(EXIT_FAILURE);
}
return 0;
}
(Esto fue compilado [ gcc -pthread -o thread_test thread_test.c
] en Fedora de 64 bits; debido a los tipos de 64 bits utilizados para pthread_t
procedente de <bits/pthreadtypes.h>
, el código requerirá cambios menores para compilar Ediciones de 32 bits.)
La salida que obtengo es la siguiente:
[bean@fedora ~]$ ./thread_test
[Process] getpid() = 28549
[Process] pthread_self() = 140050170017568
[Thread 2] getpid() = 28549
[Thread 2] pthread_self() = 140050161620736
[Thread 1] getpid() = 28549
[Thread 1] pthread_self() = 140050170013440
[bean@fedora ~]$
Al usar el programador de bloqueo en gdb
, puedo mantener el programa y sus subprocesos vivos para poder capturar lo que dice top
, que, simplemente mostrando procesos , es:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
28602 bean 20 0 15272 1112 820 R 0.4 0.0 0:00.63 top
2036 bean 20 0 108m 1868 1412 S 0.0 0.0 0:00.11 bash
28547 bean 20 0 231m 16m 7676 S 0.0 0.4 0:01.56 gdb
28549 bean 20 0 22688 340 248 t 0.0 0.0 0:00.26 thread_test
28561 bean 20 0 107m 1712 1356 S 0.0 0.0 0:00.07 bash
Y al mostrar hilos, dice:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
28617 bean 20 0 15272 1116 820 R 47.2 0.0 0:00.08 top
2036 bean 20 0 108m 1868 1412 S 0.0 0.0 0:00.11 bash
28547 bean 20 0 231m 16m 7676 S 0.0 0.4 0:01.56 gdb
28549 bean 20 0 22688 340 248 t 0.0 0.0 0:00.26 thread_test
28552 bean 20 0 22688 340 248 t 0.0 0.0 0:00.00 thread_test
28553 bean 20 0 22688 340 248 t 0.0 0.0 0:00.00 thread_test
28561 bean 20 0 107m 1860 1432 S 0.0 0.0 0:00.08 bash
Parece ser bastante claro que los programas, o quizás el núcleo, tienen una forma distinta de definir subprocesos en contraste con los procesos. Cada hilo tiene su propio PID según la top
, ¿por qué?
En Linux, cada hilo obtiene un ID de hilo . El ID de hilo del hilo principal cumple una doble función como ID de proceso (y es bastante conocido en la interfaz de usuario). El ID de hilo es un detalle de implementación de Linux y no está relacionado con el ID de POSIX. Para obtener más detalles, consulte la gettid sistema gettid (no disponible en Python puro, ya que es específico del sistema).
Imagina algún tipo de "meta-entidad". Si la entidad no comparte ninguno de los recursos (espacio de direcciones, descriptores de archivo, etc.) de su principal, entonces es un proceso, y si la entidad comparte todos los recursos de su principal, entonces es un hilo. Incluso podría tener algo a medio camino entre el proceso y el subproceso (por ejemplo, algunos recursos compartidos y otros no compartidos). Eche un vistazo a la llamada al sistema "clone ()" (por ejemplo, http://linux.die.net/man/2/clone ) y verá cómo Linux hace las cosas internamente.
Ahora oculte eso detrás de algún tipo de abstracción que hace que todo se vea como un proceso o un hilo. Si la abstracción es perfecta, nunca se sabe la diferencia entre "entidades" y "procesos y subprocesos". Sin embargo, la abstracción no es del todo impecable: el PID que está viendo es en realidad un "ID de entidad".
Todas estas confusiones se derivan del hecho de que los desarrolladores del kernel originalmente tenían una opinión irracional e incorrecta de que los subprocesos podían implementarse casi por completo en el espacio de usuario usando procesos del kernel como primitivos, siempre que el kernel ofreciera una forma de hacer que compartan memoria y descriptores de archivos . Esto llevó a la notoriamente mala implementación de LinuxThreads de subprocesos POSIX, que era más bien un nombre inapropiado porque no daba nada remotamente parecido a la semántica de subprocesos POSIX. Finalmente, LinuxThreads fue reemplazado (por NPTL), pero persisten muchos de los confusos términos y malentendidos.
Lo primero y más importante es darse cuenta de que "PID" significa diferentes cosas en el espacio del kernel y en el espacio del usuario. Lo que el kernel llama a los PID son en realidad identificadores de subproceso a nivel de kernel (a menudo llamados TID), que no deben confundirse con pthread_t
que es un identificador separado. Cada hilo en el sistema, ya sea en el mismo proceso o en uno diferente, tiene un TID único (o "PID" en la terminología del kernel).
Lo que se considera un PID en el sentido POSIX de "proceso", por otro lado, se denomina "ID de grupo de hilos" o "TGID" en el kernel. Cada proceso consta de uno o más subprocesos (procesos del kernel), cada uno con su propio TID (PID del kernel), pero todos comparten el mismo TGID, que es igual al TID (PID del kernel) del subproceso inicial en el que se ejecuta main
.
Cuando la top
muestra los subprocesos, muestra los TID (PID del kernel), no los PID (TGID del kernel), y esta es la razón por la que cada subproceso tiene uno separado.
Con la llegada de NPTL, la mayoría de las llamadas del sistema que toman un argumento PID o actúan sobre el proceso de llamada se cambiaron para tratar el PID como un TGID y actuar en todo el "grupo de hilos" (proceso POSIX).