취업,면접 대비

면접 대비 운영체제 공부 (abraham 4장 thread)

studying develop 2019. 12. 9. 16:34

현대의 거의 모든 운영체제는 한 프로세스가 다중 스레드를 포함하는 특성을 제공한다.

목표

  1. 다중 스레드 컴퓨터 시스템의 기초를 이루는 CPU의 기본 단위인 스레드를 소개한다.
  2. Pthreads API 및 Windows와 Java 스레드 라이브러리에 대해 논의한다.
  3. 암시적 스레딩을 지원하는 몇 가지 전략을 탐구한다.
  4. 다중 스레드 프로그래밍과 관련된 여러 쟁점들을 검토한다.
  5. window, linux의 스레드 지원에 대해 알아본다.

4.1 개요

스레드는 CPU 이용의 기본 단위 이다.
스레드는 스레드 ID, 프로그램 카운터, 레지스터 집합, 그리고 스택<https://dsnight.tistory.com/50>으로 구성된다. 스레드는 같은 프로세스에 속한 다른 스레드와 코드, 데이터 섹션 그리고, 열린 파일이나 신호와 같은 운영체제 자원들을 공유한다.

지금 그림 4.1(책)에 단일 및 다중 스레드 프로세스 비교 그림 보면서 알게 된건데, 다중 스레드 구조에서 열린 파일들을 공유 한다면 pthread 프로그래밍에서 어떻게 했어야 했지. 부모가 파일 열고 -> 여러번 fork() -> 각자 파일에 동시에 쓰면 안되잔아...? 공유는 된다는 건데 같이 동시에 접근하지는 말아야 될 수 도 있는건가

나는 부모가 읽고 메모리에 올린후 나중에 메모리에 적고 부모가 디스크에 한꺼번에 쓰도록 했었던거 같다.

4.1.1 동기(motivation)

현대의 컴퓨터에서 동작하는 거의 모든 소프트웨어 응용들은 다중 스레드를 이용한다. 하나의 응용은 몇 개의 실행 흐름을 가진 독립적인 프로세스로 구현된다. 웹 브라우저는 이미지 또는 텍스트를 표시하는 하나의 스레드와 네트워크로부터 데이터를 가져오는 또 다른 스레드를 가질 수 있다.

하나의 응용 프로그램이 여러 개의 비슷한 작업들을 수행할 필요가 있는 상황에서 멀티 스레딩은 좋은 방법이다. 다른 해결책은 서버의 요청을 받아들이는 하나의 프로세스로 동작하게 하는 것이다. 즉 서버에게 서비스 요청이 들어오면, 프로세스는 그 요청을 수행할 별도의 프로세스를 생성하는 것이다. 이 방식은 스레드의 대중화전에는 매우 보편적인 방법이었다.

그러나 프로세스 생성 작업은 매우 많은 시간을 소비하고 많은 자원을 필요로 하는 일이다. 하지만 새 프르세스가 해야 할 일이 기존 프로세스가 하는 일과 동일하므로 대부분의 경우 하나의 프로세스 안에 여러 스레드를 만들어 나가는 것이 더 효율적이다.
또한 현재의 운영체제 커널은 다중화 되어 있다. 커널 안에 다수의 스레드가 동작하고 각 스레드는 장치 또는 인터럽트 처리등의 특정 작업을 수행한다.

4.1.2 다중 스레딩의 장점

** thread vs process usage:

<https://minnie.tuhs.org/CompArch/Lectures/week12.html>

 

  1. 응답성 (Responsiveness) : 대화형 응용을 다중 스레드화하면 응용 프로그램의 일부분이 봉쇄되거나, 또는 응용 프로그램이 긴 작업을 수행하더라도 프로그램의 수행이 계속되는 것을 허용함으로써, 사용자에 대한 응답성을 향상시킨다.

  2. 자원 공유 (Resource sharing) : 코드와 데이터 공유의 이점은 한 응용 프로그램이 같은 주소 공간 내에 여러 개의 다른 작업을 하는 스레드를 가질 수 있다는 점이다.

  3. 경제성(Economy) : 프로세스 생성을 위해 메모리와 자원을 할당하는 것은 비용이 많이 든다. 스레드는 자신이 속한 프로세스의 자원들을 공유하기 때문에, 스레드를 생성하고 문맥 교환하는 것이 보다 더 경제적이다. 즉 프로세스 생성 시간이 스레드 생성 시간보다 오래걸리고, 문맥 교환 시간도 길다.

  4. 규모 적응성( Scalability) : 다중 스레드의 이점은 다중 처리기 구조에서 더욱 증가한다.다중 처리기 구조에서는 각각의 스레드가 다른 처리기에서 병렬로 수행될 수 있다. 단일 스레드 프로세스는 처리기가 아무리 많더라도 오직 한 처리기에서만 실행된다. 뒤에서 자세히

일단 다중 코어,처리기 이고 멀티 스레딩이면 매우 좋은 것이다...

4.2 다중코어 프로그래밍

하나의 코어는 한 번에 오직 하나의 스레드만 실행할 수 있기 때문에 안일 코어 시스템 상에서 병행성은 단순히 스레드의 실행이 시간에 따라 교대로 실행된다는 것을 의미한다. 그러나 여러 코어를 가진 시스템에서는 시스템이 개별 스레드를 각 코어에 배정할 수 있기 때문에 병행성은 스레드들이 병령적으로 실행될 수 있다는 것을 뜻한다.

4.3 다중 스레드 모델(Multithreading Models)

<예시 : https://www.quora.com/What-are-some-best-examples-of-multithreaded-applications>

 

스레드를 위한 지원은 사용자 스레드(user threads)를 위해서는 사용자 수준에서, 커널 스레드(kernel threads)를 위해서는 커널 수준에서 제공된다. 사용자 스레드는 커널 위에서 지원되며 커너의 지원없이 관리된다. 반면에 커널 스레드는 운영체제에 의해 직접 지원되고 관리된다.

궁극적으로 사용자 스레드와 커널 스레드는 어떤 연관 관계가 존재해야 한다. 연관 관계를 확립하는 세 가지 일반적인 방법을 살펴보자.

user level thread vs kernel level thread

[user level thread] https://kldp.org/node/295

User-Level threads는 응용 프로그램과 Link/Load가 되는 라이브러리로 구현되어집니다. 이 라이브러리에 동기화, 스케줄링 기능을 모두 담고 있습니다. 커널에서는 아무런 지원을 해주지 않으며, 커널이 보기에는 단지 그냥 Single process일뿐입니다. 프로세스마다 런타임 라이브러리의 Copy가 호출되므로 스케줄링 정책을 프로세스마다 달리 취할 수 있으며, 각 Thread마다 time quantum을 소모할 필요 없고, 런타임 라이브러리가 context를 유지하기때문에 switching을 할 필요가 없습니다. 그래서 User-Level Threads는 빠르고, 매우 효율적입니다. 그러나 장애가 꽤 있습니다.

  1. Blocking System Calls
    Blocking function이란 처리가 완료되지않으면 return되지 않는 함수인데, 만약 특정 Thread에서 Blocking이 되어 버리면, 전체 process가 Blocking이 되어버립니다. 이런 이유로 운영체제가 제공하는 non-blocking 함수들만 사용해야 하며, 사용 빈도가 높은 함수(read,select,wait,...)들은 해당 함수의 non-blocking 버젼으로 대체해야할 필요가 있습니다.

  2. Shared System Resources
    동기화나 Locking없이 Thread끼리 공유하는 변수(드러나지 않고 감춰져 있는 경우)가 있을때, 그 Thread가 thread-safe하지 않으면 Overwrite되는 문제가 생길 수 있습니다. 이 이유로 사용할 함수는 재진입이 가능해야합니다. User-Level뿐만아니라 Kernel-Level 함수까지 모두.

  3. signal Handling, Thread Scheduling
    User-Level에서 이것을 구현하기란 상당히 어렵습니다. Timeslice를 다루기 위해 Hardware Clock 인터럽트를 보통의 방법으로는 받지 못합니다. 선점형(Preemptive) 스케줄링을 하기 위해서는 커널로 부터 Time Siganl을 받는 함수를 등록해두어야 하며, Timer Alarm Siganl을 다루는것은 다른 시그널을 다루는 것보다 아주 어럽습니다.

  4. Multiprocess Utilization
    하나의 프로세스에서 Time을 공유하고 있기때문에 여러개의 CPU를 동시에 사용할 수는 없습니다.

정리 - 구현상의 어려움과 복잡성 그리고, 몇가지 장애에도 불구하고, concurrency와 efficiency의 이득을 가져다 줍니다.
[kernel vs user ]https://www.geeksforgeeks.org/difference-between-user-level-thread-and-kernel-level-thread/ 여기서 본 예시로 phtread도 user level thread 였네.

[Kernel-level threads]

Kernel-level에 있는 Threads는 독립적으로 스케줄되므로 특정 Thread에서의 Blocking이 process로 전파되지 않습니다. 그래서 Blocking System Calls를 이용할수 있습니다. 또한 각 Threads끼리 Signal을 주고 받을 수 있습니다.
Kernel-level threads는 특별히 고려할만한 장애를 가지고 있지는 않습니다. 물론 마찬가지로 Thread-Safe해야 하지만, OS 개발자들은 대개의 표준 라이브러리를 Thread-Safe하게(재진입해도 문제없겠끔) 만들기에 User-level threads보다 통상적으로 보다덜 말썽을 피웁니다.

4.3.1 다대일 모델 (Many-to-One Model)

많은 사용자 수준 스레드를 하나의 커널 스레드로 사상한다. 스레드 관리는 사용자 공간의 스레드 라이브러리에 의해 행해진다. 따라서 효율적이라 할 수 있다.

초기 버전의 자바 그리고 솔라리스 시스템을 위한 그린 스레드 라이브러리가 다대일 모델이였는데 다중 코어의 이점을 살릴 수 없으므로 이 모델은 더 이상 사용되지 않는다.

4.3.2 일대일 모델 (One-to-One Model)

각 사용자 스레드를 각각 하나의 커널 스레드로 사상한다. 이 모델은 하나의 스레드가 봉쇄적 시스템을 콜을 호출하더라도 다른 스레드가 실행될 수 있기 때문에 다대일 모델보다 더 많은 병렬성을 제공한다. 또한 이 모델은 다중 처리기에서 다중 스레드가 병렬로 수행되는 것을 허용한다. 이 모델의 단 하나의 단점은 사용자 수준 스레드를 생성할 때 그에 따른 커널 스레드를 생성해야 한다는 점이다. 커널 스레드를 생성하는 오버헤드가 응용 프로그램의 성능을 저하시킬 수 있으므로, 이 모델의 대부분의 구현은 시스템에 의해 지원되는 스레드의 수를 제한한다. Windows 계열의 운영체제들과 Linux가 일대일 모델을 구현한다.

4.3.3 다대다 모델 (Many-to-Many Model)

다대다 모델은 여러 개의 사용자 수준 스레드를 그보다 작은 수 혹은 , 같은 수의 커널 스레드로 멀티플렉스 한다. 커널 스레드의 수는 응용 프로그램이나 특정 기계에 따라 결정된다. 일대다, 일대일 모델 각각의 문제점을 해결한 방법이 다대다 모델이다. 개발자는 필요한 만큼 많은 사용자 수준 스레드를 생성할 수 있다. 그리고 커널 스레드가 다중 처리기에서 병렬로 수행될 수 있다. 또한 스레드가 봉쇄형 시스템 콜을 발생시켰을 때, 커널이 다른 스레드의 수행을 스케줄 할 수 있다.

스레드 라이브러리 (Thread Library)

4.4.1 Pthreads

스레드 생성과 동기화를 위해 제정한 표준 API이다. 스레드의 동작에 관한 명세이지 구현은 아니다!!
그리고 커널,사용자 수준 라이브러리 모두 제공 가능하다.

4.6 스레드와 관련된 문제들 (Threading Issues)

4.6.1 Fork() 및 Exec() 시스템 호출

fork()가 별도의 복제된 프로세스를 생성하는데 사용됨을 보았다.

다중 스레드 프로그램에서는 fork()와 exec()의 의미가 달라질 수 있다.
만일 한 프로그램의 스레드가 fork()를 호출하면 새로운 프로세스는 모든 스레드를 복제해야 하는가 아니면 한 개의 스레드만 가지는 프로세스이어야 하는가? 유닉스는 둘다 가능하다. 즉 하나는 모든 스레드를 복사하는 것과 다른 하나는 fork()를 호출한 스레드만 복제한다.

exec() 시스템 호출은 보통 3장에서 기술한 것과 같은 방법으로 수행된다. 즉 어떤 스레드가 exec() 시스템 호출을 부르면 exec()의 매개변수로 지정된 프로그램이 모든 스레드를 포함한 전체 프로세스를 대체 시킨다.
대체 시킨다니? 해당 스레드의 프로세스의 모든 하위 스레드들이 매개변수로 지정된 프로그램을 실행시킨다는 건가

두 버전의 fork()중 어느 쪽을 택할 것인지는 응용 프로그램에 달려있다. fork()를 부르자마자 다시 exec()을 부른다면 모든 스레드를 다 복제해서 만들어주는 것이 불필요 하다. 어차피 exec()이 다시 그 스레드들의 모든 것을 대체할 것이므로 복사 2번할 필요 없이 만들고 대체할 필요 없다. fork()로 해당 스레드만 복사하는 것이 적절하다. 그러나 fork()후 exec을 하지 않는 다면 새 프로세스는 모든 스레드들을 복제해야 한다.

4.6.5 스케줄러 엑티베이션 (Scheduler Activatoins)

다중 스레드 프로그램과 관련하여 마지막으로 고려할 문제는 스레드 라이브러리와 커널의 통신 문제이다. 통신 조정은 응용 프로그램이 최고의 성능을 보이도록 보장하기 위하여 커널 스레드의 수를 동적으로 조절하는 것을 가능하게 한다.

다대다 또는 두 수준 모델을 구현하는 많은 시스템들은 사용자와 커널 스레드 사이에 중간 자료 구조를 둔다. 이 자료 구조를 경량 프로세스 LWP라 부른다.

사용자 스레드 라이브러리에게 LWP 방식은 응용이 사용자 스레드를 수행하기 위하여 스케줄 할 가상 처리기(virtual processor) 처럼 보인다. 각 LWP는 하나의 커널 스레드에 부속되어 있으며 물리 처리기에서 스케줄 하는 대상은 바로 이 커널 스레드이다. 입출력이 완료되기를 기다리는 동안 같이 커널 스레드가 봉쇄되면 LWP도 같이 봉쇄된다. 이 연관을 따라 LWP에 부속된 사용자 수준 스레드도 역시 봉쇄된다.

사용자 스레드 라이브러리와 커널 스레드 간의 통신 방법 중의 하나는 스케줄러 엑티베이션이라고 알려진 방법이다. 작동 방식은 커널은 응용에게 가상 처리기(LWP)의 집합을 제공하고 응용은 사용자 스레드를 가용한 가상 처리기로 스케줄 한다. 커널은 응용에게 특정 사건에 대해 알려줘야 한다. 이 프로시저를 upcall이라 부른다.
음 몰랐던 부분인데 upcall 설명 이해가 잘 안되서 생략..