스레드 단위로 관리되는 구조체로, 각 스레드의 문맥과 제어 정보를 저장.
- 하나의 PCB(Process Control Block)에 여러 TCB가 있을 수 있다.
(즉, 프로세스 내의 스레드 단위로 관리.)
* 위 그림은 Linux에서의 TCB와 PCB의 구조이다.
/* threads/thread.c */
struct thread {
/* Owned by thread.c. */
tid_t tid; /* Thread identifier. */
enum thread_status status; /* Thread state. */
char name[16]; /* Name (for debugging purposes). */
int priority; /* Priority. */
int init_priority; /* 해당 스레드의 '원래' Priority. */
int nice; /* nice값: 클수록 CPU 양보 ↑ */
int recent_cpu; /* 해당 스레드가 최근 얼마나 많은 CPU time을 사용 했는지 */
struct list donations; /* 해당 스레드가 가지고 있는 lock을 필요로 하는 스레드들 */
struct list_elem d_elem; /* donations 리스트를 쓰기 위한 리스트 요소 */
struct lock * wait_on_lock; /* 해당 스레드가 기다리고 있는 lock을 가리키는 포인터 */
int64_t wake_tick; /* 스레드가 깨어날 시각*/
/* Shared between thread.c and synch.c. */
struct list_elem elem; /* List element. */
struct list_elem allelem; /* List element. */
#ifdef USERPROG
/* Owned by userprog/process.c. */
uint64_t *pml4; /* Page map level 4 */
/* Project 2: System Call 구현 */
int exit_status; // exit 상태를 나타내는 정수형 변수
struct file **fdt; // fd 테이블
int fd_idx; // fd 인덱스
struct file *runn_file; // 실행중인 file
struct intr_frame parent_if; // 해당 스레드의 interrupt frame
struct list child_list; // 자식 프로세스 리스트
struct list_elem child_elem; // 자식 프로세스 리스트 요소
struct semaphore fork_sema; // fork가 완료될 때 sgnal
struct semaphore exit_sema; // 자식 프로세스 종료 signal
struct semaphore wait_sema; //exit_sema를 기다릴 때 사용
#endif
/* Owned by thread.c. */
struct intr_frame tf; /* Information for switching */
unsigned magic; /* Detects stack overflow. */
};
위에서 얘기했던 스레드 ID, 상태 정보, 스택 포인터, 레지스터 상태, 우선 순위 등은 모두 struct thread
자료구조에 포함된다.
사실상 struct thread
가 TCB에 해당하는 정보들을 가지고 있고, 스레드가 스케줄링 되고 실행 상태가 변화하면서 스레드 간 Context Switching 동작을 수행한다.
프로세스 단위로 관리되는 구조체로, 각 프로세스의 상태와 자원 정보를 저장.
- PCB는 스레드 단위의 정보를 포함하는 TCB를 관리하며, 프로세스 자원을 책임짐.
* 위 그림은 Linux에서의 TCB와 PCB의 구조이다.
우리가 작성하고 있는 PintOS는 기본적으로 단일 스레드 프로세스를 전제한다.
즉, 멀티 스레딩을 지원하지 않는다.
→ 스레드의 관리 (TCB)와 프로세스의 관리(PCB)를 구분할 필요가 크지 않다!
따라서, 단일 자료구조인
struct thread
만으로 TCB와 PCB를 분리하지 않고 구현이 가능하다.
보통 멀티스레드를 지원하는 시스템에서 각 스레드가 TCB를 통해 관리되고, 이러한 스레드가 속한 프로세스는 PCB를 통해 관리된다.
PintOS와 같은 프로젝트에서는 struct thread
로 이러한 구조를 단순화 시키는 것이 효율적이다!
/* include/threads/interrupt.h */
struct intr_frame {
/* intr-stubs.S의 intr_entry에 의해 푸시됨.
중단된 작업의 저장된 레지스터입니다. */
struct gp_registers R; // 정수 레지스터 구간
uint16_t es; // Extra Segment - Extra Data 영역
uint16_t __pad1;
uint32_t __pad2;
uint16_t ds; // Data Segment - 데이터 영역
uint16_t __pad3;
uint32_t __pad4;
/* intr-stubs.S의 intrNN_stub에 의해 푸시됨. */
uint64_t vec_no; /* Interrupt vector number. */
/* 때로는 CPU에 의해 푸시되고, 그렇지 않으면 일관성을 위해 intrNN_stub에 의해 0으로 푸시됩니다.
CPU는 이를 'EIP' (Extended) Instruction Pointer 바로 아래에 두지만 우리는 여기로 옮깁니다. */
uint64_t error_code;
/* CPU에 의해 푸시됨.
중단된 작업의 저장된 레지스터입니다.. */
uintptr_t rip; // Instruction Pointer = Program Counter 다음에 실행될 명령의 주소
uint16_t cs; // Code Segment - 명령어 영역
uint16_t __pad5;
uint32_t __pad6;
uint64_t eflags; // Extended Flags - 상태, 제어, 시스템 플래그 -> 레지스터가 어떤 일을 수행하는 지
uintptr_t rsp; // Stack Pointer - 스택 포인터
uint16_t ss; // Stack Segment - 임시 Stack 영역
uint16_t __pad7;
uint32_t __pad8;
} __attribute__((packed));
Interrupt 또는 System Call 발생 시 CPU의 실행 상태(레지스터, 스택 포인터, 플래그 등)를 임시 저장.
- Interrupt, System Call이 발생했을 때, 기존의 실행 상태를 보존하여 작업이 끝난 후 복구할 수 있게 한다.
rax
, rbx
, rsp
, rip
등.rflags
).cs
, ss
등.→ Interrupt, System Call 발생 시 레지스터 상태 임시 저장 및 복원 (의미, 개념론적인 interrupt frame으로 사용)
→
struct thread
구조체 안에 위치하면서, 스레드 간 문맥 전환이 발생할 때에도 레지스터 상태 정보를 저장. (레지스터 상태를 저장하는 자료구조로서 사용)" 스레드의 레지스터 정보도 저장해야하는데, 이를 어떻게 저장할까? "
" → 이미 레지스터를 저장할 수 있는 구조체가 있으니, 이를 재사용하자! "
/* threads/thread.h */
struct thread {
/* Owned by thread.c. */
tid_t tid; /* Thread identifier. */
enum thread_status status; /* Thread state. */
char name[16]; /* Name (for debugging purposes). */
int priority; /* Priority. */
int origin_priority; //* 본래 priority
...
#ifdef USERPROG
/* Owned by userprog/process.c. */
uint64_t *pml4; /* Page map level 4 */
/* Project 2: System Call 구현 */
int exit_status; // exit 상태를 나타내는 정수형 변수
struct file **fdt; // fd 테이블
int fd_idx; // fd 인덱스
struct file *runn_file; // 실행중인 file
struct intr_frame parent_if; // interrupt frame
struct list child_list; // 자식 프로세스 리스트
struct list_elem child_elem; // 자식 프로세스 리스트 요소
struct semaphore fork_sema; // fork가 완료될 때 sgnal
struct semaphore exit_sema; // 자식 프로세스 종료 signal
struct semaphore wait_sema; //exit_sema를 기다릴 때 사용
#endif
...
/* Owned by thread.c. */
struct intr_frame tf; /* Information for switching */
unsigned magic; /* Detects stack overflow. */
};
parent_if
, tf
가 말하자면 각각 interrupt frame
, thread frame
.if
는 system call의 처리나 interrupt의 처리에 사용tf
는 thread 간 context switch에 사용
- PintOS에서 TCB, PCB라는 이름은 사용되지 않지만, 스레드 관리를 위해
struct thread
라는 자료구조를 사용하며, 이는 TCB의 역할을 수행한다.
- 이 때, 우리가 구현한 PintOS는 기본적으로 단일 스레드 프로세스를 전제하기 때문에,
struct thread
만으로 사실상 PCB의 역할까지도 구현할 수 있다.
- Interrupt Frame은 스레드 간 Context Switch가 아닌 Interrupt, System Call 등 예외 상황이 발생했을 때 커널과 사용자 모드 간 전환을 지원하기 위해 사용된다.
but! PintOS 안의 thread 정보를 저장할 때 어차피 레지스터 정보도 저장해야 하므로,interrupt frame
을struct thread
안에서도 재사용 한다.
struct thread
자료구조에 사용자 프로그램을 실행할 스레드 정보를 포함. → 프로세스 생성 시 해당 스레드에 프로그램의 실행 파일 적재struct thread
에 저장하고, 다음 실행할 스레드의 상태를 복원해 실행. (즉, Context Switch) → Context Switch 이후 interrupt handler는 intr_frame을 복구하고 프로그램의 실행을 재개.
- [WIL] PintOS 프로젝트와 함께 알아보는 병행성과 동기화 도구: Semaphore, Lock, Mutex, Condition Variable, Monitor
https://velog.io/@takealittletime/WIL-PintOS-프로젝트와-함께-알아보는-병행성과-동기화-도구-Semaphore-Lock-Mutex-Condition-Variable-Monitor