Skip to content

Commit

Permalink
post: thread
Browse files Browse the repository at this point in the history
  • Loading branch information
lja3723 committed Apr 7, 2024
1 parent a49a259 commit cbbd7f0
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 21 deletions.
311 changes: 290 additions & 21 deletions _posts/Computer-Science/OS/2024-04-08-thread.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,26 @@ tags: ['OS', '프로세스', '스레드']

프로세스는 여러 프로그램을 동시에 실행시키기 위해 고안된 개념이지만, 이것을 그대로 사용하기에는 다음과 같은 **문제점**이 있다.

<br>

- **프로세스 생성 오버헤드가 크다.**
- 메모리 할당 → `fork()` → PCB → Page(segment) 매핑 테이블 → ... 으로 이뤄지는 **복잡한 과정**을 거쳐야 한다.
### 1.1. 프로세스 생성 오버헤드가 크다.

<br>
- 메모리 할당 → `fork()` → PCB → Page(segment) 매핑 테이블 → ... 으로 이뤄지는 **복잡한 과정**을 거쳐야 한다.

- **프로세스 간 통신이 어렵다.**
- 프로세스들은 **완전히 독립적인 주소 공간**을 가지고 있다.
- 서로 다른 프로세스끼리는 **간섭이 불가능**하다.
- 프로세스간의 통신을 위해 **별도의 방법이 필요**하다.
- Shared memory
- socket
- message queue 등
### 1.2. 프로세스 간 통신이 어렵다.

**하나의 작업****여러 모듈 단위로 쪼개서 일할 때**, 위와 같은 문제점으로 인해 작업이 어려워진다.
- 프로세스들은 **완전히 독립적인 주소 공간**을 가지고 있다.
- 서로 다른 프로세스끼리는 **간섭이 불가능**하다.
- 프로세스간의 통신을 위해 **별도의 방법이 필요**하다.
- Shared memory
- socket
- message queue 등

![프로세스의 문제점]({{ img_path }}/1. 프로세스의 문제점.png){: width='580'}
_프로세스간 다양한 통신 방법_
![프로세스의 문제점]({{ img_path }}/1. 프로세스의 문제점.png){: width='500'}
_(참고) 프로세스간 다양한 통신 방법_

---

이와 같은 문제점들로 인해 **하나의 작업****여러 모듈 단위로 쪼개서 작업**하기가 어려워진다.

**예를 들어** 미디어 플레이어의 경우, 아래 기능들이 **동시에**[^concurrent] 이뤄져야 한다.

- 영상 처리
Expand All @@ -45,7 +43,7 @@ _프로세스간 다양한 통신 방법_

[^concurrent]: 사실 진짜 동시는 아니고, 시분할을 통해 번갈아가며 실행된다.

### 1.1. → 스레드의 등장 배경
### 1.3. **스레드:** 프로세스의 대안

위와 같은 프로세스의 문제점을 해결하기 위해 고안된 것이 바로 **스레드**이다.

Expand All @@ -65,7 +63,7 @@ _프로세스간 다양한 통신 방법_
- **OS**의 작업 단위: **Process**
- **CPU**의 작업 단위: **Thread**

![스레드 등장 배경]({{ img_path }}/1.1. 스레드 등장 배경.png){: width='400'}
![스레드 등장 배경]({{ img_path }}/1.3. 스레드 등장 배경.png){: width='400'}
_작업 단위의 구분_

[^work-manage-unit]: 참고: OS 입장에서 작업 관리 단위는 프로세스이다.
Expand Down Expand Up @@ -192,26 +190,28 @@ ret2 = 200000
### 3.1. 스레드 사적 공간

- 스레드 코드(Thread code)
- 스레드 로컬 스토리지(TLS, Thread Local Storage)
- **스레드 로컬 스토리지(TLS, Thread Local Storage)**
- 스레드 스택

### 3.2. 스레드 사이의 공유 공간

- 프로세스 코드
- 프로세스의 데이터 공간(로컬 스토리지 제외)
- 프로세스 힙 영역
- 프로세스의 데이터 공간(**로컬 스토리지 제외**)
- **프로세스 힙 영역**

![스레드 주소 공간]({{ img_path }}/3. 스레드 주소 공간.png){: width='500'}

#### **TLS example (for xUNIX OS)**
{: data-toc-skip=''}

- `__thread`: 스레드 로컬 스토리지임을 선언하는 키워드이다.

```c
#include <pthread.h> // pthread lib
#include <stdio.h>
#include <stdlib.h>
int gsum = 0;
int __thread tsum = 1;
int __thread tsum = 1; //__thread: 로컬 스토리지임을 선언하는 키워드이다.

void func(int a) {
printf("5_%d. gsum= %d / tsum= %d \n", a, gsum, tsum);
Expand Down Expand Up @@ -266,6 +266,275 @@ int main() {
8_main. gsum= 8020 / tsum= 1
```

![TLS 실행 결과]({{ img_path }}/3.2. TLS 실행 결과.png){: width='300'}
_실행 결과 참고 도표_



## 4. 스레드 Lifecycle

스레드 라이프사이클은 프로세스의 것과 흡사하며, **TCB로 관리**한다.

### 4.1. 스레드의 상태 변화

| 스레드 상태 | 설명 |
| ------------------------- | ------------------------------------ |
| **준비 상태(Ready)** | 스레드가 스케줄 되기를 기다리는 상태 |
| **실행 상태(Running)** | 스레드가 CPU에 의해 실행 중인 상태 |
| **대기 상태(Blocked)** | 스레드가 입출력 요청하거나 `sleep()`과 같은<br> syscall로 인해 커널에 의해 중단된 상태 |
| **종료 상태(Terminated)** | 스레드가 종료된 상태 |

!스레드의 상태 변화]({{ img_path }}/4.1. 스레드의 상태 변화.png){: width='550'}



## 5. 스레드 Operation

### 5.1. 스레드 생성

- 스레드는 **스레드 생성하는 syscall**이나 **라이브러리 함수를 호출**하여 **다른 스레드를 생성할 수 있음**
- 프로세스 생성 시 자동으로 **main 스레드 생성**

### 5.2. 스레드 종료

- **프로세스 종료****스레드 종료**의 구분이 필요하다.

#### 5.2.1. 프로세스 종료

- 프로세스에 속한 어떤 스레드라도 `exit()` syscall 부르면 **프로세스 종료**(모든 스레드 종료)
- **메인 스레드의 종료**(C언에서 main()의 종료) → **모든 스레드도 함께 종료**

#### 5.2.2. 스레드 종료

- `pthread_exit()`와 같이 스레드만 종료하는 syscall 호출 시 **해당 스레드만 종료**
- `main()`에서 `pthread_exit()` 부르면 역시 **main 스레드만 종료**(다른 스레드는 남아있음 → 프로세스가 살아있음)

### 5.3. 스레드 조인(join)

- 스레드가 **다른 스레드가 종료할 때까지 대기**하는 것
- 주로 **부모 스레드****자식 스레드의 종료를 대기**한다.

![스레드 조인]({{ img_path }}/5.3. 스레드 조인.png){: width='500'}
_스레드의 조인_

### 5.4. 스레드 양보(yield)

- 스레드가 **자발적으로** `yield()` syscall 호출을 통해 **자신의 실행을 중단**하고 **다른 스레드를 스케줄하도록 양보(지시)**하는 것



## 6. Thread Context

스레드의 실행중인 상태 정보들은 **TCB(Thread Control Block)**에 저장이 된다.

### 6.1. Thread Control Block(TCB)

- 스레드 생성 시 커널에 의해 만들어진다.
- 스레드 소멸 시 같이 사라진다.
- 각종 **CPU 레지스터의 값을 관리**한다.
- **PC**: 실행 중인 코드 주소
- **SP/BP**: 실행 중 함수의 스택 주소
- **Flag**: 현재 CPU의 상태 정보
-

나머지 메모리들은 어차피 **공유**되기 때문에, **레지스터만 저장해 두면** 필요할 때 CPU에 복귀하면 이전에 실행하던 상태로 돌아갈 수 있다.

<table>
<thead>
<tr>
<th>구분</th>
<th>요소</th>
<th>설명</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="2">스레드 정보</td>
<td>tid</td>
<td>스레드 ID<br>스레드가 생성될 때 부여된 고유 번호</td>
</tr>
<tr>
<td>state</td>
<td>스레드의 상태 정보<br>Running, Ready, Blocked, Terminated 가능</td>
</tr>
<tr>
<td rowspan="3">컨텍스트</td>
<td>PC</td>
<td>CPU의 PC 레지스터 값</td>
</tr>
<tr>
<td>SP</td>
<td>CPU의 SP 레지스터 값</td>
</tr>
<tr>
<td>다른 레지스터들</td>
<td>스레드 중지 당시 레지스터의 여러 값들</td>
</tr>
<tr>
<td rowspan="2">스케줄링</td>
<td>우선순위</td>
<td>스케줄링 우선순위</td>
</tr>
<tr>
<td>CPU 사용 시간</td>
<td>스레드 생성 이후 CPU 사용시간</td>
</tr>
<tr>
<td rowspan="3">관리를<br>위한<br>포인터들</td>
<td>PCB 주소</td>
<td>스레드 속한 프로세스의 PCB 주소</td>
</tr>
<tr>
<td>다른 TCB에 대한 주소</td>
<td>이웃 스레드를 연결하기 위한 링크</td>
</tr>
<tr>
<td>블록 리스트/준비 리스트 등</td>
<td>입출력 대기하고 있는 스레드 연결하는 TCB 링크<br>준비상태에 있는 스레드 연결하는 TCB 링크(스레드<br> 스케줄링 시 사용) 등</td>
</tr>
</tbody>
</table>

[^info]: ㅎㅎ

![Thread Control Block]({{ img_path }}/6.1. Thread Control Block.png){: width='470'}
_TCB의 도식화_



## 7. Thread Context Switching(a.k.a. thread switching)

- 현재 실행중인 스레드를 중단시키고, 다른 스레드에 CPU를 할당하는 과정
- **현재 CPU 컨텍스트를 TCB에 저장**하고, **다른 TCB에 저장된 컨텍스트를 CPU에 적재**함으로서 구현된다.

![Thread Context Switching]({{ img_path }}/7. Thread Context Switching.png){: width='550'}
_Thread Switching 과정_

### 7.1. CPU 레지스터 저장 및 복귀

1. 현재 실행 중인 **스레드 A의 컨텍스트**를 TCB-A에 저장
2. TCB-B에 저장된 **스레드 B의 컨텍스트**를 CPU에 적재

- **PC**가 복구됨으로서, CPU는 **스레드 B가 이전에 중단된 위치에서 실행 재개**가 가능하다.
- **SP**가 복구됨으로서, 자신의 이전 스택을 되찾게 된다.
- 스택에는 이전 중단 시 실행하던 함수 매개변수/지역변수들이 그대로 저장되어 있음

### 7.2. 커널 정보 수정

- TCB-A와 TCB-B에 스레드 상태정보와 CPU 사용 시간 등을 수정
- TCB-A를 준비 리스트나 블록 리스트로 옮김
- TCB-B를 준비 리스트에서 분리

![Thread Context Switching 2]({{ img_path }}/7. Thread Context Switching 2.png){: width='400'}
_Thread Switching 과정 2_

## 8. Thread Context Switching의 오버헤드

- Context Switching은 **상당히 비싼 작업**: CPU의 본래 할 일 못하고 다른 작업에 리소스를 빼앗긴다.
- 문맥 교환 시간 길거나 잦은 경우, **컴퓨터 처리율이 심각하게 저하**될 수 있음

### 8.1. **동일 프로세스**의 다른 스레드로 스위칭되는 경우

1. 컨텍스트 저장 및 복귀
- 현재 CPU의 컨텍스트(PC, SP 등)를 TCB에 저장
- TCB로부터 스레드 컨텍스트를 CPU에 복귀
2. TCB 리스트 조작
3. 캐시 Flush와 채우기 시간 소요

### 8.2. **다른 프로세스**의 스레드로 스위칭되는 경우

- 다른 프로세스로 교체되면, CPU가 실행하는 주소 공간이 바뀌는 큰 변화로 인해 추가적인 오버헤드가 발생한다.

1. **추가적인 메모리 오버헤드**
- 시스템 내에 현재 실행 중인 프로세스의 매핑 테이블을 **새 프로세스의 매핑 테이블로 교체**한다.
2. **추가적인 캐시 오버헤드**
- 프로세스 바뀌므로 **CPU 캐시에 담긴 코드와 데이터가 무력화**된다.
- 새 프로세스의 스레드가 실행 시작하면 **CPU 캐시 미스 발생**한다. → 다시 캐시 채워지는데 상당한 시간 소요



## 9. 스레드 모델: 멀티스레딩 모델

### 9.1. 스레드의 타입

- **Kernel-level thread**
- OS가 커널에서 관리하는 스레드
- **User-level thread**
- User-space에서 관리하는 스레드

### 9.2. Kernel-level Thread

커널이 직접 생성하고 관리하는 스레드

- 응용프로그램이 **syscall 통해** 커널 레벨 스레드 생성
- 커널 스레드에 대한 정보(TCB)는 커널 공간에 생성하고 공유
- 즉 커널에 의해 스케줄된다.
- **스레드 주소 공간(스레드 코드 & 데이터): 사용자 공간에 존재**
- **main thread는 커널 스레드**
- 응용프로그램 적재되어 프로세스 생성될 때, 자동으로 커널은 main 스레드 생성

#### 9.2.1. Pure(순수) Kernel-level Thread

- 부팅 때부터 커널의 기능 돕기 위해 만들어진 스레드
- 커널 코드 실행하는 스레드
- **스레드 주소 공간은 모두 커널 공간에 형성됨**
- **커널 모드에서 작동**되며, 사용자 모드에서 실행되는 일은 없음

### 9.3. User-level Thread

라이브러리에 의해 구현된 일반적인 스레드

- 응용프로그램이 **라이브러리 함수 호출**하여 사용자 레벨 스레드 생성
- 스레드 라이브러리가 스레드 정보(U-TCB)를 사용자 공간에 생성하고 소유
- 스레드 라이브러리는 사용자 공간에 존재
- 스레드 라이브러리에 의해 스케줄됨
- **커널(OS)은 이들의 존재에 대해 알 수 없음 → 하나의 프로세스로만 인식함**
- 스레드 주소 공간(스레드 코드 & 데이터): **사용자 공간에 존재**

### 9.4 Multithreading models

- 멀티스레드의 구현
- 응용 프로그램에서 작성한 스레드가 시스템에서 실행되도록 구현하는 방법
- 사용자가 만든 스레드가 시스템에서 스케줄되고 실행되도록 구현하는 방법
- 스레드 라이브러리와 커널의 syscall의 상호 협력 필요

#### 9.4.1. Many-to-One(**N:1**) model

- N개의 사용자 레벨 스레드 ↔ 1개의 커널 레벨 스레드 매핑

#### 9.4.2. One-to-One(**1:1**) model

- 1개의 사용자 레벨 스레드 ↔ 1개의 커널 레벨 스레드 매핑

#### 9.4.3. Many-to-Many(**N:M**) model

- N개의 사용자 레벨 스레드 ↔ M개의 커널 레벨 스레드 매핑

#### **비교**
{: data-toc-skip=''}

![스레드 모델 비교]({{ img_path }}/9. 스레드 모델 비교.png)

![스레드 모델 비교 2]({{ img_path }}/9. 스레드 모델 비교 2.png)

#### **참고**: SMT(Simultaneous multithreading)
{: data-toc-skip=''}

- a.k.a., 하이퍼스레딩(Hyper-threading){: width='650'}

![SMT](9. SMT.png)



## 10. 추가적인 스레드 이슈

### 10.1. 멀티스레드와 fork()와 exec()

작성 예정

### 10.2. 자원 동기화 문제: thread-safe 개념

작성 예정



Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/posts/OS/2024-04-08-thread/9. SMT.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit cbbd7f0

Please sign in to comment.