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
시스템 콜 호출이어야 한다
시스템 콜에 잘못된 인자 전달된 경우, 다음 중 하나로 대응해야 한다
오류값 반환, 정의되지 않은 값 반환, 프로세스 종료