Linux의 스레드는 프로세스로 구현됩니까?

Linux의 스레드는 프로세스로 구현됩니까?

나는 겪고 있다이 책, Mark Mitchell, Jeffrey Oldham 및 Alex Samuel의 "고급 Linux 프로그래밍". 2001년 작품이라 좀 오래된 내용이네요. 하지만 그래도 꽤 괜찮은 것 같아요.

그러나 나는 이것이 내 Linux가 쉘 출력에서 ​​생성한 것과 다르다는 것을 발견했습니다. 92페이지(뷰어에서는 116)에서 4.5장의 GNU/Linux 스레딩 구현은 다음 명령문이 포함된 단락으로 시작됩니다.

GNU/Linux의 POSIX 스레드 구현은 한 가지 중요한 방식으로 다른 많은 UNIX 계열 시스템의 스레드 구현과 다릅니다. GNU/Linux에서 스레드는 프로세스로 구현됩니다.

이는 중요한 포인트인 것으로 보이며 나중에 C 코드에서 설명하겠습니다. 책의 결과는 다음과 같습니다.

main thread pid is 14608
child thread pid is 14610

내 Ubuntu 16.04에서는 다음과 같습니다.

main thread pid is 3615
child thread pid is 3615

ps출력이 이를 지원합니다.

2001년부터 지금까지 뭔가가 달라진 것 같아요.

다음 페이지의 다음 하위 섹션인 4.5.1 신호 처리는 이전 설명을 기반으로 합니다.

신호와 스레드 간의 상호 작용은 UNIX 계열 시스템마다 다릅니다. GNU/Linux에서는 스레드가 프로세스로 구현된다는 사실에 따라 동작이 결정됩니다.

이 책의 후반부에서는 이것이 더 중요할 것 같습니다. 무슨 일인지 설명해 줄 수 있는 사람이 있나요?

나는 이것을 본 적이 있다Linux 커널 스레드는 실제로 커널 프로세스입니까?, 그러나 별로 도움이 되지 않았습니다. 혼란스러워요.

C 코드는 다음과 같습니다.

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void* thread_function (void* arg)
{
    fprintf (stderr, "child thread pid is %d\n", (int) getpid ());
    /* Spin forever. */
    while (1);
    return NULL;
}

int main ()
{
    pthread_t thread;
    fprintf (stderr, "main thread pid is %d\n", (int) getpid ());
    pthread_create (&thread, NULL, &thread_function, NULL);
    /* Spin forever. */
    while (1);
    return 0;
}

답변1

제 생각에는 이 부분이clone(2)매뉴얼 페이지차이점이 제거될 수 있습니다. PID:

CLONE_THREAD(Linux 2.4.0-test8부터)
CLONE_THREAD가 설정되면 하위 프로세스는 호출 프로세스와 동일한 스레드 그룹에 배치됩니다.
스레드 그룹은 단일 PID를 공유하는 스레드 그룹의 POSIX 스레드 개념을 지원하기 위해 Linux 2.4에 추가된 기능입니다. 내부적으로 이 공유 PID는 소위 스레드 그룹의 스레드 그룹 식별자(TGID)입니다. Linux 2.4부터 getpid(2)를 호출하면 호출자의 TGID가 반환됩니다.

"스레드가 프로세스로 구현된다"라는 문구는 과거 스레드가 별도의 PID를 가지고 있던 문제를 의미합니다. 기본적으로 Linux에는 처음에는 프로세스 내에 스레드가 없었고 가상 메모리나 파일 설명자와 같은 일부 공유 리소스를 소유할 수 있는 별도의 프로세스(별도의 PID 포함)만 있었습니다. 프로세스 ID (*)CLONE_THREAD 와 스레드 ID를 분리하면 Linux가 다른 시스템과 더 유사하게 동작하게 되며 이러한 의미에서 POSIX 요구 사항과 더 유사해집니다. 기술적으로 말하면 운영 체제에는 여전히 스레드와 프로세스의 별도 구현이 없습니다.

신호 처리는 이전 구현의 또 다른 문제 영역이었습니다.종이@FooF는 다음을 가리킨다.그들의 대답에.

댓글에서 언급했듯이 Linux 2.4도 이 책과 같은 해인 2001년에 출시되었으므로 뉴스가 출판되지 않은 것은 놀라운 일이 아닙니다.

답변2

당신 말이 맞습니다. 실제로 "2001년 이후 확실히 몇 가지 변화가 일어났습니다." 당신이 읽고 있는 책은 Linux에서 POSIX 스레드를 최초로 구현한 역사적 사례의 관점에서 세계를 설명합니다.리눅스 스레드(당신은 또한 볼 수 있습니다위키피디아 페이지이 주제 및 Linux 정보병렬 스레드(7)매뉴얼 페이지).

LinuxThreads에는 POSIX 표준과의 호환성 문제(예: 스레드가 PID를 공유하지 않음)와 기타 심각한 문제가 있습니다. 이러한 결함을 수정하기 위해 Red Hat은 더 나은 POSIX 규정 준수를 위해 필요한 커널 및 사용자 공간 라이브러리 지원을 추가하기 위해 NPTL(Native POSIX Threading Library)이라는 또 다른 구현을 개척했습니다. IBM의 또 다른 구현은 NGPT("Next Posix 스레드 생성'), 참조NPTL에 관한 Wikipedia 기사). 추가 플래그가 추가되었습니다.clone(2)시스템 호출(특별히 CLONE_THREAD언급됨@ikkkachu그의 대답)은 아마도 커널 수정에서 가장 분명한 부분일 것입니다. 작업의 사용자 공간 부분은 결국 GNU C 라이브러리에 병합되었습니다.

오늘날 일부 임베디드 Linux SDK는 LibC라는 더 작은 메모리 공간 버전을 사용하기 때문에 여전히 이전 LinuxThreads 구현을 사용합니다.uClibc(μClibc라고도 함), GNU LibC에서 NPTL 사용자 공간 구현을 이식하는 데 수년이 걸렸습니다.그리고일반적으로 이러한 특정 플랫폼은 최신 유행을 따르려고 노력하지 않기 때문에 기본 POSIX 스레드 구현이 가정됩니다. 실제로 이러한 플랫폼에 있는 여러 스레드의 PID가 POSIX 표준에 지정된 PID와 다르다는 점에 주목하면 읽고 있는 책에서 설명하는 것과 마찬가지로 LinuxThreads 구현이 실제로 작동하는 것을 관찰할 수 있습니다. 실제로 를 호출하자마자 pthread_create()갑자기 프로세스 수를 1에서 3으로 늘리게 됩니다. 혼란을 정리하기 위해 추가 프로세스가 필요하기 때문입니다.

리눅스병렬 스레드(7)매뉴얼 페이지는 둘 사이의 차이점에 대한 포괄적이고 흥미로운 개요를 제공합니다. 차이점에 대한 또 다른 설명은 (구식이긴 하지만) 다음과 같습니다.종이저자: NPTL 설계에 관한 Ulrich Depper 및 Ingo Molnar.

나는 당신이 책의 이 부분을 너무 심각하게 받아들이지 말 것을 제안합니다. 대신 해당 주제에 대해서는 Butenhof의 POSIX 스레드 프로그래밍과 POSIX 및 Linux 매뉴얼 페이지를 추천합니다. 이 주제에 대한 많은 튜토리얼은 부정확합니다.

답변3

(사용자 공간) 스레드는 자체 개인 주소 공간이 없기 때문에 Linux에서 프로세스로 구현되지 않지만 여전히 상위 프로세스의 주소 공간을 공유합니다.

그러나 이러한 스레드는 커널 프로세스 계산 시스템을 사용하도록 구현되므로 자체 스레드 ID(TID)가 할당되지만 상위 프로세스와 동일한 PID 및 "스레드 그룹 ID"(TGID)가 제공됩니다. 이는 동일하지 않습니다. Forking으로 새로운 TGID와 PID를 생성하며, TID는 PID와 동일합니다.

따라서 최근 커널에는 쿼리할 수 있는 별도의 TID가 있는 것으로 보이며 이는 스레드마다 다릅니다. 위의 각 main() thread_function()에서 이를 표시하는 적합한 코드 조각은 다음과 같습니다.

    long tid = syscall(SYS_gettid);
    printf("%ld\n", tid);

따라서 전체 코드는 다음과 같습니다.

#include <pthread.h>                                                                                                                                          
#include <stdio.h>                                                                                                                                            
#include <unistd.h>                                                                                                                                           
#include <syscall.h>                                                                                                                                          

void* thread_function (void* arg)                                                                                                                             
{                                                                                                                                                             
    long tid = syscall(SYS_gettid);                                                                                                                           
    printf("child thread TID is %ld\n", tid);                                                                                                                 
    fprintf (stderr, "child thread pid is %d\n", (int) getpid ());                                                                                            
    /* Spin forever. */                                                                                                                                       
    while (1);                                                                                                                                                
    return NULL;                                                                                                                                              
}                                                                                                                                                             

int main ()                                                                                                                                                   
{                                                                                                                                               
    pthread_t thread;                                                                               
    long tid = syscall(SYS_gettid);     
    printf("main TID is %ld\n", tid);                                                                                             
    fprintf (stderr, "main thread pid is %d\n", (int) getpid ());                                                    
    pthread_create (&thread, NULL, &thread_function, NULL);                                           
    /* Spin forever. */                                                                                                                                       
    while (1);                                                                                                                                                
    return 0;                                                                                                                                                 
} 

예제 출력을 제공하십시오.

main TID is 17963
main thread pid is 17963
thread TID is 17964
child thread pid is 17963

답변4

기본적으로 Linux에서 스레딩 구현의 역사가 매우 열악하기 때문에 책에 있는 정보는 역사적으로 정확합니다. SO 관련 질문에 대한 내 답변은 귀하의 질문에 대한 답변으로도 사용될 수 있습니다.

https://stackoverflow.com/questions/9154671/distinction- Between-processes-and-threads-in-linux/9154725#9154725

이 모든 혼란은 커널이 스레드가 메모리를 공유하고 프로세스가 구현되는 방법을 제공하는 한 커널 개발자가 처음에 스레드가 거의 전적으로 사용자 공간에서 실행될 수 있다는 비합리적이고 잘못된 견해를 갖고 있다는 사실에서 비롯됩니다. 원시인으로서. . 이로 인해 POSIX 스레드의 LinuxThreads 구현이 악명 높게 나타났습니다. 이는 POSIX 스레드 의미와 원격으로 유사한 것을 제공하지 않았기 때문에 잘못된 이름입니다. 결국 LinuxThreads는 NPTL로 대체되었지만 혼란스러운 용어와 오해가 많이 남아 있었습니다.

먼저 깨달아야 할 가장 중요한 것은 "PID"가 커널 공간과 사용자 공간에서 다른 것을 의미한다는 것입니다. 커널이 PID라고 부르는 것은 실제로 커널 수준 스레드 ID(종종 TID라고 함)이며 pthread_t별도의 식별자와 혼동하지 마십시오. 동일한 프로세스에 있든 다른 프로세스에 있든 시스템의 모든 스레드에는 고유한 TID(또는 커널 용어로 "PID")가 있습니다.

반면, POSIX 의미에서 "프로세스"의 PID는 커널에서 "스레드 그룹 ID" 또는 "TGID"라고 합니다. 각 프로세스는 하나 이상의 스레드(커널 프로세스)로 구성되며, 각 스레드는 자체 TID(커널 PID)를 갖지만 모두 동일한 TGID를 공유합니다. 이는 main실행 중인 초기 스레드의 TID(커널 PID) 와 같습니다 .

top스레드가 표시되면 PID(커널 TGID)가 아닌 TID(커널 PID)가 표시되므로 각 스레드마다 별도의 스레드가 있습니다 .

NPTL의 출현으로 PID 매개변수를 사용하거나 호출에 따라 조치를 취하는 대부분의 시스템 호출프로세스PID를 TGID로 처리하고 전체 "스레드 그룹"(POSIX 프로세스)에 대해 작동하도록 변경되었습니다.

관련 정보