2012-02-06 19 views
17

Después de leer en this answer y "Linux Kernel Development" de Robert Love y, posteriormente, en la llamada al sistema clone(), descubrí que los procesos y subprocesos en Linux son (casi) indistinguibles del kernel . Hay algunos ajustes entre ellos (discutidos como "compartir más" o "menos compartir" en la pregunta SO citada), pero todavía tengo algunas preguntas por responder.Distinción entre procesos e hilos en Linux

Recientemente trabajé en un programa que involucraba un par de hilos POSIX y decidí experimentar con esta premisa. En un proceso que crea dos subprocesos, todos los subprocesos obtienen un valor único devuelto por pthread_self(), sin embargo,, no por getpid().

Un programa de ejemplo 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; 
} 

(Este fue compilado [gcc -pthread -o thread_test thread_test.c] en 64 bits Fedora; debido a los tipos de 64 bits utilizados para pthread_t procedente de <bits/pthreadtypes.h>, el código requerirá menor cambios para compilar en ediciones de 32 bits)

La salida que se ve es la siguiente:.

[[email protected] ~]$ ./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 
[[email protected] ~]$ 

mediante el uso de schedu ler bloqueo en gdb, que puede mantener el programa y sus hilos vivo para que pueda captar lo que top dice que, acaba de mostrar los 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 cuando se muestran las discusiones, 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 bastante claro que los programas, o quizás el núcleo, tienen una forma distinta de definir los hilos en contraste con los procesos. Cada hilo tiene su propio PID según top - ¿por qué?

+1

'clone()' es exactamente como Linux implementa ambos hilos y 'fork()'. Todo lo que importa es que hablar con un PID transmitirá la señal a todos los que necesitan saber. Si el kernel asigna identificadores adicionales a los hilos, eso no es de su incumbencia y no afecta la forma en que habla con sus procesos. –

+0

Bueno [enlace] (http://opensourceforgeeks.blogspot.in/2014/03/processes-and-threads-in-linux.html) para ver. –

+0

"* los procesos y los hilos en Linux son (casi) indistinguibles del kernel *" Umm, no es realmente cierto. No hay casi nada que pueda decir sobre cómo funciona el kernel de Linux, que es cierto tanto para los procesos como para los hilos. Posee una vista de vm? Solo procesos Se puede programar? Solo hilos. Tiene una tabla de descriptores de archivos? Solo procesos Tiene una prioridad? Solo hilos. Y así sucesivamente hasta la línea. –

Respuesta

29

Todas estas confusiones se deben al hecho de que los desarrolladores del kernel tenían una visión irracional e incorrecta de que los hilos podían implementarse casi por completo en el espacio de usuario utilizando procesos del núcleo como primitiva, siempre que el kernel ofreciera una forma de compartirlos memoria y descriptores de archivos. Esto condujo a la implementación de LinuxThreads notoriamente mala de los hilos POSIX, que era más bien un nombre inapropiado porque no daba nada que se asemejara remotamente a la semántica de los hilos POSIX. Eventualmente, LinuxThreads fue reemplazado (por NPTL), pero persisten muchas confusas terminologías y malentendidos.

Lo primero y más importante es darse cuenta de que "PID" significa cosas diferentes en el espacio del kernel y el espacio de usuario. Lo que el kernel llama PIDs en realidad son identificadores de subprocesos a nivel de kernel (a menudo llamados TIDs), 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 único TID (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 subprocesos" o "TGID" en el kernel.Cada proceso consiste en uno o más subprocesos (procesos kernel) cada uno con su propio TID (kernel PID), pero todos comparten el mismo TGID, que es igual al TID (Kernel PID) del subproceso inicial en el que se ejecuta main.

Cuando top muestra hilos, muestra TID (kernel PID), no PID (kernel TGID), y esta es la razón por la cual cada hilo tiene uno separado.

Con el advenimiento de NPTL, la mayoría de las llamadas al sistema que tienen un argumento PID o actuar en el proceso de llamar se cambiaron para tratar el PID como TGID y actuar sobre todo el "grupo de hilos" (proceso de POSIX).

+0

Gracias por su respuesta, y estaré investigando lo que dice por un tiempo, sin duda. Tal vez la pregunta más importante de mi publicación de hoy (hasta donde sé) es, '¿por qué no han preguntado otros sobre esto?' (Al menos, a través de un recurso fácilmente disponible.) Sin duda, este debe ser un gran tema para aquellos que, como yo, están involucrados en aplicaciones multiproceso. – Doddy

+0

Bueno, hoy (ahora que ya pasamos el fiasco LinuxThreads), los programadores realmente pueden hacer negocios * utilizando los hilos POSIX * como lo especifica POSIX, sin preocuparse demasiado por lo que sucede debajo del capó, porque todo funciona correctamente. Sospecho que esa es la razón por la cual los detalles de implementación ya no reciben mucha atención. BTW 'man 7 pthreads' tiene alguna explicación básica de cómo funciona. –

+0

@bean - la pregunta ha sido hecha de muchas maneras diferentes desde muchos ángulos diferentes aquí. R ha dado una respuesta particularmente buena ya que afecta a varios niveles de confusión tanto desde el punto de vista histórico como técnico de Linux. – Duck

0

En Linux, cada hilo obtiene un con ID de hilo. La identificación del subproceso del subproceso principal cumple una doble función como ID de proceso (y es bastante conocida en la interfaz de usuario). El ID de subproceso es un detalle de implementación de Linux y no está relacionado con la ID POSIX. Para obtener más detalles, consulte la llamada al sistema gettid (no disponible en Python puro, ya que es específico del sistema).

+0

No estoy seguro de si 'La identificación del subproceso del subproceso principal cumple una función doble ya que el ID del proceso es verdadero. Cuando ejecuté el código anterior, la identificación del hilo principal no era igual que el PID. – codeforester

1

Imagina algún tipo de "meta-entidad". Si la entidad no comparte ninguno de los recursos (espacio de direcciones, descriptores de archivos, etc.) de su padre, entonces es un proceso, y si la entidad comparte todos los recursos de su padre, entonces es un hilo. Incluso podría tener algo a medio camino entre el proceso y el hilo (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 esconde eso detrás de un tipo de abstracción que hace que todo parezca un proceso o un hilo. Si la abstracción es perfecta, nunca sabría la diferencia entre "entidades" y "procesos e hilos". La abstracción no es del todo perfecta, el PID que estás viendo es en realidad una "identificación de entidad".

+0

Lo siento, su respuesta no arroja ninguna luz sobre mi pregunta en absoluto. Ya he consultado la página man de 'clone()'. El hecho de que hubo abstracción entre los procesos y los hilos fue lo que me hizo plantear la pregunta en primer lugar. Es muy fácil decir que, debido a que he elegido llamar a 'clone()' con menos del 50% de recursos compartidos, debería recibir un * thread * en lugar de un proceso, y del mismo modo. – Doddy