본문 바로가기
C언어/Pintos

개요

by lacuca9 2024. 9. 25.

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 +---------------------------------+
  1. Kernel Stack :
    • 크기 : 4kb
    • 스레드가 실행되는 동안 사용하는 스택, 다운워드 방향으로 자람. 데이터 추가될수록 주소가 낮아짐
    • 스레드 컨텍스트(context) 스위칭같은 작업을 처리하는 데 필요.
      인터럽트프레임이나 함수 호출 스택이 여기에 쌓임
    • kernel stack 부분은 스레드가 시스템 호출을 하거나 인터럽트를 처리하는 동안 커널 모드에서 사용됨.

  2. Thread Structure
    스택의 가장 하단 부분에 위치함. 스레드의 상태와 정보를 저장하는 데 사용.
    sizeof(struct thread)로 표시된 영역. 여러 중요한 정보가 들어감
    • magic : 스레드의 무결성을 확인하는 데 사용되는 값.
      스레드가 손상되지 않았는지 확인하기 위한 마커 역할.
      잘못된 메모리 접근이나 스레드 손상 감지.
    • intr_frame : 인터럽트 시 스레드의 레지스터 상태를 저장하는 구조체.
      인터럽트 발생 시, CPU의 레지스터 상태를 저장했다가 다시 복원하는 데 필요
    • status : 스레드의 상태를 나타냄. 스레드가 실행 중인지, 준비 상탠지, 종료인지
    • tid : 스레드 ID. 각 스레드를 고유하게 식별하기 위한 식별자
  3. 메모리 배치
    • 메모리 공간에서 가장 하단(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