Project 2: System Calls
시스템 호출 (System Calls)
- 두 번째 프로젝트는 PintOS를 다음과 같이 대폭 업그레이드했습니다:
- 자식 프로세스가 파일을 로드하고 실행할 수 있도록 허용.
- 자식 프로세스로 인자 전달 기능 구현.
- 견고한 동기화를 갖춘 시스템 호출 기능 추가.
-
아래에서는 각 구성 요소에 대한 목표, 도전 과제, 해결책 및 구현을 간략하게 설명했습니다.

Part 1: 실행 파일 로딩 (Executable Loading)
목표
- 이 섹션의 주요 목표는
process_wait()함수를 재구현하여 부모 프로세스가 자식 프로세스와 안정적으로 동기화할 수 있도록 하는 것입니다.- 특히, 부모는 자식이 지정된 파일을 성공적으로 로드하고 실행할 때까지, 또는 자식이 종료될 때까지 기다릴 수 있어야 합니다.
현재 문제
- PintOS의 원래
process_wait()함수는 본질적으로 기능이 없었으며, 항상-1을 반환하는 단순한 자리 표시자(placeholder) 역할만 했습니다. -
이러한 구현의 완전한 부재는 부모 프로세스가 자식과 동기화할 수 있는 메커니즘이 전혀 없었음을 의미했습니다.
Click to see the original code
int process_wait (tid_t child_tid UNUSED) { return -1; }
원래 점수

해결책
- 이러한 한계를 극복하기 위해,
process_wait()함수는 다음과 같이 포괄적으로 재설계되었습니다:- 부모-자식 계층 구조 유지:
struct thread내의 연결 리스트 구조가 사용되어 각 부모에 대한 자식 프로세스의 명확한 계층 구조를 유지함으로써, 부모가 자신의 직계 자손을 추적할 수 있도록 합니다.
- 부모 대기 가능:
- 이제 부모 프로세스는 지정된 자식 프로세스가 실행을 완료할 때까지 실행을 효과적으로 일시 중단할 수 있습니다.
- 동기화를 위한 세마포어 활용:
- 세마포어가 핵심 동기화 기본 요소로 사용됩니다.
- 이는 부모가 자식 프로세스가 종료된 후 또는 자식이 로딩 상태를 보고한 후에만 안정적으로 알림을 받고 실행을 재개할 수 있도록 보장합니다.

- 부모-자식 계층 구조 유지:
구현 세부 사항
- 다음 구성 요소들이 수정되거나 도입되었습니다:
thread.hstruct thread- 자식 프로세스 관리 및 동기화를 지원하기 위해
struct thread에 새로운 멤버들이 추가되었습니다:struct list children: 이 스레드에 의해 생성된 모든 자식 스레드의struct list_elem childelem을 담는 리스트입니다.struct list_elem childelem: 자식 스레드를 해당 부모의children리스트에 연결하는 데 사용되는 리스트 요소입니다.struct semaphore sema_parent: 부모가 자식의 종료 상태를 기다리는 데 사용되는 세마포어입니다.struct semaphore sema_child: 자식이 부모에게 자신의 종료를 알리는 데 사용되는 세마포어입니다.int exit_status: 자식 프로세스의 종료 상태를 저장하는 정수입니다.
- 이 새로운 멤버들은 이전 파트에서
donors가 처리된 것과 유사하게init_thread()함수 내에서 적절하게 초기화됩니다.
Click to see the refined code
struct thread { /* Owned by thread.c. */ tid_t tid; /* Thread identifier. */ enum thread_status status; /* Thread state. */ char name[16]; /* Name (for debugging purposes). */ uint8_t *stack; /* Saved stack pointer. */ int priority; /* Priority. */ struct list_elem allelem; /* List element for all threads list. */ /* Shared between thread.c and synch.c. */ struct list_elem elem; /* List element. */ int64_t tick_wakeup; /* Tick till wake up */ struct lock *lock_to_wait; /* Address of the lock to wait */ int original_priority; /* Original Priority value */ struct list donors; /* Doners for priority inversion */ struct list_elem donelem; /* List element for donors list */ #ifdef USERPROG /* Owned by userprog/process.c. */ uint32_t *pagedir; /* Page directory. */ struct list children; /* List of child processes */ struct list_elem childelem; /* List element for child processes */ struct semaphore sema_parent; /* Semaphore for waiting parent process*/ struct semaphore sema_child; /* Semaphore for waiting child process*/ int exit_status; /* Exit status for exit() */ #endif /* Owned by thread.c. */ unsigned magic; /* Detects stack overflow. */ };- 자식 프로세스 관리 및 동기화를 지원하기 위해
thread.cinit_thread()- 이 함수는 자식 프로세스 추적 및 동기화를 위해 새로 추가된 데이터 멤버들, 특히
children,sema_parent,sema_child, 그리고exit_status를 초기화하도록 업데이트되었습니다.Click to see the refined code
static void init_thread (struct thread *t, const char *name, int priority) { // other codes list_init(&t->donors); #ifdef USERPROG list_init(&t->children); sema_init(&t->sema_parent, 0); sema_init(&t->sema_child, 0); t->exit_status = -1; #endif old_level = intr_disable (); list_push_back (&all_list, &t->allelem); intr_set_level (old_level); }
- 이 함수는 자식 프로세스 추적 및 동기화를 위해 새로 추가된 데이터 멤버들, 특히
thread_create()- 새 스레드(자식 프로세스)가 생성될 때, 해당 스레드는 이제 현재 스레드(부모)의
children리스트에 추가되어 부모-자식 관계가 설정됩니다.Click to see the refined code
tid_t thread_create (const char *name, int priority, thread_func *function, void *aux) { struct thread *t; // other codes sf->ebp = 0; #ifdef USERPROG /* push child process to parent */ list_push_back(&thread_current()->children, &t->childelem); #endif // other codes }
- 새 스레드(자식 프로세스)가 생성될 때, 해당 스레드는 이제 현재 스레드(부모)의
thread_exit()- 스레드가 종료되기 전에,
sema_up(&thread_current()->sema_child)를 사용하여 자신의 부모에게 신호를 보내, 자신이 종료되었음을 알립니다.- 그런 다음, 부모가 자신의 종료 상태를 완전히 회수할 때까지
sema_parent에서 대기하여 리소스를 완전히 파괴하기 전에 부모의 회수 완료를 보장합니다.
Click to see the refined code
void thread_exit (void) { ASSERT (!intr_context ()); #ifdef USERPROG sema_up(&thread_current()->sema_child); process_exit (); sema_down(&thread_current()->sema_parent); #endif /* Remove thread from all threads list, set our status to dying, and schedule another process. That process will destroy us when it calls thread_schedule_tail(). */ intr_disable (); list_remove (&thread_current()->allelem); thread_current ()->status = THREAD_DYING; schedule (); NOT_REACHED (); } - 그런 다음, 부모가 자신의 종료 상태를 완전히 회수할 때까지
- 스레드가 종료되기 전에,
process.cprocess_wait()- 이 함수는 부모 프로세스 대기를 담당합니다.
- 현재 스레드(부모)의
children리스트를 반복하여 일치하는child_tid를 가진 자식을 찾습니다. - 일단 찾으면,
- 자식은 리스트에서 제거되고, 부모는
sema_down(&child_thread->sema_child)를 호출하여 자식이 종료를 알릴 때까지 효과적으로 블록됩니다.
- 자식은 리스트에서 제거되고, 부모는
- 자식이 종료된 후,
- 부모는 자식의
exit_status를 회수하고sema_up(&child_thread->sema_parent)를 통해 자식(이제THREAD_DYING상태)에게 신호를 보내 자식의 리소스가 완전히 할당 해제될 수 있도록 합니다.
- 부모는 자식의
- 만약
child_tid가 직계 자식에 해당하지 않거나 이미 해당 자식에 대해 대기(wait)가 완료된 경우,- 단순히
-1을 반환합니다.
- 단순히
Click to see the refined code
int process_wait (tid_t child_tid) { struct thread *child_thread = NULL, *current_thread = thread_current(); for(struct list_elem *current_element = list_begin(¤t_thread->children), *end_element = list_end(¤t_thread->children); current_element != end_element; current_element = list_next(current_element)) { struct thread *thread_current_element = list_entry(current_element, struct thread, childelem); if (thread_current_element->tid == child_tid) { child_thread = thread_current_element; list_remove(current_element); break; } } if(child_thread == NULL) return -1; sema_down(&child_thread->sema_child); int exit_status = child_thread->exit_status; sema_up(&child_thread->sema_parent); return exit_status; }
결론
- 이러한 수정으로 부모 프로세스는 이제 자식의 종료를 안정적으로 대기하고 종료 상태를 회수할 수 있게 되어, 중요한 동기화 메커니즘이 확립되었습니다.
- 하지만, 자식이 실제로 실행 파일을 로드하고 실행하는 능력과 인자 전달 기능은 프로젝트의 이후 파트에서 다루어질 예정입니다.
Part 2: 인자 전달 (Argument Passing)
목표
- 이 섹션의 목표는
start_process()함수를 향상시켜, 부모 프로세스로부터 새로 로드된 자식 실행 파일로 명령줄 인자를 올바르게 파싱하고 전달할 수 있도록 하는 것입니다. - 이를 통해 사용자 프로그램이 일반적인 운영체제의 명령줄 애플리케이션처럼 인자를 받고 해석할 수 있게 됩니다.
현재 문제
- 원래의
start_process()함수는 근본적인 한계점을 가지고 설계되었습니다:file_name인자가 오직 실행 파일의 이름만 포함한다고 가정했습니다.
-
사용자(예:
myprog arg1 arg2)가 제공한 추가 인자들은 완전히 무시되어, 사용자 프로그램이 명령줄 입력을 받고 처리하는 것을 불가능하게 만들었습니다.Click to see the original code
static void start_process (void *file_name_) { char *file_name = file_name_; struct intr_frame if_; bool success; /* Initialize interrupt frame and load executable. */ memset (&if_, 0, sizeof if_); if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG; if_.cs = SEL_UCSEG; if_.eflags = FLAG_IF | FLAG_MBS; success = load (file_name, &if_.eip, &if_.esp); /* If load failed, quit. */ palloc_free_page (file_name); if (!success) thread_exit (); /* Start the user process by simulating a return from an interrupt, implemented by intr_exit (in threads/intr-stubs.S). Because intr_exit takes all of its arguments on the stack in the form of a `struct intr_frame', we just point the stack pointer (%esp) to our stack frame and jump to it. */ asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (&if_) : "memory"); NOT_REACHED (); }
해결책
- 견고한 인자 전달을 가능하게 하기 위해,
start_process()함수는 다음과 같은 핵심 단계들을 수행하도록 재구현되었습니다:- 인자 파싱 (Argument Parsing):
- 실행 파일 이름과 해당 인자들을 포함하는 입력 문자열을 파싱하여 개별 인자들을 추출합니다.
- 사용자 스택 조작 (User Stack Manipulation):
- 파싱된 인자들은 새로 생성된 프로세스의 사용자 스택에 신중하게 푸시됩니다.
- 이는 명령줄 인자에 대한 특정 스택 레이아웃을 규정하는 80x86 호출 규약(Calling Convention)을 준수합니다.
- 이 규약은 인자들($argv[i]$)을 오른쪽에서 왼쪽으로 푸시하고, 그 뒤에 인자들의 시작 주소, 인자의 개수($argc$), 그리고 반환 주소(일반적으로 $0$)를 푸시하도록 요구합니다.
- 스택 정렬 및 패딩 (Stack Alignment and Padding):
- 스택 상의 인자들이 4바이트 경계에 적절하게 정렬되도록 보장하는 것이 중요합니다.
- 이는 x86 아키텍처에서 시스템 안정성과 성능에 필수적인 올바른 메모리 정렬을 유지하기 위해 종종 패딩 바이트를 추가하는 것을 포함합니다.

- 인자 파싱 (Argument Parsing):
구현 세부 사항
- 다음 구성 요소들이 수정되거나 도입되었습니다:
syscall.ccollapse_spaces()- 새로운 헬퍼 함수인
collapse_spaces()가 도입되었습니다.- 그 목적은 입력 인자 문자열에서 연속된 여러 개의 공백을 단일 공백으로 대체하여 문자열을 정규화하는 것입니다.
- 이는 일관된 파싱을 보장하고 명령줄의 다양한 간격으로 인해 발생하는 문제를 방지합니다.
Click to see the refined code
void collapse_spaces(char *str) { char *source = str, *destination = str; bool in_space = false; while (*source) { if (*source == ' ') { if (in_space == false) { *destination++ = ' '; in_space = true; } } else { *destination++ = *source; in_space = false; } source++; } *destination = '\0'; }- 새로운 헬퍼 함수인
start_process()- 이 함수는 인자 파싱 및 스택 설정을 처리하기 위해 상당한 수정을 거칩니다.
- 먼저 입력
file_name_에 대해collapse_spaces()를 호출합니다. - 그런 다음
strtok_r을 사용하여 문자열을 토큰화하고, 실행 파일 이름을 인자들과 분리하여 로컬argv배열에 저장합니다.argc는 인자의 개수를 추적합니다. - 원래의
load()함수는 실행 파일 이름(argv[0])과 함께 호출됩니다. - 스택 구성:
load가 성공하면, 함수는 아래(가장 높은 주소)에서 위(가장 낮은 주소)로 인자 스택 프레임을 구성하는 작업을 진행합니다.- 인자 문자열:
argv의 각 인자 문자열은 사용자 스택에 복사됩니다. 스택 포인터(if_.esp)는 이에 따라 감소됩니다. 복사된 각 인자 문자열의 시작 주소는arguments_starting_points에 저장됩니다. - 패딩: 인자 문자열 뒤에 스택 포인터가 4바이트 정렬되도록 패딩 바이트가 추가됩니다. 이는
sum_bits(인자 문자열의 총 길이)를 사용하여(4 - sum_bits % 4) % 4로 계산됩니다. - Null
argv[argc]: 사용자 프로그램을 위한argv배열의 끝을 표시하는 널 포인터(0)가 스택에 푸시됩니다. argv포인터:arguments_starting_points에 저장된 주소(스택 상의 실제 인자 문자열을 가리키는 포인터)들이 역순(즉, $argc - 1$부터 $0$까지)으로 스택에 푸시됩니다.argv기준 포인터: 첫 번째argv포인터(arguments_starting_points[0])의 주소가 푸시되며, 이는main함수로 전달될char** argv인자가 됩니다.argc: 인자의 개수가 푸시됩니다.- 가짜 반환 주소: 가짜 반환 주소로
0이 푸시됩니다.
- 인자 문자열:
- 마지막으로, 어셈블리 명령어가 사용되어
intr_exit스텁으로 점프하며, 이는 구성된struct intr_frame(수정된if_.esp포함)을 사용하여 사용자 프로세스의 초기 스택과 레지스터를 설정하게 됩니다.Click to see the refined code
static void start_process (void *file_name_) { /* Parse the first argument */ collapse_spaces(file_name_); const int MAX_COUNT_ARGUMENT = 32; const int MAX_LENGTH_ARGUMENT = 64; char argv[MAX_COUNT_ARGUMENT][MAX_LENGTH_ARGUMENT]; int argc = 0; char *saveptr, *token = strtok_r(file_name_, " ", &saveptr); while (token != NULL && argc < MAX_COUNT_ARGUMENT) { strlcpy(argv[argc], token, MAX_LENGTH_ARGUMENT); ++argc; token = strtok_r(NULL, " ", &saveptr); } char *file_name = argv[0]; /* Initialize interrupt frame and load executable. */ struct intr_frame if_; bool success; struct thread *current_thread = thread_current(); memset (&if_, 0, sizeof if_); if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG; if_.cs = SEL_UCSEG; if_.eflags = FLAG_IF | FLAG_MBS; success = load (file_name, &if_.eip, &if_.esp); /* If load failed, quit. it should be file_name_ not file_name, because file_name_ points to the allocated page */ palloc_free_page (file_name_); if (!success) thread_exit (); /* Push arguments to interrupt frame with the 80x86 calling convention. The registers in interrupt frame will be restored to user stack of the loaded executable */ int sum_bits = 0; int64_t arguments_starting_points[MAX_COUNT_ARGUMENT]; for(int length, current_count = 0; current_count < argc; ++current_count) { length = strlen(argv[current_count]) + 1; if_.esp -= length; // decrement is needed because stack should be moved downward memcpy(if_.esp, argv[current_count], length); arguments_starting_points[current_count] = if_.esp; sum_bits += length; } uint8_t padding_value = (4 - sum_bits % 4) % 4; if(padding_value != 0) { if_.esp -= padding_value; memset(if_.esp, 0, padding_value); // use memset instead of memcpy } if_.esp -= sizeof(char*); memset(if_.esp, 0, sizeof(char*)); for(int current_count = argc - 1; current_count > -1; --current_count) { if_.esp -= sizeof(char*); memcpy(if_.esp, &arguments_starting_points[current_count], sizeof(char*)); // store the actual physical memory address, not the address of local variable } int64_t start_point = if_.esp; if_.esp -= sizeof(char**); memcpy(if_.esp, &start_point, sizeof(char**)); if_.esp -= sizeof(int); memcpy(if_.esp, &argc, sizeof(int)); if_.esp -= sizeof(char*); memset(if_.esp, 0, sizeof(char*)); /* Start the user process by simulating a return from an interrupt, implemented by intr_exit (in threads/intr-stubs.S). Because intr_exit takes all of its arguments on the stack in the form of a `struct intr_frame', we just point the stack pointer (%esp) to our stack frame and jump to it. */ asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (&if_) : "memory"); NOT_REACHED (); }
결론
start_process()에 대한 이러한 포괄적인 수정은 자식 프로세스가 명령줄 인자를 성공적으로 수신하고 처리할 수 있도록 하여, 그 기능과 상호작용 능력을 크게 향상시킵니다.- 이로써 인자 전달 문제는 해결되었지만, 이 인자들의 완전한 유용성, 특히 인자를 기반으로 시스템 호출을 실행하는 것은 여전히 견고한 시스템 호출 핸들러의 구현에 달려 있습니다 (Part 4에서 다룰 예정).
Part 3: 동기화 문제 (Synchronization Issues)
목표
- 포괄적인 시스템 호출 기능 구현을 진행하기 전에, 동시 파일 작업 중에 발생할 수 있는 잠재적인 경쟁 조건(race condition)을 해결하는 것이 중요합니다.
- 여기서의 주요 목표는 견고한 동기화 메커니즘을 도입하여 파일 시스템의 무결성과 일관성을 보장하는 것입니다.
- 이는 전역 파일 시스템 락(
filesys_lock)을 구현하고, 실행 파일에 대한 무단 또는 충돌하는 수정을 방지하기 위해file_deny_write()를 전략적으로 활용하는 것을 포함합니다.
- 이는 전역 파일 시스템 락(
현재 문제
- 기존 PintOS 커널은 치명적인 취약점을 안고 있었습니다:
- 공유 파일 시스템 리소스를 보호하기 위한 전용
filesys_lock이 부족했습니다.
- 공유 파일 시스템 리소스를 보호하기 위한 전용
- 또한, 실행 파일을 로드하는 역할을 하는
load()함수는 파일이 로드된 후file_deny_write()를 명시적으로 호출하지 않았습니다.- 이러한 간과는 로드된 실행 파일이 사용 중인 상태에서 다른 프로세스에 의해 잠재적으로 수정되거나 삭제될 수 있음을 의미했으며, 이로 인해 동시 파일 접근으로 인한 예측 불가능한 동작, 충돌 또는 보안 취약점이 발생할 수 있었습니다.
Click to see the original code
bool load (const char *file_name, void (**eip) (void), void **esp) { struct thread *t = thread_current (); // other codes done: /* We arrive here whether the load is successful or not. */ file_close (file); return success; }
해결책
- 이러한 중요한 동기화 문제를 해결하기 위해 다음과 같은 해결책들이 구현되었습니다:
filesys_lock도입:- 전역 뮤텍스인
filesys_lock이 도입되었습니다. - 파일 시스템에 접근하거나 수정하는 모든 작업은 이제 이 락으로 보호되어 상호 배제를 보장하고 경쟁 조건을 방지합니다.
- 전역 뮤텍스인
load()함수 수정:load()함수는 다음과 같이 업데이트되었습니다:- 실행 파일을 열고 읽기 전에
filesys_lock을 획득합니다. - 실행 파일을 성공적으로 로드한 직후
file_deny_write()를 호출합니다.- 이는 다른 프로세스가 현재 프로세스에서 사용 중인 파일에 쓰기 작업을 하는 것을 방지합니다.
- 로드된 실행 파일에 대한
file_close()호출이 지연되었습니다.- 로딩 직후 파일을 닫는 대신, 로드된 실행 파일을 나타내는
struct file객체는 이제 스레드 구조체(current_running_file)에 저장되며, 프로세스가 종료될 때(process_exit())만 명시적으로 닫힙니다. - 이는 deny-write 상태가 실행 파일을 사용하는 프로세스의 전체 수명 동안 지속되도록 보장합니다.
- 로딩 직후 파일을 닫는 대신, 로드된 실행 파일을 나타내는
- 로딩 과정 후에는 성공 여부와 관계없이
filesys_lock을 해제합니다.
- 실행 파일을 열고 읽기 전에
process_execute()강화:process_execute()는 새 프로세스를 생성하기 전에 이제 지정된file_name의 존재 여부와 접근 가능성을 확인하는 초기 검사를 수행합니다.- 이를 통해 존재하지 않거나 열 수 없는 파일에 대한 스레드 생성을 방지하고, 불필요한 자원 할당을 피하며 조기에
TID_ERROR를 반환합니다.
구현 세부 사항
- 다음 구성 요소들이 수정되거나 도입되었습니다:
syscall.hfilesys_lockstruct lock filesys_lock에 대한 선언이syscall.h에 추가되었습니다.- 이는 파일 시스템과 상호 작용하는
userprog서브시스템의 모든 부분에서 이 락에 접근할 수 있도록 합니다.
- 이는 파일 시스템과 상호 작용하는
- 이 락은
syscall_init()에서 명시적으로 초기화됩니다.
Click to see the refined code
#ifndef USERPROG_SYSCALL_H #define USERPROG_SYSCALL_H void syscall_init (void); struct lock filesys_lock; #endif /* userprog/syscall.h */
syscall.csyscall_init()- 커널 초기 단계에서 호출되는
syscall_init()함수는 이제lock_init(&filesys_lock)호출을 포함하여 파일 시스템 락을 적절하게 초기화합니다.Click to see the refined code
void syscall_init (void) { intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall"); lock_init(&filesys_lock); }
- 커널 초기 단계에서 호출되는
process.cload()- 파일 열기 및 로드 프로세스 동안 파일 시스템을 보호하기 위해
filesys_open()전에lock_acquire(&filesys_lock)이 추가되었습니다. file이 성공적으로 열리고 로드되면,- 실행 파일 수정을 방지하기 위해
file_deny_write(file)이 호출됩니다.
- 실행 파일 수정을 방지하기 위해
done레이블에 있던file_close(file)호출이 제거되었습니다.- 대신, 파일 포인터는 나중에 닫기 위해
t->current_running_file에 저장됩니다.
- 대신, 파일 포인터는 나중에 닫기 위해
- 함수에서 반환하기 전에
lock_release(&filesys_lock)이 호출됩니다.Click to see the refined code
bool load (const char *file_name, void (**eip) (void), void **esp) { // other codes /* Open executable file. */ lock_acquire(&filesys_lock); file = filesys_open (file_name); // other codes done: /* We arrive here whether the load is successful or not. */ if(file != NULL) file_deny_write(file); // call this instead of file_close() lock_release(&filesys_lock); t->current_running_file = file; return success; }
- 파일 열기 및 로드 프로세스 동안 파일 시스템을 보호하기 위해
process_exit()- 프로세스가 종료될 때,
file_close(cur->current_running_file)가 이제 명시적으로 호출됩니다. - 이는 실행 파일에 대한 쓰기 금지(deny-write) 락을 해제하여, 프로세스가 더 이상 사용하지 않을 경우 파일이 수정되거나 삭제될 수 있도록 합니다.
Click to see the refined code
void process_exit (void) { struct thread *cur = thread_current (); uint32_t *pd; file_close(cur->current_running_file); /* Destroy the current process's page directory and switch back to the kernel-only page directory. */ pd = cur->pagedir; if (pd != NULL) { /* Correct ordering here is crucial. We must set cur->pagedir to NULL before switching page directories, so that a timer interrupt can't switch back to the process page directory. We must activate the base page directory before destroying the process's page directory, or our active page directory will be one that's been freed (and cleared). */ cur->pagedir = NULL; pagedir_activate (NULL); pagedir_destroy (pd); } }
- 프로세스가 종료될 때,
process_execute()- 새 스레드를 생성하기 전에,
process_execute()는 이제filesys_lock을 획득하고 토큰(파싱된 프로그램 이름)으로 지정된 실행 파일에 대해filesys_open()을 시도합니다. - 만약
filesys_open()이NULL을 반환하면(파일이 존재하지 않거나 열 수 없음을 나타냄), 락이 해제되고, 할당된 페이지가 해제되며, 즉시TID_ERROR가 반환됩니다.- 이는 유효하지 않은 프로세스의 생성을 방지합니다.
- 열었던
current_file은 단순히 존재 여부를 확인하는 것이 목적이었으므로, 이 검사 직후 즉시 닫힙니다.Click to see the refined code
tid_t process_execute (const char *file_name) { char *fn_copy; tid_t tid; /* Make a copy of FILE_NAME. Otherwise there's a race between the caller and load(). */ fn_copy = palloc_get_page (0); if (fn_copy == NULL) return TID_ERROR; strlcpy (fn_copy, file_name, PGSIZE); /* Create a new thread to execute FILE_NAME. */ const unsigned MAX_PROCESS_NAME_COUNT = 32; char new_process_name[MAX_PROCESS_NAME_COUNT]; strlcpy(new_process_name, file_name, MAX_PROCESS_NAME_COUNT); char *saveptr, *token = strtok_r(new_process_name, " ", &saveptr); // parse the first argument only to make it as name of the thread lock_acquire(&filesys_lock); struct file* current_file = filesys_open (token); if (current_file == NULL) { lock_release(&filesys_lock); palloc_free_page (fn_copy); return TID_ERROR; } file_close(current_file); lock_release(&filesys_lock); tid = thread_create (token, PRI_DEFAULT, start_process, fn_copy); if (tid == TID_ERROR) palloc_free_page (fn_copy); return tid; }
- 새 스레드를 생성하기 전에,
결론
- 이러한 중요한 동기화 강화는 파일 작업이 안전하고 일관되게 수행되도록 보장하여, 경쟁 조건을 방지하고 파일 시스템의 무결성을 유지합니다.
- 공유 리소스를
filesys_lock으로 보호하고 실행 파일 쓰기 작업을file_deny_write()로 관리함으로써, PintOS는 이제 동시 접근 문제에 대해 훨씬 더 견고해졌습니다. - 이러한 토대는 다음 파트의 핵심이 될 시스템 호출의 안정적인 구현을 위해 필수적입니다.
Part 4: 시스템 호출 구현 (System Call Implementation)
목표
- 이 섹션의 주요 목표는
syscall_handler()함수를 근본적으로 개선하여 광범위한 시스템 호출에 대한 포괄적인 지원을 제공하는 것입니다. - 이러한 시스템 호출은 사용자 프로세스가 커널과 상호 작용하는 데 필수적이며, 프로세스 생성, 종료, 다양한 파일 관리 작업과 같은 기능을 가능하게 합니다.
현재 문제
- PintOS의 원래
syscall_handler()는 극도로 초보적이었습니다.- 단지
"system call!"이라는 디버깅 메시지를 출력한 다음 호출한 스레드를 즉시 종료시켰습니다. - 이 자리 표시자 구현은 사용자 프로그램이 기본 실행 및 종료 이상의 의미 있는 운영체제 상호 작용을 수행할 수 없도록 만들었습니다.
- 단지
-
시스템 호출 번호나 인자를 해석하는 메커니즘은 물론, 특정 처리 루틴으로 디스패치(dispatch)하는 기능조차 없었습니다.
Click to see the original code
static void syscall_handler (struct intr_frame *f UNUSED) { printf ("system call!\n"); thread_exit (); }
해결책
- PintOS를 더욱 기능적인 운영체제로 변모시키기 위해
syscall_handler()가 완전히 재구현되었습니다. - 수정된 접근 방식은 다음을 포함합니다:
- 인수 유효성 검사 및 구문 분석 (Argument Validation and Parsing):
- 핸들러는 이제 사용자 스택(
f->esp)의 주소를 엄격하게 검증하여 해당 주소가 사용자 주소 공간 내에 있고 매핑되었는지 확인합니다. - 그런 다음 인터럽트 프레임의 스택에서 시스템 호출 번호와 해당 인수를 직접 구문 분석합니다:
esp[0]은 요청된 커널 서비스에 대한 고유 식별자 역할을 하는 시스템 호출 번호로 해석됩니다.- 이후의 스택 위치(
esp[1],esp[2],esp[3]등)는 특정 시스템 호출에 대한 인수로 처리됩니다.
- 핸들러는 이제 사용자 스택(
- 시스템 호출 디스패칭 (System Call Dispatching):
- 추출된 시스템 호출 번호를 기반으로, 핸들러는 제어를 적절하고 전용인 시스템 호출 함수로 분배합니다.
- 예:
halt(),exit(),read(),write(),open()등
- 반환 값 처리 (Return Value Handling):
- 시스템 호출 함수의 반환 값은 인터럽트 프레임의
eax레지스터(f->eax)에 신중하게 저장되어, 사용자 프로세스가 커널 요청 결과를 검색할 수 있도록 합니다.
- 시스템 호출 함수의 반환 값은 인터럽트 프레임의
- 종료 시 포괄적인 파일 닫기 (Comprehensive File Closure on Exit):
- 프로세스가 종료될 때 해당 프로세스에 속한 모든 열린 파일 디스크립터가 올바르게 닫히도록
process_exit()에도 중요한 수정이 이루어졌습니다. - 이는 리소스 누수를 방지하고 파일 시스템 무결성을 유지합니다.
- 프로세스가 종료될 때 해당 프로세스에 속한 모든 열린 파일 디스크립터가 올바르게 닫히도록

- 인수 유효성 검사 및 구문 분석 (Argument Validation and Parsing):
구현 세부 사항
- 다음과 같은 구성 요소들이 수정되거나 도입되었습니다:
thread.hFD_TABLE_MAX_SLOT매크로가32의 값으로 정의되었습니다.- 이는 단일 프로세스가 동시에 열 수 있는 최대 파일 디스크립터 수를 설정합니다.
struct thread- 파일 디스크립터 테이블을 관리하기 위해 새로운 멤버인
fd_table과next_fd가 추가되었습니다.struct file* fd_table[FD_TABLE_MAX_SLOT]:- 이 배열은 각 스레드(프로세스)의 파일 디스크립터 테이블 역할을 하며, 정수형 파일 디스크립터를 해당 커널 파일 객체에 매핑합니다.
int next_fd:- 주어진 스레드에 대해 다음에 할당 가능한 (가장 낮은 번호의) 파일 디스크립터를 추적하는 정수형 변수로, 새로운 FD 할당을 최적화합니다.
- 이 새로운 멤버들은
init_thread()내에서 적절하게 초기화됩니다.
Click to see the refined code
#define FD_TABLE_MAX_SLOT 32 struct thread { // other codes struct file* fd_table[FD_TABLE_MAX_SLOT]; /* File Descriptor table*/ int next_fd; /* Empty fd value for next file object */ // other codes };- 파일 디스크립터 테이블을 관리하기 위해 새로운 멤버인
thread.cinit_thread()init_thread()함수가 새로운 파일 디스크립터 관련 필드를 초기화하도록 업데이트되었습니다.- 구체적으로,
t->next_fd는 3으로 설정되고 (표준 입력, 출력, 오류를 위해 0, 1, 2를 예약),t->fd_table의 모든 항목은NULL로 초기화됩니다.
Click to see the refined code
static void init_thread (struct thread *t, const char *name, int priority) { // other codes list_init(&t->donors); #ifdef USERPROG list_init(&t->children); sema_init(&t->sema_parent, 0); sema_init(&t->sema_child, 0); t->exit_status = -1; t->next_fd = 3; for(unsigned current_index = 0; current_index < FD_TABLE_MAX_SLOT; ++current_index) t->fd_table[current_index] = NULL; t->current_running_file = NULL; #endif old_level = intr_disable (); list_push_back (&all_list, &t->allelem); intr_set_level (old_level); }- 구체적으로,
syscall.cis_valid_address()- 새로운 헬퍼 함수
is_valid_address()가 구현되었습니다. - 이 함수는 다음 두 가지 조건을 확인하여 핵심적인 유효성 검사를 수행합니다:
- 주어진 포인터
ptr이 사용자 가상 주소 범위 내에 있는지 확인합니다 (is_user_vaddr()사용).is_user_vaddr()를 호출하기 위해서는threads/vaddr.h를 포함해야 한다는 점에 주목할 필요가 있습니다.
- 가상 주소
ptr이 현재 프로세스의 페이지 디렉터리에서 실제로 물리 페이지에 매핑되어 있는지 확인합니다 (pagedir_get_page()사용).
- 주어진 포인터
- 이 함수는 사용자 프로그램에 의한 세그먼테이션 오류 및 기타 메모리 접근 위반을 방지하는 데 결정적인 역할을 합니다.
Click to see the refined code
#include "threads/vaddr.h" // other codes bool is_valid_address(void *ptr) { if(ptr != NULL && is_user_vaddr(ptr) == true) { if(pagedir_get_page(thread_current()->pagedir, ptr) != NULL) return true; } return false; }
- 새로운 헬퍼 함수
syscall_handler()- 이 함수는 모든 시스템 호출(system calls)을 위한 중앙 디스패처(central dispatcher)입니다.
- 먼저, 유효하지 않은 메모리 접근을 방지하기 위해
f->esp(스택 포인터)의 유효성을 검사합니다. - 이후,
switch문은current_esp[0](시스템 호출 번호)의 값에 따라 적절한 시스템 호출 핸들러로 디스패치합니다. - 시스템 호출을 위한 인자들은
current_esp[1],current_esp[2],current_esp[3]등에서 직접 검색됩니다. - 시스템 호출 함수의 반환 값은
f->eax에 할당되며, 이는 인터럽트에서 복귀할 때 사용자 프로그램에 의해 확인될 수 있습니다.Click to see the refined code
static void syscall_handler(struct intr_frame* f) { int *current_esp = f->esp; if(is_valid_address(current_esp) == false) return; switch (current_esp[0]) { case SYS_HALT: halt(); break; case SYS_EXIT: exit(current_esp[1]); break; case SYS_EXEC: f->eax = exec(current_esp[1]); break; case SYS_WAIT: f->eax = wait(current_esp[1]); break; case SYS_CREATE: f->eax = create(current_esp[1], current_esp[2]); break; case SYS_REMOVE: f->eax = remove(current_esp[1]); break; case SYS_OPEN: f->eax = open(current_esp[1]); break; case SYS_FILESIZE: f->eax = filesize(current_esp[1]); break; case SYS_READ: f->eax = read(current_esp[1], current_esp[2], current_esp[3]); break; case SYS_WRITE: f->eax = write(current_esp[1], current_esp[2], current_esp[3]); break; case SYS_SEEK: seek(current_esp[1], current_esp[2]); break; case SYS_TELL: f->eax = tell(current_esp[1]); break; case SYS_CLOSE: close(current_esp[1]); break; } }
- System Call Handlers
halt()- PintOS를 종료합니다.
Click to see the refined code
void halt(void) { shutdown_power_off(); }
- PintOS를 종료합니다.
exit()- 사용자 프로그램을
exit_status와 함께 종료합니다.Click to see the refined code
void exit(int status) { if(status < 0) status = -1; struct thread *current_thread = thread_current(); printf("%s: exit(%d)\n", current_thread->name, status); current_thread->exit_status = status; thread_exit(); }
- 사용자 프로그램을
exec()- 인자와 함께 새로운 실행 파일(executable)을 실행합니다.
Click to see the refined code
int exec(const char *cmd_line) { if(is_valid_address(cmd_line) == false) return -1; const unsigned MAX_PROCESS_NAME_COUNT = 32; char new_process_name[MAX_PROCESS_NAME_COUNT]; strlcpy(new_process_name, cmd_line, MAX_PROCESS_NAME_COUNT); char *saveptr, *token = strtok_r(new_process_name, " ", &saveptr); lock_acquire(&filesys_lock); struct file *temp_file = file_open(token); if(temp_file == NULL) { lock_release(&filesys_lock); return -1; } file_close(temp_file); lock_release(&filesys_lock); return process_execute(cmd_line); }
- 인자와 함께 새로운 실행 파일(executable)을 실행합니다.
wait()- 자식 프로세스를 기다리고 해당 프로세스의
exit_status를 검색합니다.Click to see the refined code
int wait(int pid) { return process_wait(pid); }
- 자식 프로세스를 기다리고 해당 프로세스의
create()- 지정된 크기의 파일을 생성합니다.
- 성공 시
true를, 그렇지 않으면false를 반환합니다.
Click to see the refined code
bool create(const char *file, unsigned initial_size) { if(is_valid_address(file) == false) exit(-1); lock_acquire(&filesys_lock); int return_value = filesys_create(file, initial_size); lock_release(&filesys_lock); return return_value; } - 성공 시
- 지정된 크기의 파일을 생성합니다.
remove()- 파일을 삭제합니다.
- 성공 시
true를, 그렇지 않으면false를 반환합니다.
Click to see the refined code
bool remove(const char *file) { lock_acquire(&filesys_lock); int return_value = filesys_remove(file); lock_release(&filesys_lock); return return_value; } - 성공 시
- 파일을 삭제합니다.
open()- 파일을 엽니다(Opens).
- 성공 시
current_fd를 반환하며, 그렇지 않으면-1을 반환합니다.
Click to see the refined code
int open(const char *file) { if(is_valid_address(file) == false) exit(-1); struct thread *current_thread = thread_current(); if(current_thread->next_fd == -1) return -1; lock_acquire(&filesys_lock); struct file *current_file = filesys_open(file); if(current_file == NULL) { lock_release(&filesys_lock); return -1; } lock_release(&filesys_lock); current_thread->fd_table[current_thread->next_fd] = current_file; int current_fd = current_thread->next_fd; while(true) { if(current_thread->fd_table[current_thread->next_fd] == NULL) break; ++current_thread->next_fd; if(current_thread->next_fd == FD_TABLE_MAX_SLOT) { current_thread->next_fd = -1; break; } } return current_fd; } - 성공 시
- 파일을 엽니다(Opens).
filesize()- 파일 디스크립터
fd로 열린 파일의 크기(size)를 바이트 단위로 반환합니다.Click to see the refined code
int filesize(int fd) { if(fd < 0 || fd >= FD_TABLE_MAX_SLOT || thread_current()->fd_table[fd] == NULL) exit(-1); lock_acquire(&filesys_lock); int return_value = file_length(thread_current()->fd_table[fd]); lock_release(&filesys_lock); return return_value; }
- 파일 디스크립터
read()- 파일 디스크립터
fd로 열린 파일에서size바이트만큼을buffer로 읽습니다(Reads). - 실제로 읽은 바이트 수(파일의 끝에서는 0)를 반환하며, 파일을 읽을 수 없는 경우(파일의 끝 이외의 조건으로 인한 경우) -1을 반환합니다.
fd 0은input_getc()를 사용하여 키보드로부터 읽습니다.Click to see the refined code
int read(int fd, void *buffer, unsigned size) { if(fd < 0 || fd >= FD_TABLE_MAX_SLOT || thread_current()->fd_table[fd] == NULL || is_valid_address(buffer) == false || size < 0) exit(-1); if(fd == STDIN_FILENO) return input_getc(); else { lock_acquire(&filesys_lock); int return_value = file_read(thread_current()->fd_table[fd], buffer, size); lock_release(&filesys_lock); return return_value; } }
- 파일 디스크립터
write()- 파일 디스크립터
fd로 열린 파일에buffer로부터size바이트만큼을 씁니다(Writes).- 실제로 쓰인 바이트 수를 반환하며, 일부 바이트를 쓸 수 없었던 경우
size보다 작을 수 있습니다.
Click to see the refined code
int write(int fd, const void *buffer, unsigned size) { if(is_valid_address(buffer) == false || size < 0) exit(-1); if(fd == STDOUT_FILENO) putbuf(buffer, size); else { if(fd < 0 || fd >= FD_TABLE_MAX_SLOT || thread_current()->fd_table[fd] == NULL) exit(-1); lock_acquire(&filesys_lock); int return_value = file_write(thread_current()->fd_table[fd], buffer, size); lock_release(&filesys_lock); return return_value; } return size; } - 실제로 쓰인 바이트 수를 반환하며, 일부 바이트를 쓸 수 없었던 경우
- 파일 디스크립터
seek()- 열린 파일
fd에서 다음에 읽거나 쓸 바이트를 파일의 시작부터position바이트로 표현된 위치로 변경합니다.Click to see the refined code
void seek(int fd, unsigned poisiton) { if(fd < 0 || fd >= FD_TABLE_MAX_SLOT || thread_current()->fd_table[fd] == NULL) exit(-1); lock_acquire(&filesys_lock); file_seek(thread_current()->fd_table[fd], poisiton); lock_release(&filesys_lock); }
- 열린 파일
tell()- 열린 파일
fd에서 다음에 읽거나 쓸 바이트의 위치를 파일의 시작부터 바이트로 표현하여 반환합니다.Click to see the refined code
unsigned tell(int fd) { if(fd < 0 || fd >= FD_TABLE_MAX_SLOT || thread_current()->fd_table[fd] == NULL) exit(-1); lock_acquire(&filesys_lock); int return_value = file_tell(thread_current()->fd_table[fd]); lock_release(&filesys_lock); return return_value; }
- 열린 파일
close()- 파일 디스크립터
fd를 닫습니다(Closes).Click to see the refined code
void close(int fd) { struct thread *current_thread = thread_current(); if(fd < 0 || fd >= FD_TABLE_MAX_SLOT || current_thread->fd_table[fd] == NULL) exit(-1); lock_acquire(&filesys_lock); file_close(current_thread->fd_table[fd]); lock_release(&filesys_lock); current_thread->fd_table[fd] = NULL; current_thread->next_fd = fd; }
- 파일 디스크립터
process.cprocess_execute()oom(메모리 부족) 테스트에 대해 정확한 동작을 보장하기 위해,process_execute()는 이제 자식 프로세스가load()작업을 완료할 때까지 기다립니다.- 자식이 자신의 로딩 상태를 알릴 때까지 차단하기 위해
sema_down(&child_thread->sema_child)를 사용합니다. - 자식의 로딩 성공 또는 실패는
child_thread->exit_status를 통해 전달되며, 이를 통해 자식이 로드에 실패하면process_execute()가TID_ERROR를 반환할 수 있습니다.Click to see the refined code
tid_t process_execute (const char *file_name) { // other codes tid = thread_create (token, PRI_DEFAULT, start_process, fn_copy); if (tid == TID_ERROR) palloc_free_page (fn_copy); // respect the result of load() struct thread *child_thread = list_entry(list_back(&thread_current()->children), struct thread, childelem); sema_down(&child_thread->sema_child); if(child_thread->exit_status == TID_ERROR) return TID_ERROR; return tid; }
start_process()- 자식 스레드에 의해 실행되는 이 함수는 이제
load()를 호출한 후current_thread->exit_status에 자신의 로딩 상태 (success ? 0 : TID_ERROR)를 명시적으로 업데이트합니다. - 그런 다음
sema_up(¤t_thread->sema_child)를 호출하여 부모에게 신호를 보내 로딩 결과를 알립니다. - 로드에 실패하면 즉시
thread_exit()를 호출합니다.Click to see the refined code
static void start_process (void *file_name_) { // other codes success = load (file_name, &if_.eip, &if_.esp); /* If load failed, quit. it should be file_name_ not file_name, because file_name_ points to the allocated page */ palloc_free_page (file_name_); // Update loading status to parent thread current_thread->exit_status = success ? 0 : TID_ERROR; sema_up(¤t_thread->sema_child); if (!success) thread_exit(); // other codes }
- 자식 스레드에 의해 실행되는 이 함수는 이제
process_exit()- 이 함수는 프로세스 종료 시 포괄적인 자원 정리(resource cleanup)를 수행하도록 크게 확장되었습니다:
donors리스트 정리:donors리스트에 남아 있는 모든 스레드 객체를 반복하여 제거함으로써, 적절한 할당 해제를 보장하고 메모리 누수를 방지합니다.
- 자식 프로세스 대기:
- 자신의
children리스트를 반복하며 남아 있는 각 자식에 대해process_wait()을 호출합니다. - 이는 부모가 종료하기 전에 모든 자식을 적절히 정리하도록 보장하여 고아 프로세스(orphaned processes)를 방지합니다.
- 자신의
- 파일 디스크립터 테이블 정리:
fd_table을 반복하며 열려 있는 모든struct file*객체에 대해file_close()를 호출하여, 프로세스가 열었던 모든 파일을 명시적으로 닫습니다.
- 또한, 파트 3에서 논의된 대로
current_running_file(실행 파일 자체)도 명시적으로 닫습니다.
- 파일 정리와 관련된 모든 작업이 수행되는 동안
filesys_lock을 획득하고 모든 정리가 완료된 후 해제하여, 종료 중 파일 시스템 작업의 원자성(atomicity)을 보장합니다.Click to see the refined code
void process_exit (void) { struct thread *cur = thread_current (); uint32_t *pd; for(struct list_elem *current_element = list_begin(&cur->donors), *end_element = list_end(&cur->donors), *next_element; current_element != end_element;) { next_element = list_next(current_element); struct thread *thread_current_element = list_entry(current_element, struct thread, donelem); list_remove(current_element); current_element = next_element; } for(struct list_elem *current_element = list_begin(&cur->children), *end_element = list_end(&cur->children), *next_element; current_element != end_element;) { next_element = list_next(current_element); struct thread *thread_current_element = list_entry(current_element, struct thread, childelem); process_wait(thread_current_element->tid); current_element = next_element; } if(lock_held_by_current_thread(&filesys_lock) == false) lock_acquire(&filesys_lock); /* deallocate opened file objects */ for(--cur->next_fd; cur->next_fd > -1; --cur->next_fd) file_close(cur->fd_table[cur->next_fd]); file_close(cur->current_running_file); /* Destroy the current process's page directory and switch back to the kernel-only page directory. */ pd = cur->pagedir; if (pd != NULL) { /* Correct ordering here is crucial. We must set cur->pagedir to NULL before switching page directories, so that a timer interrupt can't switch back to the process page directory. We must activate the base page directory before destroying the process's page directory, or our active page directory will be one that's been freed (and cleared). */ cur->pagedir = NULL; pagedir_activate (NULL); pagedir_destroy (pd); } lock_release(&filesys_lock); }
- 이 함수는 프로세스 종료 시 포괄적인 자원 정리(resource cleanup)를 수행하도록 크게 확장되었습니다:
exception.c- 이 수정 사항은 OS 기능 자체와는 관련이 없지만, 테스트를 통과하기 위해 필수적이라는 점에 주목할 필요가 있습니다.
kill()- 기본적으로 치명적인 오류가 발생하면 PintOS는 시스템을 패닉(panic)시키기 위해
kill()을 호출합니다. - 그러나 현재는
kill()이 시스템 패닉을 유발하는 대신 단순히exit(-1)을 호출합니다.
Click to see the refined code
static void kill (struct intr_frame *f) { /* This interrupt is one (probably) caused by a user process. For example, the process might have tried to access unmapped virtual memory (a page fault). For now, we simply kill the user process. Later, we'll want to handle page faults in the kernel. Real Unix-like operating systems pass most exceptions back to the process via signals, but we don't implement them. */ /* The interrupt frame's code segment value tells us where the exception originated. */ /* switch (f->cs) { case SEL_UCSEG: // User's code segment, so it's a user exception, as we // expected. Kill the user process. printf ("%s: dying due to interrupt %#04x (%s).\n", thread_name (), f->vec_no, intr_name (f->vec_no)); intr_dump_frame (f); thread_exit (); case SEL_KCSEG: // Kernel's code segment, which indicates a kernel bug. // Kernel code shouldn't throw exceptions. (Page faults // may cause kernel exceptions--but they shouldn't arrive // here.) Panic the kernel to make the point. intr_dump_frame (f); PANIC ("Kernel bug - unexpected interrupt in kernel"); default: // Some other code segment? Shouldn't happen. Panic the kernel. printf ("Interrupt %#04x (%s) in unknown segment %04x\n", f->vec_no, intr_name (f->vec_no), f->cs); thread_exit (); } */ exit(-1); }- 기본적으로 치명적인 오류가 발생하면 PintOS는 시스템을 패닉(panic)시키기 위해
page_fault()- 마찬가지로, 테스트를 통과하기 위해
printf()문이 주석 처리되었습니다.Click to see the refined code
static void page_fault (struct intr_frame *f) { // other codes write = (f->error_code & PF_W) != 0; user = (f->error_code & PF_U) != 0; /* To implement virtual memory, delete the rest of the function body, and replace it with code that brings in the page to which fault_addr refers. */ /*printf ("Page fault at %p: %s error %s page in %s context.\n", fault_addr, not_present ? "not present" : "rights violation", write ? "writing" : "reading", user ? "user" : "kernel"); */ kill (f); }
- 마찬가지로, 테스트를 통과하기 위해
exception_print_stats()- 마찬가지로, 테스트를 통과하기 위해
printf()문이 주석 처리되었습니다.Click to see the refined code
void exception_print_stats (void) { //printf ("Exception: %lld page faults\n", page_fault_cnt); ; }
- 마찬가지로, 테스트를 통과하기 위해
결론
- PintOS는 이제 견고하고 기능적인 시스템 호출 집합을 지원하여, 사용자 프로그램이 프로세스 관리, 파일 생성, 읽기, 쓰기 및 커널과의 다양한 기타 상호 작용과 같은 복잡한 작업을 수행할 수 있게 되었습니다.
- 이러한 구현은 적절한 인자 유효성 검사 및 동기화와 결합되어 운영체제의 기능과 안정성을 크게 향상시키며, 더욱 정교한 사용자 응용 프로그램의 실행을 가능하게 합니다.
향상된 점수

최종 의견
- 이러한 개선 사항들은 다음을 통해 PintOS를 근본적으로 더욱 유능하고 견고한 운영체제로 변모시켰습니다:
- 수정된 프로세스 관리를 통한 다중 작업(multitasking) 환경 구축.
- 명령줄 입력 활성화.
- 견고한 동기화 메커니즘과 포괄적인 시스템 호출 집합을 통한 파일 시스템 무결성 보장.
- 이 두 번째 프로젝트는 PintOS의 기능을 크게 확장하고, 자원 활용 및 스레드 조정을 최적화하여, PintOS를 완전한 기능을 갖춘 운영체제 환경에 더욱 가깝게 만들었습니다.
Leave a comment