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


65

Mark Mitchell, Jeffrey Oldham 및 Alex Samuel의 Advanced Linux Programming 이 책을 보겠습니다 . 2001 년부터 시작 됐습니다. 그러나 어쨌든 나는 그것을 아주 좋아합니다.

그러나 리눅스가 쉘 출력에서 ​​생성하는 것과 다른 시점에 도달했습니다. 92 페이지 (뷰어에서 116)에서 4.5 장 GNU / 리눅스 스레드 구현은이 문장을 포함하는 단락으로 시작합니다.

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

이것은 요점처럼 보이며 나중에 C 코드로 설명됩니다. 이 책의 결과는 다음과 같습니다.

main thread pid is 14608
child thread pid is 14610

그리고 우분투 16.04에서는 다음과 같습니다.

main thread pid is 3615
child thread pid is 3615

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

2001 년과 지금 사이에 뭔가 바뀌었을 것 같습니다.

다음 페이지의 다음 하위 챕터 4.5.1 신호 처리는 이전 명령문에서 빌드됩니다.

신호와 스레드 간의 상호 작용 동작은 UNIX 계열 시스템마다 다릅니다. GNU / 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
혼란의 원인이 무엇인지 모르겠습니다. 스레드는 부모와 주소 공간을 공유하는 프로세스로 구현됩니다.
Johan Myréen

2
@ JohanMyréen 왜 스레드 pid가 같은가요?
Tomasz

아, 이제 알겠습니다. 예, 무언가가 정말로 바뀌 었습니다. @ilkkachu의 답변을 참조하십시오.
Johan Myréen

5
스레드는 여전히 프로세스로 구현되지만 이제 스레드 그룹 IDgetpid 라고하는 것을 반환 하고 사용해야하는 프로세스의 고유 ID를 얻습니다 . 그러나 대부분의 사람과 도구는 커널 이외의 다른 시스템과의 일관성을 위해 스레드 그룹을 프로세스라고하고 프로세스를 스레드라고합니다. gettid
user253751

실제로는 아닙니다. 프로세스는 이렇게하면 될 것이다, 스레드를 호출되지 않습니다, 자체 메모리 및 파일 기술자를 가지고 다른 시스템과의 일치.
reinierpost

답변:


50

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 포함) 만 있습니다. CLONE_THREAD프로세스 ID (*) 와 스레드 ID를 분리하면 Linux 동작이 다른 시스템과 비슷하게 보이고 POSIX 요구 사항과 유사하게됩니다. 기술적으로 OS에는 여전히 스레드와 프로세스에 대한 별도의 구현이 없습니다.

신호 처리는 이전 구현에서 또 다른 문제가있는 부분이었습니다.이 내용은 @FooF 가 그들의 답변에서 참조 하는 논문에 자세히 설명되어 있습니다 .

주석에서 언급했듯이 Linux 2.4는 2001 년에 책과 같은 해에 릴리스되었으므로 뉴스가 그 기사를 얻지 못한 것은 놀라운 일이 아닙니다.


2
가상 메모리 나 파일 디스크립터와 같은 일부 공유 리소스가있을 수있는 별도의 프로세스. 그것은 여전히 ​​당신이 언급 한 문제를 정리하면서 Linux 스레드가 작동하는 방식과 거의 같습니다. 커널 "스레드"또는 "프로세스"에 사용 된 스케줄링 단위를 호출하는 것은 실제로 관련이 없습니다. 그들이 "프로세스"라고 불리는 리눅스에서 시작했다고해서 이것이 전부라는 것은 아닙니다.
Andrew Henle

@AndrewHenle은 약간 편집했습니다. 나는 당신의 생각을 포착하기를 바랍니다. 다른 유닉스 계열 OS는 스레드와 프로세스가 더 뚜렷하게 분리되어 있으며 Linux는 실제로 한 가지 유형의 서비스 만 제공한다는 점에서 예외입니다. 두 기능. 그러나 나는 다른 시스템에 대해 충분히 알지 못하고 소스가 없기 때문에 구체적인 것을 말하기는 어렵습니다.
ilkkachu

@tomas이 답변은 현재 Linux가 작동하는 방식을 설명합니다. ilkkachu가 암시 하듯이, 책을 쓸 때 다르게 작동했습니다. FooF의 답변 은 당시 Linux가 어떻게 작동했는지 설명합니다.
Gilles

38

실제로 "2001 년과 현재 사이에 무언가 변화가 있었음"이 맞습니다. 읽고있는 책은 LinuxThreads 라고하는 Linux에서 POSIX 스레드의 최초의 역사적 구현에 따라 세계를 설명합니다 ( 일부 는 Wikipedia 기사 참조 ).

LinuxThreads는 POSIX 표준과 일부 호환성 문제 (예 : PID를 공유하지 않는 스레드) 및 기타 심각한 문제가있었습니다. 이러한 결함을 해결하기 위해 Red Hat은 NPTL (Native POSIX Thread Library)이라는 또 다른 구현을 통해 필요한 커널 및 사용자 공간 라이브러리 지원을 추가하여보다 나은 POSIX 준수를 달성했습니다 (IBM의 NGPT라는 또 다른 경쟁적인 재 구현 프로젝트에서 좋은 부분을 얻음). 차세대 Posix 스레드 "), NPTL에 대한 Wikipedia 기사 참조 ). 에 추가 된 추가 플래그 clone(2)(특히 시스템 호출 에 지적 그의 대답은 ) 아마 커널 수정의 가장 명백한 부분이다. 작업의 사용자 공간 부분은 결국 GNU C 라이브러리에 통합되었습니다.CLONE_THREAD@ikkkachu

오늘날 일부 임베디드 Linux SDK는 uClibc (µClibc라고도 함) 라는 더 작은 메모리 공간 버전의 LibC를 사용하기 때문에 이전 LinuxThreads 구현을 사용 하며 GNU LibC의 NPTL 사용자 공간 구현이 포팅되어 다음 과 같이 가정 되기까지 상당한 시간이 걸렸 습니다. 기본적으로 POSIX 스레딩 구현은 일반적으로 이러한 특수 플랫폼이 최신 속도로 빠른 속도를 따르려고하지 않습니다. 이는 실제로 해당 플랫폼의 다른 스레드에 대한 PID가 POSIX 표준이 지정한 것과 다르게 다르다는 사실을 알 수 있습니다. 실제로 전화하면pthread_create()혼란을 함께 유지하기 위해 추가 프로세스가 필요했기 때문에 프로세스 수를 갑자기 1에서 3으로 늘 렸습니다.

Linux pthreads (7) 매뉴얼 페이지는이 둘의 차이점에 대한 포괄적이고 흥미로운 개요를 제공합니다. 차이에 대한 또 다른 깨달음, 차이점에 대한 설명 은 NPTL 디자인에 대한 Ulrich Depper와 Ingo Molnar의 논문 입니다.

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


22

(Userspace) 스레드는 자체 개인 주소 공간이 없으므로 여전히 상위 프로세스의 주소 공간을 공유한다는 점에서 Linux와 같은 프로세스로 구현되지 않습니다.

그러나 이러한 스레드는 커널 프로세스 계정 시스템을 사용하도록 구현되므로 자체 스레드 ID (TID)가 할당되지만 상위 프로세스와 동일한 PID 및 '스레드 그룹 ID'(TGID)가 제공됩니다. 새 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

3
@tomas einonm이 맞습니다. 이 책의 내용을 무시하면 정말 혼란 스럽습니다. Dunno는 아이디어 작성자가 무엇을 전달하고 싶어하는지 알지만 실패했습니다. 따라서 Linux에는 커널 스레드와 사용자 공간 스레드가 있습니다. 커널 스레드는 본질적으로 사용자 공간이없는 프로세스입니다. 사용자 공간 스레드는 일반적인 POSIX 스레드입니다. 사용자 공간 프로세스는 파일 디스크립터를 공유하고 코드 세그먼트를 공유 할 수 있지만 완전히 별개의 가상 주소 공간에 있습니다. 프로세스 내의 사용자 공간 스레드는 코드 세그먼트, 정적 메모리 및 힙 (동적 메모리)을 공유하지만 별도의 프로세서 레지스터 세트와 스택을 가지고 있습니다.
보리스 Burkov

8

기본적으로 책의 정보는 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를 공유합니다. 동일한 TGID는 main실행 되는 초기 스레드의 TID (커널 PID)와 같습니다 .

top당신이 스레드를 보여줍니다, 그것은의 TID (커널의 PID),하지의 PID (커널 TGIDs)를 보여주는, 그리고 각 스레드는 별도의 하나를 가지고 이유입니다.

NPTL의 출현으로 PID 인수를 취하거나 호출 프로세스 에 작용하는 대부분의 시스템 호출이 PID를 TGID로 취급하고 전체 "스레드 그룹"(POSIX 프로세스)에 작용하도록 변경되었습니다.


8

내부적으로 리눅스 커널에는 프로세스 나 스레드가 없습니다. 프로세스와 스레드는 대부분 사용자 영역 개념이며, 커널 자체는 "태스크"만 볼 수 있습니다. "태스크"는 다른 태스크와 자원의 일부 또는 전부를 공유 할 수없는 예약 가능한 개체입니다. 스레드는 대부분의 자원 (주소 공간, mmap, 파이프, 열린 파일 핸들러, 소켓 등)을 상위 태스크와 공유하도록 구성된 태스크이며 프로세스는 상위 태스크와 최소 자원을 공유하도록 구성된 태스크입니다. .

fork ()pthread_create () 대신 Linux API를 직접 사용하는 경우 ( clone () , pthread_create () ), 공유하거나 공유하지 않을 리소스의 양을 정의하는 데 훨씬 더 융통성이 있으며 완전히 다른 작업을 만들 수 있습니다 스레드도 처리하지 않습니다. 이러한 저수준 호출을 직접 사용하는 경우 실제로 모든 리소스를 상위 작업과 공유하거나 그 반대로 생성하는 새로운 TGID (대부분의 사용자 도구로 프로세스로 처리됨)로 작업을 생성 할 수도 있습니다. 상위 작업과 리소스를 공유하지 않는 공유 TGID (대부분의 사용자 도구에서 스레드로 처리)가있는 작업

Linux 2.4는 TGID를 구현하지만 이는 대부분 자원 계정의 이점을위한 것입니다. 많은 사용자와 사용자 공간 도구는 관련 작업을 함께 그룹화하고 리소스 사용량을 함께보고하는 것이 유용하다는 것을 알게되었습니다.

Linux에서의 작업 구현은 사용자 공간 도구가 제공하는 프로세스 및 스레드 세계관보다 훨씬 유동적입니다.


종이 를 읽은 후, 난 정말 언급하지 않았다, 그래서에 연결 @FooF 커널은 프로세스와 스레드 별도의 실체로 (예를 들어, 신호 처리 및 간부를 ()) 고려하는 점의 수를 설명 그러한 없다 "고 리눅스 커널에서 프로세스 나 스레드로
ilkkachu

5

Linus Torvalds는 1996 년 커널 메일 링리스트 게시물에서 "스레드와 프로세스 모두 '실행 컨텍스트'로 취급됩니다."는 "그 CoE의 모든 상태를 모아 놓은 것"입니다. 상태, MMU 상태, 권한 및 다양한 통신 상태 (파일 열기, 신호 처리기 등) "

// simple program to create threads that simply sleep
// compile in debian jessie with apt-get install build-essential
// and then g++ -O4 -Wall -std=c++0x -pthread threads2.cpp -o threads2
#include <string>
#include <iostream>
#include <thread>
#include <chrono>

// how many seconds will the threads sleep for?
#define SLEEPTIME 100
// how many threads should I start?
#define NUM_THREADS 25

using namespace std;

// The function we want to execute on the new thread.
void threadSleeper(int threadid){
    // output what number thread we've created
    cout << "task: " << threadid << "\n";
    // take a nap and sleep for a while
    std::this_thread::sleep_for(std::chrono::seconds(SLEEPTIME));
}

void main(){
    // create an array of thread handles
    thread threadArr[NUM_THREADS];
    for(int i=0;i<NUM_THREADS;i++){
        // spawn the threads
        threadArr[i]=thread(threadSleeper, i);
    }
    for(int i=0;i<NUM_THREADS;i++){
        // wait for the threads to finish
        threadArr[i].join();
    }
    // program done
    cout << "Done\n";
    return;
}

보시다시피이 프로그램은 한 번에 25 개의 스레드를 생성하며 각 스레드는 100 초 동안 잠자기 한 다음 기본 프로그램에 다시 참여합니다. 25 개의 스레드가 모두 프로그램에 다시 가입하면 프로그램이 완료되고 종료됩니다.

사용 top당신은 "threads2"프로그램의 25 개 인스턴스를 볼 수 있습니다. 하지만 지루 해요 의 결과 ps auwx는 훨씬 덜 흥미 롭다. 그러나 ps -eLf약간 흥미 롭다.

UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
debian     689   687   689  0    1 14:52 ?        00:00:00 sshd: debian@pts/0  
debian     690   689   690  0    1 14:52 pts/0    00:00:00 -bash
debian    6217   690  6217  0    1 15:04 pts/0    00:00:00 screen
debian    6218  6217  6218  0    1 15:04 ?        00:00:00 SCREEN
debian    6219  6218  6219  0    1 15:04 pts/1    00:00:00 /bin/bash
debian    6226  6218  6226  0    1 15:04 pts/2    00:00:00 /bin/bash
debian    6232  6219  6232  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6233  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6234  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6235  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6236  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6237  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6238  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6239  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6240  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6241  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6242  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6243  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6244  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6245  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6246  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6247  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6248  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6249  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6250  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6251  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6252  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6253  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6254  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6255  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6256  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6257  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6260  6226  6260  0    1 15:04 pts/2    00:00:00 ps -eLf

여기에서 thread2프로그램이 생성 한 26 개의 CoE를 모두 볼 수 있습니다 . 모두 동일한 프로세스 ID (PID)와 상위 프로세스 ID (PPID)를 공유하지만 각각 LWP ID (경량 프로세스)가 다르며 LWP (NLWP)의 수는 26 개의 CoE가 있음을 나타냅니다. 스레드 25 개가 생성되었습니다.


맞아요, 스레드는 가벼운 프로세스 (LWP)입니다
fpmurphy

3

리눅스 프로세스 및 스레드에 관해서입니다 종류의 같은 일. 즉, 동일한 시스템 호출로 생성됩니다 clone.

그것에 대해 생각하면 스레드와 프로세스의 차이점은 자식과 부모가 커널 개체를 공유하는 것입니다. 프로세스의 경우 많은 것이 아닙니다 : 열린 파일 설명자, 작성되지 않은 메모리 세그먼트, 아마도 내 머리 꼭대기에서 생각할 수없는 몇 가지 다른 것입니다. 스레드의 경우 훨씬 더 많은 객체가 공유되지만 전부는 아닙니다.

리눅스에서 스레드와 객체를 더 가깝게 만드는 것은 unshare시스템 호출입니다. 공유 된 것으로 시작된 커널 객체는 스레드 생성 후 공유를 해제 할 수 있습니다. 예를 들어, 스레드가 작성된 후 파일 디스크립터의 공유를 취소하여 파일 디스크립터 공간이 다른 동일한 프로세스의 두 스레드를 가질 수 있습니다. 스레드를 작성하고 unshare두 스레드를 모두 호출 한 다음 모든 파일을 닫고 두 스레드에서 새 파일, 파이프 또는 오브젝트를 열어서 직접 테스트 할 수 있습니다 . 그런 다음 살펴보고 스레드 ( /proc/your_proc_fd/task/*/fdtask스레드로 만든)마다 fd가 다르다는 것을 알 수 있습니다.

실제로, 새로운 스레드와 새로운 프로세스의 생성은 clone아래에서 호출하는 라이브러리 루틴이며 새로 생성 된 process-thread-thingamajig (즉, task)가 호출 프로세스 / 스레드와 공유 할 커널 객체를 지정 합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.