/////
Search
📄

Introduction

Pintos와 그 내부 구조, 그리고 스레드 패키지에 익숙해졌으니, 이번에는 사용자 프로그램 실행을 가능하게 하는 부분을 다룰 차례
기본 코드에는 이미 사용자 프로그램을 로드하고 실행할 수 있는 기능이 있지만, 입출력(I/O)이나 상호작용은 아직 불가능. 이번 프로젝트에서는 시스템 콜을 구현하여 프로그램이 운영체제와 상호작용할 수 있게 할 것
이 과제는 userprog 디렉터리에서 작업하며, Pintos의 거의 모든 부분과도 상호작용하게됨

기본 사항

Project 2는 Project 1 결과물 위에서 빌드
Project 1의 코드가 직접적으로 Project 2의 코드에 영향을 주지는 않지만, Project 1의 테스트를 모두 통과해야함(단계적 프로젝트이므로)

추가 도전 과제(Optional Extra Challenge)

Project 2에는 선택적으로 구현할 수 있는 추가 도전 과제가 존재
해당 부분에는 제공되는 스켈레톤 코드가 없고, 테스트 케이스만 제공 → 따라서 설계와 구현은 전적으로 여러분의 몫
추가 요구사항을 제출하고 테스트하려면 userprog/Make.vars를 수정해야함

코드 수정 가능 범위

코드에 TODO가 없다고 해서 반드시 수정이 필요 없는 것은 아님
Project 2에서는 테스트 코드만 제외하고, 다른 모든 소스 코드를 자유롭게 수정 가능

배경

지금까지 실행한 모든 코드는 운영체제 커널의 일부. 예를 들어, 지난 과제에서 실행한 테스트 코드도 모두 커널 내부에서 실행되었으며, 커널의 모든 권한을 사용했었음
그러나 이번 과제에서는 사용자 프로그램(user program)을 운영체제 위에서 실행. 이때는 상황이 달라짐 :
여러 개의 프로세스를 동시에 실행 가능해야 함
각 프로세스는 하나의 스레드만 가짐 (멀티스레드 프로세스는 지원하지 않음)
사용자 프로그램은 마치 자기 혼자 컴퓨터 전체를 쓰는 것처럼 착각해야 함
따라서 여러 프로세스를 실행할 때 운영체제가 메모리, 스케줄링 등을 관리하여 이런 착각이 유지되도록 해야 함
Project 1에서는 테스트 프로그램이 커널에 직접 포함되어 있었기 때문에, 커널에서 특정 함수 인터페이스를 강제함. 이제부터는 운영체제 위에서 사용자 프로그램을 실행하며 테스트하므로 훨씬 자유도가 높음
제약: 반드시 사용자 프로그램 인터페이스를 명세대로 구현해야함. 그 외에는 커널 코드를 자유롭게 재구조화하거나 재작성 가능
#ifdef VM 안의 코드는 수정하지말 것. 이 코드는 Project 3(가상 메모리 구현)에서 사용 예정
#ifndef VM 안의 코드는 Project 3에서는 사라질 예정

참고 : 추천

이번 프로젝트를 시작하기 전에 동기화(synchronization)가상 주소(virtual address) 개념을 반드시 공부하기

소스 파일

process.c & process.h

ELF 실행 파일을 로드하고 프로세스를 시작

syscall.c & syscall.h

사용자 프로세스가 커널 기능을 쓰고 싶을 때 시스템 콜을 호출
현재는 단순히 메시지를 출력하고 프로세스를 종료하는 형태
이번 프로젝트에서는 실제 시스템 콜 처리를 구현해야 함

syscall-entry.S

시스템 콜 핸들러를 부트스트랩하는 작은 어셈블리 코드(이해할 필요는 없음)

exception.c & exception.h

사용자 프로세스가 잘못된 동작(권한 없는 작업 등)을 하면 예외가 발생하고 커널로 진입
현재는 단순히 메시지를 출력하고 프로세스를 종료하는 형태
일부 해법에서는 page_fault() 수정이 필요

gdt.c & gdt.h

전역 디스크립터 테이블(GDT) 설정(수정 필요 X)

tss.c & tss.h

x86-64에서는 TSS(Task-State Segment)가 태스크 스위칭에 사용되지 않지만, 커널 스택 포인터를 찾을 때 필요(수정 필요 X)

파일 시스템 사용

사용자 프로그램은 파일 시스템에서 로드되므로, 이번 과제에서는 파일 시스템 코드와 인터페이스해야함
하지만 이 과제의 초점은 파일 시스템이 아니므로, filesys 디렉터리에 이미 간단하면서도 완전한 파일 시스템이 제공됨
filesys.h, file.h를 참고
이번 과제에서는 파일 시스템 코드 자체를 수정할 필요 없음
대신, 동기화 처리는 직접 해줘야 함 (동시에 접근하면 꼬일 수 있음)

파일 시스템의 한계

동시성 보장 (직접 락으로 막아야 함)
파일 크기는 생성 시 고정됨
디렉토리 지원 (하위 디렉토리 없음)
파일 이름은 14자 제한
파일은 디스크의 연속된 공간에만 저장 가능 → 외부 단편화 가능성 있음
시스템 크래시 시 자동 복구 불가능

지원하는 기능

파일 삭제의 유닉스 방식 지원 : 열려 있는 파일을 삭제하면, 실제 데이터는 안 지워지고, 열려 있는 모든 프로세스가 닫을 때까지 유지

디스크에 사용자 프로그램 넣기

Project 1과 달리, 사용자 프로그램은 커널에 직접 포함되지 않고 파일 시스템 디스크에 넣어야 실행 가능. 다행히 make check 스크립트가 대부분 자동으로 처리됨

수동으로 넣는 방법 :

1. 디스크 생성

pintos-mkdisk filesys.dsk 2
Shell
복사
2MB짜리 파일 시스템 디스크 생성

2. 디스크 지정

pintos --fs-disk filesys.dsk -- ...
Shell
복사

3. 파일 시스템 포맷

pintos -- -f -q
Shell
복사
format

4. 파일 복사(put/get)

파일 넣기 :
pintos -p file -- -q pintos -p file:newname -- -q
Shell
복사
put
파일 꺼내기 :
pintos -g filename -- -q
Shell
복사
get

예시

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

사용자 프로그램 실행 원리

Pintos는 일반적인 C 프로그램을 실행 가능(조건 : 메모리에 맞고, floating point 사용하지 않아야 함)
ELF 실행 파일을 로드하여 실행 (process.c에 로더 구현됨)
C 프로그램을 x86-64 ELF 형식으로 컴파일하면 실행 가능

가상 메모리 레이아웃

사용자 영역: 0 ~ KERN_BASE
커널 영역: KERN_BASE 이상 (물리 메모리에 1:1 매핑)
사용자 프로세스는 자기 메모리만 접근 가능.
잘못된 접근 → page faultexception.c: page_fault()에서 종료
USER_STACK +----------------------------+ | 사용자 스택 | || | (아래로 성장 downward) | +----------------------------+ | BSS (초기화X) | +----------------------------+ | Data Segment (초기화O) | +----------------------------+ | Code Segment | 0x400000 +----------------------------+ 0 +----------------------------+
Lua
복사
스택은 프로젝트 2에서는 고정 크기
프로젝트 3에서는 스택 확장 지원해야 함

사용자 메모리 접근

시스템 콜 처리 시, 커널은 사용자가 준 포인터를 따라가야 함. 하지만 사용자가 잘못된 포인터(널, unmapped, 커널 영역)를 주면, 커널이 터질 수 있음
따라서 :
포인터 유효성 검사 필요
잘못된 경우 → 해당 프로세스를 종료

두 가지 방법

1.
검증 후 접근
포인터가 유효한지 확인하고 접근
단순 & 구현 쉬움
2.
접근 후 page fault 핸들링
포인터가 KERN_BASE 아래인지 확인만 하고 접근
잘못되면 page fault 발생 → page_fault()에서 처리
실제 운영체제는 이 방식 사용 (빠름)

예시 코드(보호된 접근)

/* Reads a byte at user virtual address UADDR. */ static int64_t get_user (const uint8_t *uaddr); /* Writes BYTE to user address UDST. */ static bool put_user (uint8_t *udst, uint8_t byte);
C
복사