Pintos란?
x86-64 아키텍쳐에서 동작하는 간단한 OS 프레임워크
- kernel - threads
- loading and running user programs
- file system
위 3개를 강화할 예정
가상 메모리구현
소스트리 둘러보기
- threads/ : 프젝1에서 수정을 시작할 base kernel 소스 코드
- userprog/ : 프젝2 에서 수정을 시작할 user program loader의 소스코드
- vm/ : 프젝3 에서 가상 메모리 구현
- filesys/ : 기본적인 file system의 소스코드. 프젝2 부터 씀
- devices/ : (키보드, 타이머, 디스크 등) I/O 장치 interfacing을 위한 소스코드. (프젝1 에서 타이머 수정)
- lib/ : (라이브러리, Pintos커널과 (프젝2)user program으로 컴파일 됨
- invide/lib/kernel/ : Pintos 한정 C라이브러리. bitmaps, doubly linked list, hashtables 등 #include <...>로 씀
- invide/lib/user/ : Pintos 한정 C 라이브러리
- tests/ : 각 프로젝트용 테스트
- examples/ : 프젝2 에서 사용할 user programs 용 예제들
- include/ : 헤더파일들(.h)의 소스코드
빌드하기
프젝1 : threads -> make : 그러면 build 디렉토리 생성. (check - 30분 걸림)
build 디렉토리
Makefile - 복사본 커널 빌드 방법
kernel.o : 커널 전체를 위한 Object file. 디버그 정보를 갖고 있고, 이걸로 GDB 또는 Backtraces
kernel.bin : 커널의 메모리 이미지. 메모리에 로드되는 정확한 바이트들의 이미지.
kernel.o에서 디버그 정보가 빠짐
loarder.bin : kernel loarder의 메모리 이미지. 메모리에서 읽고 시작시키는 어샘블리어 코드의 작은 chunk.
dependency 파일들은 다른 소스나 헤더파일이 변경되면,
make 한테 어떤 소스파일이 다시 컴파일되어야 하는지 알려줌
실행하기
pintos argument... 로 pintos에 설정을 적용할 수 있음.
threads/build로 이동. pintos -- -q run 'alarm-multiple' 실행 (이러면 run, alarm argument가 전달)
run은 커널이 테스트를 돌게 함
로그파일에 저장 pintos -- run alarm-multiple > logfile
어떤 옵션을 특정하려면, 옵션들은 pintos 커널에 전달되는 커멘드 보다 앞이어야 됨
'pintos -- option.. --argument...'
THREADS
기본에서 확장하여 synchronization 의 문제들을 잘 이해하게 되는 것이 목표
threads 디렉토리에서 시작. (컴파일은 여기서 이루어져야 함)
devices 디렉토리도 할 일 있음
Threads
스레드 or 유저 프로세스를 의미. thread 구조체에 멤버 추가
스레드 구조체와 스택 구조 :
4 kB +---------------------------------+
| kernel stack |
| | |
| | |
| V |
| grows downward |
| |
| |
| |
| |
| |
| |
| |
| |
sizeof (struct thread) +---------------------------------+
| magic |
| intr_frame |
| : |
| : |
| status |
| tid |
0 kB +---------------------------------+
- Kernel Stack :
- 크기 : 4kb
- 스레드가 실행되는 동안 사용하는 스택, 다운워드 방향으로 자람. 데이터 추가될수록 주소가 낮아짐
- 스레드 컨텍스트(context) 스위칭같은 작업을 처리하는 데 필요.
인터럽트프레임이나 함수 호출 스택이 여기에 쌓임 - kernel stack 부분은 스레드가 시스템 호출을 하거나 인터럽트를 처리하는 동안 커널 모드에서 사용됨.
- Thread Structure
스택의 가장 하단 부분에 위치함. 스레드의 상태와 정보를 저장하는 데 사용.
sizeof(struct thread)로 표시된 영역. 여러 중요한 정보가 들어감- magic : 스레드의 무결성을 확인하는 데 사용되는 값.
스레드가 손상되지 않았는지 확인하기 위한 마커 역할.
잘못된 메모리 접근이나 스레드 손상 감지. - intr_frame : 인터럽트 시 스레드의 레지스터 상태를 저장하는 구조체.
인터럽트 발생 시, CPU의 레지스터 상태를 저장했다가 다시 복원하는 데 필요 - status : 스레드의 상태를 나타냄. 스레드가 실행 중인지, 준비 상탠지, 종료인지
- tid : 스레드 ID. 각 스레드를 고유하게 식별하기 위한 식별자
- magic : 스레드의 무결성을 확인하는 데 사용되는 값.
- 메모리 배치
- 메모리 공간에서 가장 하단(0kb 부분)에 위치하며, 커널 스택은 그 위쪽에 자리 잡음
- 커널 스택은 위에서 아래로 자람. 스택포인터가 커널 스택의 상단을 가리킴.
스레더 구조체는 커널 스택 아래쪽에 고정된 위치에 있어서 스레드의 고유 정보는 항상 일정한
위치에 존재하게 됨.
스레드 구조체에 중요한 점
1. struct thread가 너무 커지면 안됨. 너무 크면 커널 스택을 위한 공간이 없어짐 (1kb미만)
2. 커널 스택도 너무 커지면 안됨. malloc()이나 palloc_get_page()
스레드 식별자 tid는 커널이 돌아가는 내내 유일한 tid가 유지되어야 함.
tid_t tid;
enum thread_status status;
다음 중 하나인 스레드 상태다.
- THREAD_RUNNING : 실행중 상태. 시간 당 1개의 스레드가 실행됨.
thread_current() : 실행중인 스레드 반환 - TRHEAD_READY
준비완, 아직 시작 안함. 스케절루거 호출되었을 때 다음에 실행하도록 선택될 수 있다.
준비된 스레드들은 read_list 라는 doubly_linked-list에 저장 - THREAD_BLOCKED
기다리는 상태. 다시 사용가능하도록 락 되었거나, 호출되기 위해 인터럽트 된 것.
thread_unblock()으로 THREAD_READY 전 까지 스케줄x. - THREAD_DYING
다음 스레드로 전환된 이후, 스케줄러에 의해 없어질 예정인 상태
int priority;
PRI_MIN (0) 에서 PRI_MAX (63) 사이 스레드 우선순위 뜻함.
높으면 우선순위 높음. 프젝1에서 우선순위 스케줄링 구현
Thread Functions
threads/thread.c는 스레드 지원을 위한 몇몇 public 함수를 구현해준다.
void thread_init (void);
주 목적은 pintos의 초기 스레드를 위한 struct thread를 만들어 주는 것.
pintos 로더가 초기 스레드의 스택을 페이지 꼭대기 부분에 놓기 떄문에 가능.
thread_init()이 실행되기 전에는 thread_current()는 실패할 것. 왜냐면 실행되고 있는 스레드의 magic 값이
올바르지 않기 때문.
lock_acquire() 같은 수많은 함수들이 thread_current()를 직간접적으로 호출하기 때문에,
thread_init()은 pintos초기화 과정에서 이른 시기에 호출되어야 한다.
void thread_start (void);
스케쥴러를 시작시키기 위해 main()으로부터 호출된다.
준비된 다른 스레드가 없을 때 스케쥴되는 스레드인 유휴 스레드를 만든다.
스케쥴러가 활성화되도록 하는 side effect가 있는 인터럽트를 활성화한다.
intr_yield_on_return()를 사용해서 타이머 인터럽트가 반환되면 스케줄러가 실행되기 때문에
인터럽트를 활성화 하는 것이다.
void thread_tick (void);
각 타이머 tick에서 발생하는 타이머 인터럽트로부터 호출됨.
스레드 통계를 추적하고, 타임 슬라이스가 만료될 때 스케쥴러를 작동.
void thread_print_stats (void);
pintos가 종료될 때 스레드 통계를 출력하기 위해 호출됨.
tid_t thread_create (const char *name, int priority, thread func *func, void *aux);
새 스레드를 생성하고 tid 반환. 새 스레드의 이름은 name,
우선순위는 priority가 된다. 함수의 단일 인자로 aux를 전달하면서 func를 실행한다.
thread_create()는 스레드의 struct thread와 스택을 위해 페이지를 할당하고 그 멤버들을 초기화.
그리고 가짜 스택 프레임을 만듦. 블록된 상태로 초기화되고, 새로운 스레드가 스케줄될 수 있도록
하기 위해서 반환되기 직전에 언블록된다.
void thread_func (void *aux);
thread_create() 로 전달되는 함수 타입이다. aux 인자도 이 함수의 인자로써 같이 전달됨.
void thread_block (void);
실행되고 있는 스레드를 실행 상태에서 블록 상태로 전환.
전환된 스레드는 thread_unblock() 이 호출되기 전까지 다시 동작x, 언블록되도록 하기 위해서 수정해야댐.
thread_block()이 너무 로우레벨이기 때매, 이 함수 대신 동기화 기초요소 중 하나를 쓰는게 나음.
void thread_unblock (struct thread *thread);
블록된 상태의 스레드를 준비 상태로 전환. 스레드가 기다리던 이벤트가 발생했을 때 호출.
스레드가 대기 하고 있던 락이 사용되게 함.
struct thread *thread_current (void);
실행중인 스레드 반환
tid_t thread_tid (void);
실행중인 스레드의 tid 반환
const char *thread_name (void);
name 반환
void thread_exit (void) NO_RETURN;
스레드 종료. 반환x
void thread_yield (void);
실행할 새 스레드를 선택하는 스케줄러에게 CPU제공.
새 스레드가 현재 스레드일수도 있어서, 이 함수에 의존하면 안됨
int thread_get_priority (void);
void thread_set_priority (int new_priority);
우선 순위 설정하고 가져오는 토막(stub)
int thread_get_nice (void);
void thread_set_nice (int new_nice);
int thread_get_recent_cpu (void);
int thread_get_load_avg (void);
고급 스케줄러를 위한 토막들(stubs)이다.
'C언어 > Pintos' 카테고리의 다른 글
WIL3 (1) (0) | 2024.10.10 |
---|---|
WIL (0) | 2024.10.01 |
Understanding Threads (0) | 2024.09.25 |
Synchronization (0) | 2024.09.25 |