pintos-kaist 시스템 콜 해석
시스템 콜 인프라스트럭처를 구현합니다.
userprog/syscall.c에 시스템 콜 핸들러를 구현합니다. 현재는 프로세스를 종료함으로써 시스템 콜을 "처리"하는 기본 구현상태입니다. 시스템 콜 번호를 취득한 후 임의의 시스템콜 인수를 취득하여 적절한 액션을 실행해야 합니다.
시스템 콜 details
첫 번째 프로젝트(threads)에서는 이미 운영체제가 사용자 프로그램에서 제어권을 회복할 수 있는 한 가지 방법, 즉 타이머와 I/O 디바이스로부터의 인터럽트를 다루었습니다. CPU 외부의 엔티티에 의해 발생하므로 이러한 인터럽트는 "외부" 인터럽트입니다.
운영체제는 프로그램 코드에서 발생하는 이벤트인 소프트웨어 예외도 처리합니다. 이러한 오류는 페이지 오류 또는 0으로 나누기 등의 오류일 수 있습니다. 예외는 사용자 프로그램이 운영 체제에서 서비스(”system call”)를 요청할 수 있는 수단이기도 합니다.
기존 x86 아키텍처에서는 시스템콜은 다른 소프트웨어 예외와 동일하게 처리되었습니다. 단, x86-64에서는 공정에서 시스템 호출에 대해 특별한 instruction을 도입하고 있습니다. syscall이를 통해 시스템콜 핸들러를 빠르게 호출할 수 있습니다.
syscall명령어는 x86-64에서 시스템콜을 호출하기 위해 가장 일반적으로 사용되는 수단입니다.Pintos에서는 사용자 프로그램이 호출합니다. 시스템 콜 번호 및 기타 인수는 통상적인 방법으로 레지스터에 설정하는데, 다음 두가지 레지스터에 대한 예외가 있습니다.
- %rax 는 시스템 콜 번호입니다.
- 네 번째 argument는 %rcx 가 아니고 %r10이다.
따라서 시스템 콜 핸들러인 syscall_handler()는 제어를 취득하고 시스템콜 번호가 rax 내에 있으며 arguments들은 %rdi, %rsi, %rdx, %r10, %r8 , %r9 순서로 전달됩니다.
The caller's registers are accessible to struct intr_frame passed to it. (struct intr_frame is on the kernel stack.)
x86-64에서는 리턴 값을 %rax에 레지스터에 배치합니다. 값을 반환하는 시스템콜의 경우,rax멤버struct intr_frame.구조체의 member인 rax를 수정함으로서 배치할 수 있습니다.
아래의 시스템 콜들을 구현합니다.
나열된 프로토타입은 다음을 포함하는 사용자 프로그램에서 볼 수 있는 프로토타입입니다. include/lib/user/syscall.h와 include/lib/user에 있는 헤더들은 사용자 프로그램에서만 사용됩니다. 각 시스템 콜의 시스템콜 번호는include/lib/syscall-nr.h에 정의되어 있습니다.
void halt (void);
src/include/threads/init.h에 구현된power off()를 호출해 Pintos를 종료합니다. 이것은, 교착 상태 등에 관한 정보가 없어지기 때문에, 거의 사용하지 말아 주세요.
void exit (int status);
status를 커널에 반환하며 현재 사용자프로그램을 terminate합니다. 만약 부모 프로세스가 기다리고 있다면, return해줄 status가 바로 이 status입니다. 일반적으로 0은 성공을, 0이 아닌 값은 error를 가리킵니다.
pid_t fork (const char *thread_name);
thread_name 라는 이름으로 현재 프로세스의 클론(복사본)을 만듭니다. callee saved 레지스터인 rbx, rsp, rbp, r12 ~ r15를 제외하고는 clone할 필요가 없습니다. 반드시 자식 프로세스의 pid를 반환해야 하고, 그렇지 않은 경우엔 유효한 pid가 되어선 안 됩니다. 자식프로세스는 반드시 0을 리턴해야 하고, file descriptor와 virtual memory space를 포함하는 모든 정보를 복사해야합니다. 부모 프로세스는 fork에서 자식 프로세스의 clone 성공여부를 확인하기 전까지 리턴해선 안 됩니다. 즉, 자식프로세스가 자원을 복사하는데 실패한 경우, 부모에서 호출한 fork는 반드시 TID_ERROR를 반환합니다.
The template utilizes the pml4_for_each() in threads/mmu.c to copy entire user memory space, including corresponding pagetable structures, but you need to fill missing parts of passed pte_for_each_func(See virtual address).
int exec (const char *cmd_line);
cmd_line으로 주어진 이름의 프로세스를 arguments를 넘겨주며 실행가능하게 바꿉니다. 만약 프로그램이 load나 run을 제대로 수행하지 못한 경우, 프로세스는 -1의 상태를 갖고 종료(terminate)됩니다. exec를 호출한 스레드의 이름을 바꾸는 것이 아닙니다.
int wait (pid_t pid);
pid(자식 프로세스)를 기다리고, exit status를 얻습니다. 만약 pid가 살아있는 경우에는 종료(terminates)될 때까지 기다립니다. 그리고는 자식 프로세스가 exit하면서 넘겨준 status를 리턴합니다. 만약 pid가 exit()을 호출하지 않고 커널에 의해 종료된 경우, wait(pid)는 -1를 반환합니다. 부모 프로세스가 부모 호출 대기 시간까지 이미 종료된 자식 프로세스를 기다리는 것은 완전히 합법적이지만, 커널은 여전히 부모가 자녀의 종료 상태를 가져오도록 허용하거나 pid가 커널에 의해 종료되었음을 알 수 있어야 합니다.
만약 아래의 상태에 해당한다면, wait은 반드시 실패하고 즉시 -1을 리턴해야합니다.
- pid 는, 호출한 프로세스의 아이를 직접 참조하고 있지 않습니다. 오직 fork함수 호출이 성공한 경우에만 직접 가리킵니다. 자녀는 상속되지 않습니다: A가 자녀B를 생성하고 B가 자녀프로세스 C를 생성하면 B가 사망해도 A는 C를 기다리지 않습니다. A가 wait(C)를 호출하는 경우 반드시 실패합니다. 마찬가지로 고립된 프로세스는 부모 프로세스가 다른 부모에게 할당해주지 않은 경우, 다른 부모에게 할당되지 않습니다.
- The process that calls wait has already called wait on pid. That is, a process may wait for any given child at most once.
과정은 많은 수의 아이들을 가질 수 있고. 어떤 순서로든(심지어 자녀들의 일부 또는 전부를 기다리지 않고) 퇴장(exit)할 수도 있습니다. 당신의 설계는 반드시 wait이 발생할 수 있는 상황을 고려해야 합니다. struct thread를 포함한 프로세스의 모든 자원은 부모가 대기하고 있는지 여부에 관계없이, 또한 자녀가 부모보다 먼저 종료하는지 또는 부모보다 나중에 종료하는지 여부에 관계없이 free해주어야 합니다.
초기 프로세스가 종료될 때까지 Pintos가 종료되지 않도록 해야 합니다. 제공된 Pintos 코드는, thread.init.c 안에 있는 main에서, process_wait을 호출함으로서 그리 하려고 시도하고 있습니다. 함수 위에 있는 주석에 따라 구현할 것을 권장합니다.
이 시스템 콜을 구현하려면 다른 어떤 작업보다 훨씬 많은 작업이 필요합니다.
bool create (const char *file, unsigned initial_size);
initial_size를 갖는 file이라는 이름의 새 파일을 만듭니다. 성공하면 true를 반환하고 그렇지 않으면 false를 반환합니다. 새 파일을 생성해도 파일이 열리지 않습니다. 새 파일을 여는 작업은 별도의 작업으로,open시스템 콜을 실행합니다.
bool remove (const char *file);
file이라는 이름을 갖는 파일을 삭제합니다. 성공하면 true를 반환하고 그렇지 않으면 false를 반환합니다. 파일은 열려 있는지 닫혀 있는지 여부에 관계없이 삭제할 수 있으며 열려 있는 파일을 삭제해도 닫히지 않습니다.자세한 내용은 FAQ에서 열린 파일 제거를 참조하십시오.
int open (const char *file);
file이라는 이름의 파일을 엽니다. "file descriptor"(fd)라는 음수가 아닌 정수 핸들을 반환합니다. 파일을 열 수 없는 경우 -1을 반환합니다. file descriptor 0과 1은 콘솔용으로 예약되어 있습니다. 각각 표준 입력과 표준 출력입니다. 이 둘은 open 시스템 콜에서 절대 반환되지 않고, 아래처럼 명시한 경우에만 유효합니다. 각 프로세스는 별도의 file descriptor를 갖지만 자식프로세스에게는 상속 가능합니다. 하나의 파일이 여러번 open 된 경우, 매 open은 새로운 descriptor를 반환합니다. 동일 파일에 대한 같은 descriptor는 close도 각각 호출해줘야하고, file position을 공유하지 않습니다. 추가 작업을 수행하려면 0부터 시작하는 정수를 반환하는 Linux 스키마를 따라야 합니다.
int filesize (int fd);
열려 있는 파일의 크기(바이트)를 반환합니다.
int read (int fd, void *buffer, unsigned size);
size만큼의 byte를 읽어 buffer에 저장합니다. 실제로 읽기 성공한 만큼의 바이트를 반환하고, EOF처럼 읽을 수 없는 경우엔 -1을 반환합니다. fd가 0인 경우엔 input_getc()를 이용해 키보드로부터 입력을 받습니다.
int write (int fd, const void *buffer, unsigned size);
buffer의 내용을 fd에 size만큼 씁니다. 실제로 쓰기 성공한 수를 반환합니다. EOF에 쓰는 경우, 보통 파일을 확장하지만 현재는 구현되어있지 않습니다. 가능한만큼만 쓰고 그 수를 반환합니다(0~). fd가 1인 경우엔 콘솔에 씁니다. 콘솔에 쓰는 코드는 모든 버퍼를 한 번의 호출로 쓰기 위해putbuf()사이즈가 수백 바이트를 넘지 않는 한(더 큰 버퍼는 분할하는 것이 적절합니다).그렇지 않으면 서로 다른 프로세스에 의해 출력되는 텍스트 행이 콘솔에 인터리빙되어 휴먼 리더와 그레이딩 스크립트가 혼동될 수 있습니다.
void seek (int fd, unsigned position);
열려 있는 파일에 읽거나 쓸 다음 위치를 변경합니다. 파일 선두로부터의 바이트 단위(따라서 position이 0인 경우, 파일의 시작입니다).파일의 현재 끝을 지난 검색은 오류가 아닙니다. 나중에 구현할read는 파일의 끝을 나타내는0 바이트를 취득합니다. 나중에 구현할 write는 파일을 확장하여 쓰이지 않은 공백을 0으로 채웁니다.(단, Pintos 파일은 프로젝트4가 완료될 때까지 길이가 정해져 있기 때문에write에러가 반환됩니다).이러한 의미론은 파일시스템에 실장되어 시스템콜 구현에 특별한 노력이 필요하지 않습니다.
unsigned tell (int fd);
열려 있는 파일에 읽거나 쓸 다음 바이트의 위치를 반환합니다.fd파일 선두부터 바이트 단위로 표시됩니다.
void close (int fd);
파일 설명자를 닫습니다.fd. 프로세스를 종료하거나 종료하면 열려 있는 모든 파일 기술자가 암묵적으로 닫힙니다.각 기능에 대해 이 함수를 호출하는 것과 같습니다.
파일은 다른 시스템 콜을 정의합니다.일단 그들을 무시하세요.그 중 일부는 프로젝트3에서, 나머지는 프로젝트4에서 실장하기 때문에 반드시 확장성을 고려하여 시스템을 설계해 주십시오.
임의의 수의 사용자 프로세스가 동시에 발신할 수 있도록 시스템콜을 동기화해야 합니다. 특히 여러 스레드에서 동시에 filesys에 기재되어 있는 파일 시스템 코드를 호출하는 것은 안전하지 않습니다. 시스템 콜 구현은 파일시스템 코드를 중요한 섹션으로 취급해야 합니다. 그걸 잊지 마세요. process_exec()파일에도 액세스합니다.현시점에서는, filesys의 코드는 변경은 하지 않는 것을 추천합니다.
lib/user/syscall.c에서 각 시스템 호출에 대해 사용자 수준의 기능을 제공하고 있습니다. 사용자 프로세스가 C 프로그램에서 각 시스템콜을 호출하는 방법을 제공합니다.각각은 작은 인라인어셈블리 코드를 사용하여 시스템콜을 호출하고 (필요에 따라) 시스템콜의 반환값을 반환합니다.
이 파트를 다 쓰고 나면, 핀토스는 영원히 bullteproof가 될 겁니다. 유저 프로그램이 실행할 수 있는 것은, OS 의 크래시, 패닉, 어설션의 실패, 또는 그 외의 오작동의 원인이 되지 않습니다. 다음을 강조하는 것이 중요합니다. 우리는 당신의 시스템 콜을 다양한 방법으로 망가뜨리려 할 겁니다. 당신은 이러한 corner case들에 대해 모두 고려해야 하고 handle해야합니다.
시스템 콜이 비활성 인수로 전달된 경우 허용 가능한 옵션에는 오류 값 반환, 정의되지 않은 값 반환 또는 프로세스 종료가 포함됩니다.