GitBOOK이다
프로젝트 2 기본적으로 테스트 돌리려면 다 읽어봐야 하기에
정리 한다
시스템 콜 구현하려면 사용자 가상 주소 공간의 데이터 읽고 쓰기가 필요하다
인자 가져오기만 할땐 그리 필요하지 않지만…
시스템 콜의 인자로 전달된 포인터가 가리키는 데이터를 읽거나 쓸 때는
반드시 필요하다 (프록시 기능)
잘못된 포인터, 커널 메모리 가리키는 포인터, 부분만 겹치는 블록을 넘길 수 있기엔
이러한 경우 해당 사용자 프로세스 종료해 처리해야 한다
userprog/syscallc에 시스템 콜 핸들러 구현해야한다
기본 스켈레톤 구현은 그냥 프로세스 종료하지 시스템 콜 처리 안한다
시스템 콜 번호 가져와 인자를 읽어 동작 수행하도록 바꿔야 한다
운영체제가 사용자 프로그램으로 부터 제외 되찾는 방식을 배웠고
CPU 외부의 요인으로 발생하기에 외부 인터럽트라고 한다
운영체제는 소프트웨어 예외(프로그램 코드에서 발생하는 사건)도 처리해야 한다
페이지 폴트, 0으로 나누기 등이 그 오류다
예외는 또한 시스템 콜이기도 하다
syscall 명령은 시스템 콜을 호출하는 가장 일반적인 수단이다
Pintos에서도 이를 쓴다
유의할 점은 syscall을 실행하기 직전 일반적인 규약대로
레지스터에 시스템 콜 번화와 인자들을 설정해야 한다는건데 두 가지 예외가 있다
%rax에는 시스템 콜 번호를 넣는다
네 번째 인자는 %rcx가 아니라 %r10 에 담는다
따라서 syscall_handler()가 제어를 넘겨받을 때,
시스템 콜 번호는 rax 에 있고
인자들은 %rdi, %rsi, %rdx, %r10, %r8, %r9 순서로 전달된다
호출자의 레지스터들은 핸들러에 전달되는 struct intr_frame을 통해 접근 가능하다
반환값은 RAX 레지스에 넣는게 관례다
struct intr_frame을 통해 rax의 멤버를 수정해 값을 돌려줄 수 있다
구현해야 할 시스템 콜들이다
프로토타입은 include/lib/user/syscall.h를 포함한 사용자 프로그램에 보이는 인터페이스다
각 시스템 콜의 번호는 include/lib/syscall-nr.h에 정의되어 있다
void halt (void);power_off()를 호출하여 Pintos를 종료 (선언: src/include/threads/init.h)
교착 상태 등에 대한 정보를 잃을 수 있으므로 드물게만 사용되어야 한다
void exit (int status);현재 사용자 프로그램을 종료하고, status를 커널에 반환한다
부모가 이 프로세스를 wait하면 이 값이 반환된다 (아래 참조)
0은 성공, 0이 아니면 오류 의미한다 (관례적으로)
pid_t fork (const char *thread_name)현재 프로세스를 복제하여 이름이 THREAD_NAME인 새 프로세스를 만든다
레지스터 중 callee-saved인 %RBX, %RSP, %RBP, %R12–%R15만 복제하면 되고, 나머지 레지스터 값은 복제할 필요가 없다고 한다
뭔 소리냐 하면 피호출자가 책임지고 관리하는 집합이 callee-saved다
이 집합을 그대로 복사하고 부모/자식 규칙에 따라 RAX만 잘 설정하면 된다
성공 시 자식의 pid를 반환하고, 자식 프로세스 쪽의 반환값은 0
자식은 파일 디스크립터와 가상 메모리 공간 포함해 리소스 복제해야한다
부모는 자식의 복제 성공여부 알기 전까지는 fork에서 반환 금지
만약 실패했을 경우 TID_ERROR를 반환해야 한다
int exec (const char *cmd_line);현재 프로세스를 cmd_line로 주어진 실행 파일로 바꾸며 인자들도 함께 전달한다
성공하면 반환 없다
못하면 -1 종료상태로 끝난다
파일 디스크립터는 exec 이후에도 열린 상태로 유지된다
int wait (pid_t pid);자식 프로세스 pid를 기다리고 종료 상태 돌려준다
pid가 살아있으면 종료 할 떄까지 기다린다
그리고 exit에 전달된 상태 반환
pid가 exit()호출 안했는데 커널에서 종료할 경우 -1 반환
wait호출 전에 종료되어도 커널은 부모가 자식의 종료 상태를 가져가거나 커널이 자식을 종료시켰다는 사실을 언제든 알 수 있게 해야 한다
다음은 wait의 실패 조건들이다 -1 반환해야 한다
pid가 호출 프로세스의 직계자식이 아닐 경우
바로 아래 직계가 아닐 경우 의미한다
wait을 중복 호출 했을 경우
모든 wait패턴 고려해야 한다
어느때든 리소스도 잘 해제되어야 하고 말이다
초기 프로세스가 종료될 떄까지는 pintos도 종료되지 않아야 한다
bool create (const char *file, unsigned initial_size);이름 file의 새 파일 만들고 초기 크기를 initial_size 바이트로 설정한다
성공 시 true, 실패 시 false 반환
여는 것과 별개다
bool remove (const char *file);이름이 file인 파일을 삭제하고 성공 시 true, 실패 시 false
파일이 열려 있든 닫혀 있든 삭제 가능
열려있는건 삭제해도 닫히지는 않는다
(닫히고 나면 그제서야 공간 해제)
int open (const char *file);이름이 file인 파일 연다
파일 디스크립터 반환하며 못열면 -1 반환
int filesize (int fd);fd로 열린 파일의 바이트 단위 크기 반환
int read (int fd, void *buffer, unsigned size);파일 디스크립터 fd에서 size 바이트 읽어 buffer에 저장
읽은 바이트 반환하고 못 읽었으면 -1 반환
fd0은 input_getc()로 키보드에서 읽는다
int write (int fd, const void *buffer, unsigned size);fd에 buffer로부터 size바이트에 쓴다
실제로 쓴 바이트 수 반환
가능한 만큼 쓰고 쓴 바이트 수 반환 시작도 못하면 0
fd 1은 콘솔에 쓴다
콘솔에 쓰는 코드는 수백바이트 이하면 그냥 putbuf() 한 번 호출해서
전체 buffer 출력
void seek (int fd, unsigned position);fd에서 다음에 읽거나 쓸 바이트의 위치를
파일 시작부터 postion 바이트로 바꾼다
현재 파일 끝 넘어도 오류가 아니란다
이후 읽을 때는 0바이트 얻어 파일 끝 나타낸다
이후 쓰기는 파일 확장하며 쓰지 않은 틈 0으로 채운다
(프로젝트 4전에는 파일 길이 고정이라 넘으면 오류처리)
파일 시스템에서 하는 거라 시스템 콜 구현시엔 딱히 할 거 없단다
unsigned tell (int fd);열린 파일 fd에서 다음에 읽거나 쓸 바이트의 파일 시작 기준 위치 반환
void close (int fd);파일 디스크립터 fd를 닫는다
프로세스가 종료될떄에는 각 열린 fd에 대해 이 함수 호출한 것처럼 닫힌다
시스템 콜 여러개 호출해도 안전하도록 동기화 해야 한다
특히, filesys 디렉터리에 제공된 파일 시스템 코드를
여러 스레드가 동시에 호출하는 것은 위험하다
그렇기에 시스템 콜 구현에서 파일 시스템 코드를 임계 구역으로 다루어야 한다
아직은 filesys 디렉터리 코드 수정하는 걸 권장하지 않는다
구현 이후 사용자 프로그램은 절대 OS를 오동작으로 몰아가면 안된다
OS를 멈추는 유일한 방법은 halt 시스템 콜 호출이어야 한다
시스템 콜에 잘못된 인자 전달된 경우, 다음 중 하나로 대응해야 한다
오류값 반환, 정의되지 않은 값 반환, 프로세스 종료