SW 개발

[Linux_kernel] 리눅스에서의 thread, fork 차이점

. . . 2011. 4. 25. 11:21
반응형

출처 까먹음 (각 코멘트들은 출처를 달아놓았음) / 인터넷 여기저기의 자료를 보기좋게 편집함.

왜 Thread인가?

Process 모델의 문제인 fork의 비효율성을 극복하기 위해 사용된다. 간단히 예를 들어보겠다. 무식한 Web Server 하나가 있다. 이 녀석은 2개 이상의 클라이언트로부터 요청이 오면 자신을 복제하여 새로운 Process를 생성한다. 그리고 이 녀석이 클라이언트 하나를 처리하도록 만든다. 여기서 Process 복사를 위해 fork system call이 발생하는데 동시에 다수의 클라이언트로부터의 요청을 처리하기 위해서는 그만큼의 fork가 일어나고 이는 시스템이 Process 복사를 하느라 다른 일을 처리하지 못하게 되는 상황이 된다. 매우 비효율적이다. 다른 이유를 찾아보면 멀티프로세싱(multi-processing)을 위해서이다. 하나의 Process는 하나의 프로세서(CPU)에서 동작하게 된다.

시스템이 자동적으로 Process를 병렬화 하는 것은 불가능 하기 때문에 멀티프로세싱이 가능한 시스템에서는 단일 Process로 동작하는 것은 매우 비효율적이다. 이러한 비효율성을 해결하기 위해서 Thread 가 등장한다.

Process, Thread두 모델간의 차이

Process 모델은 heavy-weight 모델이라고 표현할 수 있다. Process를 복사할 때 Process가 가지고 있는 모든 자료구조를 다시 생성하고 복사하는 등 이로 인해 발생하는 비용이 크다. 반면 Thread 모델은 light-weight 모델이다. Process의 대부분의 내용을 공유하며 Thread를 위한 자료구조, 지역변수, register 저장을 위한 공간, stack, PC 등만 필요할 뿐이다.

  1. 전통적인 Process 자료구조 = Process Context + (data, code, stack), Process Context = Program Context + Kernel Context
  2. 현대의 Process 자료구조 = Thread + (code, data) + Kernel context, Thread = Thread context + User stack
  3. 각각의 Thread는 자신만의 logical control flow, 즉 PC(Program Counter)를 가진다.
  4. 각각의 Thread는 동일한 code, data, Kernel context를 공유한다.
  5. 각각의 Thread는 자신의 Thread ID를 가진다.
  6. 논리적 구조가 Tree 형태가 아니다. Process로부터 파생이 되기는 하지만 모든 Thread는 동일한 Level의 Child 일 뿐이다. 반면 Process의 복제에서는 Tree처럼 이루어진다.
  7. OS가 Swap out을 할 때 Process 단위로 이루어진다. Thread의 경우 Process가 Swap out되면 함께 되는 것이므로 따로 Swap out의 대상이 되지 않는다.
  8. 하나의 Thread는 하나의 Process에만 속하게 된다. 그러나 하나의 Process는 여러 개의 Thread를 가질 수 있다.

주소공간의 차이

하나의 Process에 속해있는 Thread들은 같은 주소공간을 공유하기 때문에 정보 공유가 용이하다. 그러나 이로 인한 동기화의 문제가 있다. 이것은 이후에 언급하도록 하겠다.

간단히 정리한 공통점과 차이점

  1. 공통점 : Thread와 Process는 스케줄링의 단위가 된다(context switch). 그리고 각각은 logical control flow인 PC를 가지며 동시에 동작한다.
  2. 차이점 : Thread는 code, data를 공유하지만 Process는 그렇지 않다. 그리고 생성과 context switching, 종료의 관점에서 Thread가 훨씬 비용이 적게 든다.

Thread의 이점

생성, 종료, context switching 비용이 적어 경제적이다. 리소스 공유를 통한 향상된 통신을 할 수 있다. 즉, Process와는 다르게 Kernel의 간섭 없이 Thread 간의 빠르게 정보교환을 할 수 있다. Process의 경우 IPC따위를 통해야 하기 때문에 복잡하다. 마지막으로 멀티프로세서 환경에서 매우 유용하다. 각각의 Thread는 다른 프로세서에서 병렬적으로 동작할 수 있다.

User Level , Kernel Level Threads

User Level Thread

Library의 link를 통해서 Thread를 관리한다. 라이브러리는 Kernel의 축소판이다. Thread를 컨트롤하기 위해 사용한다. Kernel의 도움 없이 동기화, 생성, 스케줄링을 할 수 있다. 즉, Kernel의 리소스를 사용하지 않는다. 그러나 Process와 동일한 주소공간을 사용한다. 그리고 User stack과 register 등의 context를 따로 저장하고 있다. 그러나 하나의 Process가 Thread를 관리하는 구조가 되므로 멀티 프로세서를 지원하지 못하는 문제점과 Thread가 Block될 경우 Process 전체가 Block되는 문제가 있다.

Kernel Level Thread : system call을 통해 Thread를 관리한다.

Kernel이 Process와 Thread를 위한 context 정보를 유지하고 있다. Process와는 독립된 스케줄링을 할 수 있으며 Kernel의 표준 동기화 메커니즘을 사용할 수 있다. Thread가 Block되어도 Process가 Block되지 않으며 멀티프로세서를 지원한다. 그러나 User-Level Thread에 비해 느리고 무겁다. Thread 생성, 스케줄링, 동기화 등을 하기 위해 system call이 사용 되기 때문이다. 또한 Thread 정보가 Kernel에 저장되기 때문에 Kernel 리소스의 제약에 의해 Thread수의 제한이 생긴다.

Threading Models

  1. Many-to-one : 여러 개의 Thread가 하나의 Kernel Thread로 동작한다. 하나의 Kernel Thread는 Process가 될 수 있다. 즉, User-Level Thread 만으로 구성되는 모델이다. User Level Thread의 장단점을 그대로 가지고 있다.
  2. One-to-one : Kernel Level Thread만으로 구성되는 모델이다. Kernel Level Thread의 장단점을 그대로 가지고 있다. (Win 2k, NT, OS/2 등)
  3. Many-to-Many : Many-to-one + one-to-one 의 형태이다. 많은 User Level Thread가 하나의 Kernel Level Thread에서 동작한다. 그리고 Process당 여러 개의 Kernel Level Thread가 생성 될 수 있다. Thread 의 생성은 User space에서 완료되며 멀티프로세서를 지원한다. 또한 block system call이 발생해도 전체 Process가 block 되지 않는다. (Solaris HP-UX, IRix 등)

Threading Issues

  1. Fork Issue: Thread 내에서 fork가 일어나면 모든 Thread를 복사해야 하는가 아니면 새로운 Process에 해당 Thread만 생성하고 말아야 하는가?
  2. Cancellation Issue :할당 받은 메모리가 남아있거나 다른 Thread와 공유중인 데이터가 업데이트 중일 때 말소될 경우 어떻게 처리해야 하는가?
  3. Signal handling Issue : signal이 발생했을 때 이를 처리할 Thread는? 즉 signal을 모든 Thread에게 보내주어야 하나 아니면 특정 Thread에만 보내주어야 하나? 참고로 Solaris의 경우 모든 Thread에서 Signal을 처리하도록 하고 있다.
  4. Thread polling Issue : Process가 시작하면 가능한 수만큼 Thread를 생성하여 pool에 보관했다가 작업이 필요하면 사용한다.
  5. Thread Interface Issue : Vender에 의존적이다. 즉 OS마다 다르다. 그러나 pThreads 같이 POSIX 표준을 지원할 경우 동일한 인터페이스로 Thread를 관리할 수 있다.

OS별 Thread 구조

  1. Solaris : Many-to-many model 이다. 여러 개의 LWP(Light Weight Process)에 여러 개의 User Level Thread가 동작한다. LWP는 Kernel Level Thread 이다. User Level Thread는 하나의 LWP에 bound된다. User Level Thread는 Kernel의 간섭 없이 Thread 라이브러리를 통해 스케줄링 된다. 각각의 Process는 최소 하나의 LWP를 가진다. LWP의 자료구조는 Kernel에서 유지하고 있다.
  2. Window 2k : one-to-one model 이다.
  3. Linux Thread : 멀리스레딩을 지원한다. 그러나 효율적인 Kernel레벨의 멀티스레딩을 지원하지는 않는다. 대신 LWP와 비슷한 형태를 제공한다. 특징이라고 하면 Process와 Thread를 구분하지 않는다는 것이다. Task_struct 하나만을 사용하고 있다. LWP는 clone() system call을 통해 만들어 진다. Fork와 유사하다. Process의 복사본을 만드는 것 대신 parent task의 주소공간을 공유하는 분리된 Process를 생성한다.

Thread / Fork 차이점에 관한 comment -1

가장 큰 차이는 별도의 address 공간을 가지는지의 여부가 아닐까 싶네요. 두 가지 모두 task가 두 개가 된다는 점에서 동일합니다. 하지만 fork()에서 만든 새로운 프로세스는 fork()를 호출한 부모 프로세스의 메모리 공간에 접근할 수 없습니다.(반대의 경우도 마찬가지 입니다.) 예를 들어 자식 프로세스가 특정 전역변수의 값을 바꾸어도 부모 프로세스는 보는 그 변수의 값은 바뀐 값으로 보이지 않습니다. 왜냐하면 자식 프로세스가 보는 메모리 공간(virtual address => physical address 매핑)과 부모 프로세스가 가지는 메모리 공간이 다르기 때문입니다.

즉 전역 변수가 존재하는 실제 메모리 위치(physical address)가 서로 다르기 때문입니다. 쉽게 생각해서 따로 따로 수행시킨 두 프로그램과 큰 차이를 보이지 않습니다. 반면 pthread()로 만든 thread는 그렇지 않습니다. 두 thread가 바꾼 전역 변수의 값이 모두 잘 보이게 됩니다. 따라서 thread 사이에서 데이터를 주고 받는 방법은 아주 쉽습니다. 다만 synchronization을 위해 몇가지 함수들이 존재할 뿐이죠. 반면에 process 사이에 데이터를 주고 받기 위해서는 IPC(Inter Process Communication)이라고 하는 OS가 지원하는 몇가지 방법만을 사용해야 합니다.

Thread / Fork 차이점에 관한 comment -2

Process and Thread

Process는 독립적인 하나의 실행단위로서 다른 process와는 구분된 완전히 별개의 program덩어리이다. 그러나 thread는 보다 작은 개념으로서 process에서 파생되어 나온 실행단위로서 독자적인 환경이나 process id를 가지는 것이 아닌라 하나의 function과 같은 역할을 수행한다. MS-Windows 환경의 경우 Web browser를 여러개 실행시키는 경우 여러개의 독자적인 process로 수행되는 것이 아니라 하나의 process하에 각 thread가 수행되어 화면처리, cache처리 등은 공통의 프로그램 코드를 사용하는 방법을 통해 마치 여러 개의 프로그램이 작동하는 것과 같이 작동한다.

Process는 fork()함수를 통해 생성되며 처음 부팅이후 init process에서 모든 process가 파생되어 나오게 된다.

Thread vs Process

  • 각 클라이언트를 다루기 위해 새로운 프로세스를 하나 만드는 것은 비용이 많이 든다. 프로세스가 하나 생성될 때마다 운영체제는 메모리, 스택, 파일/소켓 식별자들 및 기타를 포함한 부모 프로세스의 전체 상태를 복사한다.
  • Thread들은 같은 프로세스 내의 멀티태스킹을 허용함으로써 이러한 비용을 감소. 새로 생성된 Thread는 부모와 같은 주소공간(코드 및 데이터)을 공유하고, 부모의 상태를 복제할 필요성 배제
  • 프로세스 복제이후 부모와 자식간에 정보를 주고받기 위해 프로세스간 통신(IPC) 필요 (자식으로부터 부모로 정보를 되돌리는 것은 더욱 많은 작업을 요구)
  • 프로세스 중의 모든 Thread가 공유하는 것
    • 프로세스 지시 사항
    • 대부분의 데이터
    • 공개된 파일들(Ex 지정 번호들)
    • 신호 처리기와 신호 배치들
    • 사용자와 그룹 ID`
  • 각 Thread 자신만이 갖는 것
    • Thread ID
    • 프로그램 계수기와 스택 지시자를 포함한 레지스터의 조합
    • (지역변수와 반환 주소를 위한) stack\
    • errno
    • 신호 선별
    • 우선순위

Thread / Fork 차이점에 관한 comment -3

Thread라는 것은, 사전에서 찾아보면 '실의 가닥', '줄거리' 같은 뜻으로 나온다고 한다. 그것이 컴퓨터와 무슨 관계가 있는 것인지는 알기 힘들지만, 컴퓨터에서 thread라는 것은 '한 프로그램의 처리 흐름'이라고 설명하는 게 좋을 듯 하다 (줄거리라는 것과 의미가 많이 비슷하다). 하나의 thread는 자신이 갖고 있는 명령어들을 CPU에 집어넣고, 그걸 처리하고, 계속 코드를 진행하게 된다.

그러다 보니 각 thread는 자신만의 데이터, 프로그램 카운터, 레지스터, 스택, 스택 포인터 등, 갖고 있을 것은 다 갖고 있다. 말이 좀 힘들게 느껴지니 예를 하나 들어보자. 우리가 흔히 쓰는 MP3 플레이어를 보면, 이 MP3 플레이어는 여러 가지 동작을 한다. 일단 음악을 재생하게 되겠고, 동작을 입력받기도 해야하고, 시각적 효과 같은 것들도 제공해야 하고, 뭐 하다못해 갑자기 다른 프로그램이 창을 덮었다가 다시 보이도록 하면 창을 다시 그려주기도 해야한다. 할 일 투성이다. 그런데 이런 프로그램을 짜기 위해서 순서도를 그린다면 어떤 식으로 되어야 할까? 만만치 않게 될 것이다.

결국, 여러 순서도를 그려놓고, 그것들이 동시에 돌아가도록 하는 것이 가장 편하겠다. 먼저 기본 순서도를 만들어놓고, 재생 명령이 들어오면 음악을 출력하는 순서도를 시작하고, 그 원래 기본 순서도는 동시에 돌아가도록 하는 방식으로 말이다. 이 때 이 '순서도'가 바로 thread이다. 이렇게 보면 thread는 프로세스(process)랑 매우 비슷해 보인다. 그렇지만 프로세스는 한 운영체제(OS)의 차원에서 돌아가는 반면, thread는 process에 비해서는 규모가 작다. 프로세스는 자기 자신만의 데이터를 갖고, 프로세스 바깥의 자원을 건드리려면 kernel을 직접 건드려야 하는 데 비해, thread는 그 프로세스의 범위 내에서 모든 것을 다른 thread 들과 직접적으로 공유하게 된다. 그렇기 때문에 만드는 데 들어가는 자원의 양 역시 thread쪽이 비교할 수 없을 정도로 적다.

반응형