본문 바로가기
OS/Pintos P.J_2

[Project 2_User Programs]_Intro

by Success T.H.I.E.F 2022. 1. 1.

project 2 Intro

프로 젝트 2에서는 프로그램과 OS 사이 시스템 콜을 통해 상호작용을 하는 것에 관하여 다루어 본다고 한다. 프로젝트 1을 통과 못했다면 2에 영향이 있으니 프로젝트 1의 Advanced Scheduler 전까지 완료를 하고 이어 가자.

 

<프로젝트 2를 들어가기전에 알고 있어야 하는 사항>

하나이상의 프로세스가 동시에 진행하도록 해야 한다.

멀티스레드는 지원하지 않고 각 프로세스는 하나의 스레드를 가진다 

사용자 프로그램은 가상의 Machine에서 쓰였다고 생각하고 메모리 스케쥴링, 기타 다른 상태들도 관리하여 이 Machine을 올바르게 유지시켜야 한다.

project 2를 진행하기에 추천하는 사전 지식  synchronization,  virtual addrees 

 

이전 thread프로젝트에서는 테스트를 진행할 때 코드를 커널에 직접 컴파일해서 진행하였기 때문에 우리는 커널에 구체적인 기능인 터페이스들을 커널에 넣어야 했다. 하지만 프로젝트 2에서는 사용자 프로그램을 실행해 운영체제를 테스트해본다. 확실히 편해지지만 유저 프로그램 인터페이스는 해당 과제에서 주어지는 요구사항들을 충족할 수 있어야 한다.

 

Source files

이번 프로젝트에서 /userprog 다이렉 살펴보면 좋은 Source file들을 살펴보자

1.process.cprocess.h

ELF 바이너리를 로드하고 프로세스를 시작

> ELF?

Executable and Linkable Format으로 실행파일, 목적 파일, 공유 라이브러리, 코어 덤프를 위한 표준 파일 형식으로 x86 기반 유닉스, 유닉스 계열 시스템들의 표준 바이너리 파일 형식이다

2.syscall.csyscall.h

사용자 프로그램이 커널의 기능에 접근하기를 원할 때 시스템 콜이 포출되는데 위의 소스파일들은 이러한 시스템콜 핸들러의 스켈레톤 함수를 담고 있다. 수정 전의 초에는 메시지 출력, 유저 프로세스 종료만 제공된다. 

이 프로젝트를 통해 코드를 추가해 시스템 콜에 필요한 모든 것을 만든다.

 

3. exception.c, exception.h

사용자 프로그램이 권한이 필요한 또는 금지된 operation을 하게 되면 커널에 exception 또는 fault로 트랩 된다.

이러한 하일은 예외처리가 필요하고 현제 모든 예외처리는 간략하게 메시지를 보내고 프로세스를 종료시킨다.

project 2의 몇몇 설루션은 Page_fault()를 수정하길 요구한다. 

 

4.gdt.c, gdt.h

x86-64는 segmented 아키텍처이다. 

GDT(Global Descripter Table)은 Segment들을 사용하기 위한 Table이다. 위 파일들은 GDT를 셋업 한 파일로 우리가 수정할 일은 없다. 하지만 GDT가 어떻게 쓰이는지 알고 싶다면 보면 좋다.

 

5.tss.c, tss.h

Task-State-Sagment는 task 스위칭에 사용된다. task 스위칭은 더 이상 사용되지 않지만 TSS는 여전히 ring switchin 동안 스택 포인터를 찾는데 필요하다. (사용자 프로세스가 인터럽트 핸들러에 들어갈 때 하드웨어가 TSS에게 커널의 스택 포인터를 찾게 한다.)

이것 역시 우리가 수정할 필요는 없지만 TSS의 작동에 대해 궁금하다면 찾아볼 수 있다.

 

File System 사용

프로젝트 2를 위해 파일 시스템 고트에 인터페이스 해야 한다. 왜냐하면 사용자 프로그램이 파일 시스템으로부터 로드되고 많은 구현해야 하는 많은 시스템 콜이 파일 시스템을 다루기 때문이다.

파일 시스템이 이 프로젝트의 주 목적은 아니기 때문에 간단한 파일시스템이 기본적으로 핀토스에 있다.( filesys/filesys.h, filesys.c) 

파일 시스템은 지금 프로젝트에서 수정할 필요가 없다. project 4에서 다룬다. 

그전까지는 아래 제한사항들이 있다.

1. 동시접속에 대한 동기화 안 해주므로 직접 해줘야 한다.

2. 파일 사이즈는 생성 시 고정되며 루트 디렉토리는 파일로 대표된다. 그렇기 때문에 파일의 생성에 제한이 있다.

3. 파일 데이터는 signal extent로 할당된다. 하나의 파일 속 data는 연속적인 disk sector를 할당받는다. 따라서 파일 시스템을 오래 사용하면 외부 단편화 가 일어날 수 있다.  

4. 하부 디렉토리가 없다.

5. 파일 네임은 14 characters로 제한된다.

6. 작업 중 system-crash가 발생하면 disk가 붕괴되며 자동으로 복구할 방법이 없다. 어떤 식으로든 파일을 복구하는 기능이 파일 시스템에 없다.

 

File을 핀토스 virtual machine에 넣는 법

파일을 핀토스 가상 머신에 넣기 위해서 먼저 파일 시스템 파티션이 있는 시뮬레이션 디스크를 생성할 수 있어야 한다. 

userprog/build 

pintos-mkdisk filesys.dsk 2

를 실행하면 filesys.dsk라는 2MB Pintos 파일 시스템 파티션을 포함하는 이름의 시뮬레이트 된 디스크를 만든다.

그 후 

pintos --fs-disk filesys.dsk를 하면 파일을 디스크에 전달(passing)해 저장한다.

그 후 pintos SCRIPT_COMMANDS -- -f -q으로 시스템 파티션을 포맷한다. 

-f는 파일 시스템 포맷하는 옵션이고 , -q는 포맷을 마치고 핀토스를 나가는 옵션이다.

 

파일을 안팎으로 복사하려면  -p(put) -g(get)을 이용한다. 

파일을 Pintos 파일 시스템에 복사하려면 

pintos -p file -- -q를 하고 이름으로 복사하려면

pintos -p file:newname -- -q를 한다.

 

예시) 터미널 경로 : userprog/build

pintos-mkdisk filesys.dsk 10
pintos --fs-disk filesys.dsk -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'

 10메가 바이트 파일을 하나 만들고 이 파일에 이번 프로젝트의 두 번째 사례인 args-single프로그램을 새로 만든 디스크에 복사한 다음 인수를 전달해 실행하는 방법이다.(인수 전달은 구현전까지 작동 안 한다.)

 

나중에 사용할 일이 없거나 단순히 검사하는 거라면 파일 시스템에 디스크를 보관하지 않고 한 번에 하려면 아래와 같이 하면 된다.

pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'

사용자 프로그램 작동방식

userprog/process.c 에서 제공되는 로더를 사용 ELF실행파일을 로드할 수 있다. 

실제로 x86-64 ELF실행 파일을 출력하는 컴파일러와 링커를 사용하여 Pintes용 프로그램을 생성할 수 있다. 

그러기 위해서는 사용자 프로그램 인터페이스가 특정한 사양을 충족할 수 있도록 해주어야 한다.

 

가상 메모리 레이아웃 

핀토스의 가상 메모리는 사용자 가상 메모리 , 커널 가상 메모리의 두 영역으로 나뉜다.

가상 주소에서 사용자 가상 메모리 범위 

가상 메모리 범위는 가상 주소 0부터 KERN_BASE까지이고 기본값은 0x800400000이다. 

 

사용자 가상 메모리는 프로세스별로 할당된다. 한 프로세스에서 다른 프로세스로 전환할 때 프로세서의 페이지 디렉토리 베이스 레지스터를 변환함으로써 사용자 가상 메모리 주소를 변환한다.

구조체 스레드는 프로세스의 페이지 테이블에 대한 포인터를 포함한다.

 

커널 가상 메모리는 전역으로 되어있어 유저 프로세스와 커널 스레드가 동작하는지와 상관없이 항상 같은 방식으로 매핑된다. 

핀토스에서 커널 가상 메모리는 피지컬 메모리와 KERN_BASE에서부터 1:1 매핑된다. 

KERN_BASE 가상 주소는 물리적 주소 0으로 KERN_BASE + 0X1234는 물리적 주소 0X1324이다.

 

사용자 프로그램은 그 소유자의 가상 메모리만 접근 가능하다. 커널 가상 메모리로의 접근 시도는 Page Fault를 야기한다.

이것은 userprog/exception.c의page_fault()로 핸들링한다.

 

프로세스 종료 시 커널 스레드는 커널 가상 메모리와 running중인 유저 프로세스의 가상 메모리에 접근 가능하다. 하지만 커널에서도 매핑 안된 사용자 가상 주소에서 메모리에 접근하려 한다면 page fault가 발생한다.

 

개념적으로 각 프로세스는 자유롭게 그들의 가상 메모리를 레이아웃 할 수 있지만 현실에서 사용자 가상 메모리는 아래와 같은 레이아웃을 가진다 

Typical Memory Layout

프로젝트 2에서는 사용자 stack 사이즈가 fix 되어있으나 프로젝트 3에서는 할당받아 증가할 수 있을 것이다.

전통적으로 사용되지 않은 데이터 세그먼트의 사이즈는 시스템 콜에 의해 조정될 수 있다 그러나 우리는 이것을 구현해야 한다.

 

핀토스의 사용자 가상 메모리에서  Code segment는 주소 :  0x400000 부터 시작되고 bottom에서부터 대략 128MB의 메모리 공간을 가진다. 이 값은 전형적인 우분투에서 사용되는 value로 큰 의미는 없다.

 

linker는 linker script의 지시에 따라 메모리에서 사용자 프로그램의 레이아웃을 set 한다.

 

Accessing User Memory

시스템 콜의 일부로 커널은 종종 사용자 프로그램에서 제공되는 포인터를 통해 메모리에 접근해야 한다. 

커널은 조심스러워야 한다 왜냐하면 사용자가 null pointer 또는 KERN_BASE위의 커널 가상 주소 공간을 가리키는 pointer를 전달하면 해당 포인터는 가상 메모리에 매핑할 수없기 때문이다. 이러한 타입의 모든 유효하지 않은 포인터에 대해 진행 중인 프로세스 또는 커널에게 해가 되지 않기 위해 반드시 거절되어야 한다.

 

이것을 처리하는 2가지 방법으로 첫째 가장 간단한 사용자 메모리 접근 핸들링 방법으로 사용자가 제공하는 포인터의 validity를 확인하여 역참조 하는 방법이 있다. 만약 이 route를 선택하면 thread/mmu.c 와 include/threads/vaddr.h 를 봐라 

 

두 번째 방법은 KERN_BASE 아래를 가리키는 사용자 포인터만 체크하는 하여 역참조 하는 것이다.

유효하지 않은 사용자 포인터는 page fault를 야기한다. 이것을 userprog/exception.c의 page_fault() 코드를 수정함으로써 해결 가능하다.

이 기술은 일반적으로 빠르다 왜냐하면 현실적인 kernel에서 MMU가 사용한 는 이점을 택하기 때문이다.

 

위 두 가지 case에서 자원의 leak이 발생하지 않게 해야 한다.

ex) system call 이 lock을 요구 시, 메모리를 malloc()으로 할당할 때

 

만약 우연히 유요 하지 않은 포인터가 위 예시의 처리 이후 들어오면 반드시 lock을 반납/ memory free를 해야 한다 왜냐하면 메모리 접근으로부터 발생한 에러 코드를 되돌릴 수 있는 방법이 없기 때문이다.

 

그렇기 때문에 latter technique를 사용해서 조금이라도 도움이 되길 바란다.

/* Reads a byte at user virtual address UADDR.
 * UADDR must be below KERN_BASE.
 * Returns the byte value if successful, -1 if a segfault
 * occurred. */
static int64_t
get_user (const uint8_t *uaddr) {
    int64_t result;
    __asm __volatile (
    "movabsq $done_get, %0\n"
    "movzbq %1, %0\n"
    "done_get:\n"
    : "=&a" (result) : "m" (*uaddr));
    return result;
}

/* Writes BYTE to user address UDST.
 * UDST must be below KERN_BASE.
 * Returns true if successful, false if a segfault occurred. */
static bool
put_user (uint8_t *udst, uint8_t byte) {
    int64_t error_code;
    __asm __volatile (
    "movabsq $done_put, %0\n"
    "movb %b2, %1\n"
    "done_put:\n"
    : "=&a" (error_code), "=m" (*udst) : "q" (byte));
    return error_code != -1;
}

각 함수는 사용자 주소가 이미 KERN_BASE 아래임을 확인해주는 함수이다. 

그리고 이 함수는 우리가 page_fault()를 수정했다는 가정하에 동작하니 커널 page fault 안 stes rax를 -1로 설정하고 이전 값을 % rip으로 해라

'OS > Pintos P.J_2' 카테고리의 다른 글

[Project 2_User Programs]_System Calls  (0) 2022.01.09
[Project 2_User Programs]_Argument Passing(인수전달)  (0) 2022.01.04