호우동의 개발일지

Today :

article thumbnail

Fork() 및 Exec() 시스템 콜

  • 다중 스레드 프로그램에서는 fork()와 exec()의 의미가 달라질 수 있다.
  • fork()에 대한 질문 - 한 프로그램의 스레드가 fork()를 호출했을 때
      1. 새로운 프로세스는 모든 스레드를 복제해야 하는가?
      2. 한 개의 스레드만 가지는 프로세스여야 하는가?
    • 몇몇 UNIX 기종은 두 가지 버전의 fork()를 다 제공
    • 두 버전 중 어느 쪽을 택할 것인지는 응용프로그램에 달려 있음

  • exec() 시스템 콜을 부를 때
    • exec()의 매개변수로 지정된 프로그램이 모든 스레드를 포함한 전체 프로세스를 대체
  • fork()를 부르자마자 다시 exec를 부르면 모든 스레드를 복제해서 만들어주는 것은 불필요
    ← exec에서 지정한 프로그램이 곧 모든 것을 다시 대체할 것이기 때문
    • 이 경우 fork() 시스템 콜을 호출한 스레드만 복사해 주는 것이 적절
  • 새 프로세스가 fork() 후 exec를 하지 않는 경우
    → 새 프로세스는 모든 스레드를 복제해야 함

 


신호처리(Signal Handling)

  • 신호는 UNIX에서 프로세스가 어떤 이벤트가 일어났음을 알려주기 위해 사용
  • 신호는 알려줄 이벤트의 근원지나 이유에 따라 동기식/비동기식으로 전달
    • 동기식 신호
      • 신호를 발생시킨 연산을 수행한 동일한 프로세스에 전달 ← 동기식인 이유
      • 예 : 불법적인 메모리 접근, 0으로 나누기 → 이러한 행동을 하면 신호가 발생
  • 비동기식 신호
    • 신호가 실행 중인 프로세스 외부로부터 발생하면 비동기식으로 전달받음
    • 예 : Control+C 같은 프로세스 강제 종료, 타이머가 만료되는 경우
    • 비동기식 신호는 통상 다른 프로세스에 전달됨


신호의 전달 형태

  1. 신호는 특정 이벤트가 일어나야 생성
  2. 생성된 신호된 프로세스가 전달된다.
  3. 신호가 전달되면 반드시 처리되어야 한다.
  • 신호는 다른 방식으로 처리될 수 있다.
    • 일부 신호는 무시될 수 있지만 다른 신호는 프로그램을 종료하여 처리된다.
      • 다른 신호 → 불법 메모리 액세스

 

디폴트 신호 처리기와 사용자 정의 처리기

  • 모든 신호는 디폴트 처리기, 사용자 처리기 둘 중 하나로 처리됨
    • 디폴트 처리기 : 모든 신호마다 커널이 실행시킴
    • 사용자 정의 처리기 : 신호를 처리하기 위하여 호출됨
    • 디폴트 처리기는 사용자 정의 처리기에 의해 대체될 수 있다.

 

다중 스레드 프로그램에서의 신호처리

  • 어느 스레드에 신호를 전달해야 하는가? - 4가지 선택이 존재
    1. 신호가 적용될 스레드에게 전달한다.
    2. 모든 스레드에 전달한다.
    3. 몇몇 스레드들에만 선택적으로 전달한다.
    4. 특정 스레드가 모든 신호를 전달받도록 지정한다.

 

신호 전달하는 방법 - 신호의 유형에 따라 다름

  • 동기식 신호는 그 신호를 호출한 스레드에 전달해야 함
    → 다른 스레드에 전달되면 안 됨

  • 비동기식 신호의 경우 → 명확하지 않음
    • Control+C 같은 비동기식 신호는 그 프로세스 내 모든 스레드에게 전달되어야 함
      • UNIX - kill(pid_t pid, int signal)
        • 함수는 특정 신호가 전달될 프로세스(pid)를 지정한다.
        • 때론 비동기식 신호를 봉쇄하지 않고 있는 스레드들에게만 신호를 전달해야 할 수도 있음
          • 다중 스레드 UNIX는 스레드를 받아들일 신호와 봉쇄할 신호를 지정할 선택권을 부여

  • Windows - 비동기식 프로시저 호출(APC)
    • 신호를 명시적으로 지원하지는 않지만 APC를 통해 대리 실행 가능
    • APC는 사용자 스레드가 특정 이벤트의 발생을 전달받았을 때,
      호출될 함수를 지정할 수 있게 한다.

  • UNIX의 비동기식 신호와 유사
    • 차이점 : APC는 프로세스에 전달되는 것이 아니라 특정 스레드에게 전달됨으로 더 간단

 


스레드 취소


개념

  • 스레드가 끝나기 전에 스레드를 강제종료시키는 작업을 일컫는 말
  • 예시
    • 여러 스레드가 데이터베이스를 병렬로 검색하다가
      한 스레드가 결과를 찾으면 나머지 스레드는 취소 가능

  • 웹브라우저에서 사용자가 웹페이지를 더는 적재하지 않기 위해 stop버튼을 클릭할 경우
    • 종종 웹페이지는 여러 스레드들을 사용하여 적재됨
      → 사용자가 stop을 누르면 웹 페이지를 가져오던 모든 스레드가 취소

 


목적 스레드(target thread)

  • 목적 스레드 : 취소되어야 할 스레드
  • 2가지 방식으로 발생 가능
    1. 비동기식 취소(asynchronous cancellation)
      • 한 스레드가 즉시 목적 스레드를 강제 종료 시킨다.
  1. 지연 취소(deferred cancellation)
    • 목적 스레드가 주기적으로 자신이 강제 종료 되어야 할지를 점검
    • 이 경우 목적 스레드가 안전하게(절차적으로) 강제종료될 수 있는 기회가 만들어짐

 


스레드 취소 - 할당된 자원 문제

  • 스레드 취소를 어렵게 만드는 것은 취소 스레드들에게 할당된 자원 문제
  • 다른 스레드와 공유하는 자료구조를 갱신하는 도중에 취소 요청이 와도 문제가 됨
    • 비동기식 취소의 경우 더 심각
      • 비동기식 취소에 의해 필요한 시스템 자원을 다 사용가능한 상태로 만들지 못할 수 있음
        • 원래 운영체제는 취소된 스레드로부터 시스템자원을 회수할 수 있음

  • 지연 취소의 경우에는 한 스레드가 목적 스레드를 취소해야 한다고 표시해야 함

    → 하지만 실제 취소는 목적 스레드가 취소 여부를 결정하기 위한 플래그 검사 이후에 일어남

    • 스레드는 자신의 취소되어도 안전하다고 판단되는 시점에서 취소 여부를 검사할 수 있음

 

Pthread에서 스레드 취소 - pthread_cancel()

  • 목적 스레드의 식별자가 이 함수의 매개변수로 전달
  • pthread_cancel()을 호출하면 대상 스레드를 취소하라는 요청만 표시
    • 하지만, 실제 취소는 요청을 처리하기 위해 대상 스레드가 설정되는 방식에 달려 있음
    • 대상 스레드가 최종적으로 취소되면 취소 스레드의 pthread_join() 호출이 반환

  • Pthread는 3가지 취소 모드를 지원 ← API를 사용하여 취소 상태 및 유형 설정 가능
    식
    • 취소 비활성화(Off)
      • 스레드를 취소 불가능
      • 스레드는 나중에 취소를 활성화하고 요청을 응답할 수 있음
        ← 취소 요청은 계속 보류 상태로 유지되기 때문

  • Pthreads는 스레드가 취소를 활성 또는 비활성하는 것을 허용
  • 기본 취소 유형은 지연 취소 → 스레드가 취소점에 도달한 경우에만 취소가 발생
    • 취소점의 예
      • POSIX 및 표준 C 라이브러리에서 대부분의 블로킹 시스템 콜
      • Linux 시스템에서 man pthreads 명령을 호출할 때 나열

        • read() 시스템 콜은 read()에서 입력을 기다리는 동안
          봉쇄된 스레드의 취소를 허용하는 취소점
  • 취소점을 설정하는 기법
    • pthread_testcancel() 함수 호출

      • 취소 요청이 보류 중인 것으로 확인
        pthread_testcancel() 호출이 복귀되지 않고 스레드가 종료된다.
      • 취소 요청이 보류 중이 아닌 것으로 확인  
        → 함수 호출이 복귀되고 스레드가 계속 실행됨  

  • 스레드가 취소될 때 정리 핸들러(clean handler)를 함수가 호출되게 할 수 있다.
    • 해당 기능을 사용하면 스레드가 종료되기 전에 스레드에 있는 모든 자원을 해제할 수 있음

스레드-로컬 저장장치(Thread-Local Storage)

  • 스레드-로컬 저장장치(TLS) : 각 스레드 자신만 접근할 수 있는 데이터
    • 상황에 따라서는 각 스레드가 자기만 액세스 할 수 있는 데이터를 가져야 할 필요도 있음

      • 예 : 트랜잭션 처리 시스템에서 각 트랜잭션을 독립된 스레드가 처리할 때
        • 스레드마다 고유한 식별자를 연관시키기 위해서 스레드 국지 저장소가 있어야 함

 


TLS의 특징

  • TLS는 전체 함수 호출에 걸쳐 보인다.
    • cf. 지역 변수는 하나의 함수가 호출되는 동안에만 보임

  • 개발자가 스레드 생성 과정에 대해 제어할 수 없는 경우 다른 방법이 필요
    • 스레드 생상 과정에 대해 제어할 수 없는 경우 → 스레드 풀과 같은 암묵적 기법

  • 어떤 면에서 TLS는 정적 데이터와 유사
    • 차이점 : TLS 데이터는 스레드마다 고유함

  • 대부분의 스레드 라이브러리 및 컴파일러는 TLS를 지원
    • Java
      • ThreadLocal <T> 객체에 대한 set() 및 get() 메서드와 함께 ThreadLocal <T> 클래스 제공

  • Pthread
    • pthread_key_t 유형이 포함되어 있으며 각 스레드에 고유한 키를 제공
      → 그런 다음 키를 사용하여 TLS 데이터에 접근 가능

 


스케줄러 액티베이션

  • 스레드 라이브러리와 커널의 통신 문제 ← 다중 스레드 프로그램에서 고려해야 할 문제
    • 다대다 및 두 수준 모델에서 반드시 해결해야 하는 문제

  • 이러한 통신의 조정은 응용프로그램이 최고의 성능을 보이도록 보장하기 위해
    커널 스레드의 수를 동적으로 조절하는 것을 가능하게 함

 


경량 프로세스(LWP)

경량 프로세스 구조
경량 프로세스

  • 사용자와 커널 스레드 사이에 중간에 두는 자료구조
    • 다대다 또는 두 수준 모델을 구현하는 많은 시스템이 많이 사용

  • 사용자 스레드 라이브러리에 LWP 방식
    • 응용이 사용자 스레드를 수행하기 위하여 스케줄 할 가상 처리기처럼 보임
    • 각 LWP는 하나의 커널 스레드에 부속되어 있음
      • 물리 처리기에서 스케줄 하는 커널 스레드(대상)

  • 입출력이 완료될 때까지 커널스레드가 봉쇄되면 LWP도 같은 봉쇄됨
    → 이 연관에 따라 LWP에 부속된 사용자 수준 스레드도 같이 봉쇄

 


앱에서의 LWP

  • 앱의 효율을 위해 임의의 개수의 LWP가 필요할 수도 있음
    • CPU 중심 앱이 아닌 입출력 중심 앱은 여러 개의 LWP가 필요할 수 있음
      ← 통상 동시에 발생하는 봉쇄형 시스템 콜마다 하나의 LWP가 필요하다.

        • 서로 다른 5개의 파일 읽기 요청이 발생
          → 모든 LWP가 입출력 완료를 커널 안에서 기다릴 수 있기 때문에 5개의 LWP 필요

스케줄러 액티베이션의 동작과정

  • 스케줄러 액티베이션 ← 사용자 스레드 라이브러리와 커널 스레드 간의 통신 방법
  • upcall
    • 커널이 앱에 가상처리기(LWP)의 집합을 제공 및 특정 이벤트에 대해 알려주고,
      앱은 사용자 스레드를 가용한 가상처리기로 스케줄 하는 과정을 일컫는 말

  • upcall은 스레드 라이브러리의 upcall 처리기에 의해 처리
    • upcall 처리기는 가상 처리기상에서 실행되어야 함

  • 응용 스레드가 봉쇄하려고 할 때 발생하는 upcall 이벤트 과정
    1. 커널은 스레드가 봉쇄하려고 한다는 사실과 그 스레드의 식별자를 알려주는 upcall 한다.
    2. 이후 커널은 새로운 가상 처리기에 응용(앱)을 할당
    3. 앱은 이 새로운 가상 처리기상에서 upcall 처리기를 수행하고,
      봉쇄 스레드의 상태를 저장하고 이 스레드가 실행 중이던 가상 처리기를 반환
      또한, upcall 처리기는 새로운 가상 처리기에서 실행 가능한 다른 스레드를 스케줄


    4. 봉쇄 스레드가 기다리던 이벤트가 발생하면 커널은 이전에 봉쇄되었던 스레드가
      이제 실행할 수 있다는 사실을 알려주는 또 다른 upcall을 스레드 라이브러리에 한다.
    5. 이 이벤트를 처리하는 upcall 처리기도 가상 처리기가 필요하고, 커널은 새로운 가상 처리기를
      할당하거나 사용자 스레드 하나를 선점하여 그 처리기에서 upcall 처리기를 실행


    6. 봉쇄가 풀린 스레드를 실행 가능 상태로 표시한 후에
      앱은 가용한 가상 처리기상에서 다른 실행 가능한 스레드를 실행